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

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

服务器只是位于阻塞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()有一个时间值来让它进行轮询,那么它永远不会发出信号表明有传入的数据要读取,我的应用程序冻结认为没有建立网络连接(尽管事实上所有其他函数调用都已成功)

关于我可能做错什么的任何指示? 是不是可以在同一个套接字上进行读写(我不能相信这是真的)。

(编辑:正确的答案,我记得在服务器上而不是在客户端上的东西,是有第二个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 } 

一切看起来都很好,除了你做的行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之前。 我不记得为什么。

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

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

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

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