使用select读取和写入相同的套接字(TCP)

时间:2010-01-14 10:14:34

标签: c sockets select blocking read-write

我们正在编写一个客户端和服务器(我认为是)非常简单的网络通信。多个客户端连接到服务器,然后服务器将数据发送回所有其他客户端。

服务器只是位于阻塞select循环中,等待流量,当它到来时,将数据发送给其他客户端。这似乎工作得很好。

问题在于客户。在阅读时,它有时会想要写一个。

但是,我发现如果我使用:

 rv = select(fdmax + 1, &master_list, NULL, NULL, NULL);

我的代码将阻止,直到有新数据要读取。但有时(异步,来自另一个线程)我将在网络通信线程上写入新数据。所以,我希望我的select定期唤醒,让我检查是否有要写的数据,如:

if (select(....) != -1)
{
  if (FD_SET(sockfd, &master_list))
     // handle data or disconnect
  else
     // look for data to write and write() / send() those.
}

我尝试使用以下选项设置选择轮询模式(或荒谬的短暂超时):

// master list contains the sockfd from the getaddrinfo/socket/connect seq
struct timeval t;
memset(&t, 0, sizeof t);
rv = select(fdmax + 1, &master_list, NULL, NULL, &t);

但已经发现然后客户端永远不会获得任何传入数据。

我也尝试将socket fd设置为非阻塞,如:

fcntl(sockfd, F_SETFL, O_NONBLOCK);

但这并没有解决问题:

  1. 如果我的客户select()没有struct timeval,那么阅读数据是有效的,但它永远不会阻止让我查找可写数据。
  2. 如果我的客户select()有一个timeval让它进行轮询,那么它永远不会发出信号表明有传入的数据要读取,我的应用程序会冻结,认为没有建立网络连接(尽管事实上所有其他函数调用都已成功)
  3. 关于我可能做错什么的任何指示?是不是可以在同一个套接字上进行读写(我不敢相信这是真的)。

    (编辑:正确的答案,我记得在服务器上而不是在客户端上的东西,是要有第二个fd_set,并在每次调用select()之前复制master_list:

    // declare and FD_ZERO read_fds:
    // put sockfd in master_list
    
    while (1)
    {
       read_fds = master_list;
       select(...);
    
       if (FD_ISSET(read_fds))
         ....
       else
         // sleep or otherwise don't hog cpu resources
    }
    

4 个答案:

答案 0 :(得分:12)

除了你if (FD_SET(sockfd, &master_list))所在的行外,一切看起来都很好。我有一个非常相似的代码结构,我使用FD_ISSET。您应该测试列表是否已设置,而不是再次设置。除此之外,我什么都没看到。

编辑。此外,我有以下超时:

timeval listening_timeout;
listening_timeout.tv_sec = timeout_in_seconds;
listening_timeout.tv_usec = 0;

如果你把它设置为0(或者你似乎在做什么?)可能会出现问题。

EDIT2。我记得当我在退出选择之后并且在我再次输入之前没有清除读取集时,我遇到了一个奇怪的问题。我不得不这样做:

FD_ZERO(&sockfd);
FD_SET(sockfd, &rd);

在我进入select之前。我不记得为什么。

答案 1 :(得分:7)

我似乎记得在网络线程和添加到select调用中的描述符的主线程之间创建和共享读/写文件描述符的技巧。 这个fd有一个字节由主线程写入它有什么要发送的时候。写入从select调用中唤醒网络线程,然后网络线程从共享缓冲区中获取数据并将其写入网络,然后在select中返回休眠状态。

很抱歉,如果这有点模糊且缺乏代码......而且我的记忆可能不正确......所以其他人可能需要进一步指导你。

答案 2 :(得分:1)

我认为你的代码没有任何问题,所以它应该有效。如果你无法使它工作,解决它的一种方法是创建一个供你的阅读线程使用的管道和准备写作的线程,并将管道的读取端添加到select组。然后,当另一个线程准备好要写入的数据时,它只是在管道上发送一些东西,你的阅读线程从select中被唤醒,然后它就可以写了。根据读取或写入数据的频率,这也可能更有效。

答案 3 :(得分:0)

2个线程应该能够同时使用同一个套接字,因此您的主线程应该能够写入客户端,而另一个线程在select等待传入数据时休眠。这当然假设两个线程都可以访问客户端列表。