C中的双向tcp套接字问题

时间:2012-07-26 00:52:41

标签: c sockets tcp client bidirectional

我正在尝试在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次并尝试了每一个广告 - 我可以想象的特定变化,但行为仍然是相同的。因此,在我开始询问具体代码摘录的具体问题之前,我想知道我对这个问题的处理方法是否正确,或者我是否在这里遇到了一些严重的概念问题。

非常感谢您的回答,

ADAM

P.S。我无法在评论中发布此代码部分,因为它太长了;这是管理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
      }
   }
}

3 个答案:

答案 0 :(得分:2)

其他人已对connect(2)listen(2)的不兼容性发表了评论。你也不需要bind(2)

使用select(2)时最常见的错误是在每次迭代时不重新初始化文件描述符集select(2)的第二个到第二个参数是输入输出,因此您每次都必须重做它们。

编辑0:

在您发布代码之后,让我补充一点,您完全没有从select(2)给您的I / O解复用中受益。通过新的连接尝试从select(2)唤醒和客户端在您输入阻止accept(2)之前删除该连接之间也存在众所周知的竞争。

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

答案 1 :(得分:2)

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

答案 2 :(得分:0)

所以,这里有几个独立的问题,它们在几个答案和评论之间分开,所以我将对它们进行总结。

发件人帖子没问题。但是,连接器/侦听器线程应如下所示:

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

关于最初发布的代码摘录,用于处理while的{​​{1}}周期是一个概念错误,应该删除,因为它会阻塞线程 - 这会使recv 1}}语句不必要(因为在这种情况下,select没有实际效果)。

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