2

我正在尝试在 C 中实现一个 TCP 客户端,它需要按如下方式工作:

  • 能够打开到给定服务器的连接
  • 能够通过已建立的连接向服务器发送任意数据,以及
  • 能够从服务器接收任意数据(将其视为对我的客户端发送到服务器的“问题”的响应)。

例如,客户端应该能够打开与任意 HTTP 服务器的连接,发送“HEAD”消息并打印来自 HTTP 服务器的响应。

(我的目标是为我日常工作使用的特定软件环境创建一个通用的“TCP 客户端插件”,该软件环境缺乏网络功能。我非常了解我的环境的 SDK,但我真的没有套接字编程方面的深厚经验。)

目前,我有 2 个单独的线程用于发送和接收数据。接收线程的工作流程(只要用户设置了服务器地址和端口就会自动启动)如下(这里我只提到套接字调用的主序列):

globalSocket = socket(); // Create socket and store it globally
bind(); // Bind the local port
connect(); // Connect to remote host & port
listen(); // Listen to the socket
while (isAlive) {
  select(... &readfds ...); // Check for ready reader descriptors
  accept(); // Accept the incoming connection
  recv(); // Receive data from server
}
close(); // End the connection

发送者线程很简单,它使用globalSocket接收者线程创建的来执行send()命令。

现在,这是我的问题:我可以毫无问题地打开与远程服务器的连接。我也可以毫无问题地发送任意数据(我确认我发送的数据实际到达服务器端没有问题)。但是,我无法从服务器取回任何数据。经过一些测试,似乎select永远不会返回正值。

我在我的代码中尝试了很多修改(比如将参数更改为select、省略listen等),我在这一天至少阅读了 Beej 的指南 10 次,并尝试了我能想象的每一个临时更改,但行为仍然相同。因此,在我开始使用特定代码摘录提出特定问题之前,我想知道我解决这个问题的方法是否正确,或者我是否在这里遇到了一些严重的概念问题。

确实感谢您的回答,

亚当

PS 我无法在评论中发布此代码部分,因为它太长了;这是管理select- accept-recv循环的代码片段:

while ( thread->isActive ) {
   // Accept connection
   timeVal.tv_sec = TIMEOUT_SEC;
   timeVal.tv_usec = TIMEOUT_USEC;
   FD_ZERO( & fileDescriptor );
   FD_SET( socketDescriptor, & fileDescriptor ); // socketDescriptor is the global socket
   result = select ( FD_SETSIZE, & fileDescriptor, NULL, NULL, & timeVal );
   if ( result > 0 ) {
      post ( "select" ); // This line is actually never reached
      connectionDescriptor = accept ( socketDescriptor, ( struct sockaddr * ) & clientAddress, & clientLength ); // connectionDescriptor is the local socket created by accept
      if ( connectionDescriptor < 0 ) { // Some error happened
         outlet_int ( thread->parent->statusOutlet, errno );
      } else {
         // Receive data
         data.clear ( ); // 'data' is an std::vector of chars that stores the incoming data and makes it accessible for the rest of the environment
         size = thread->parent->bufferSize;
         buffer = new unsigned char [ size ]; // this buffer is used for receiving the data from 'recv'
         receivedBytes = 1;
         while ( receivedBytes > 0 ) {
            receivedBytes = recv ( connectionDescriptor, ( char * ) buffer, size, 0 );
            if ( receivedBytes < 0 ) { // Socket error
               outlet_int ( thread->parent->statusOutlet, errno );
            }
            data.insert ( data.end ( ), buffer, buffer + receivedBytes );
         }
         delete [ ] buffer;
#ifdef WIN_VERSION
         closesocket ( connectionDescriptor );
#else
         close ( connectionDescriptor );
#endif
         // Output received data
         ... blah ... blah ... blah
      }
   }
}
4

3 回答 3

2

其他人已经评论了connect(2)和的不兼容性listen(2)。你也不需要bind(2)那里。

using 最常见的错误select(2)是没有在每次迭代时重新初始化文件描述符集。select(2)的第二个到第四个参数是input-output,因此您每次都必须重做它们。

编辑0:

在您发布您的代码之后,让我补充一点,您根本没有从 I/O 解复用中受益select(2)。在被新的连接尝试唤醒select(2)和客户端在您进入阻塞之前断开该连接之间也存在众所周知的竞争accept(2)

如果您不热衷于采用非阻塞路由(使用select(2)poll(2)epoll(7)kqueue(2)等的首选方式),您不妨摆脱select(2)并在循环中接受和处理客户端连接。

于 2012-07-26T01:21:08.643 回答
2

首先,您是否在物理上接收来自远程服务器的数据包?我问是因为我在分配期间遇到了类似的问题,当我运行 tcpdump 时,它也没有显示任何接收到的数据包。问题原来是防火墙,它显然允许传出流量但阻止所有传入数据包......

于 2012-07-26T01:38:18.530 回答
0

所以,这里有几个独立的问题,分为几个答案和评论,所以我要总结一下。

发件人线程正常。但是,连接器/侦听器线程应如下所示:

socket();
connect();
while (isAlive) {
   if (select(... &readfds ...) > 0) {
      recv();
   }
}
close();

关于最初发布的代码摘录,while用于处理的循环recv是一个概念错误,应该删除,因为它会阻塞线程 - 这将使select语句变得不必要(因为在这种情况下,select不会产生实际影响)。

还应该指出,如果recv返回 0,则意味着套接字已被远程对等方关闭,因此在这种情况下,即使父线程未将while (isAlive)循环设置为 false,也应中止循环。isAlive但是,循环内不需要额外的close 语句(这也是原始代码中的概念错误)。

于 2012-07-26T09:13:27.053 回答