11

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

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

问题是客户端。为了响应读取,它有时会想要进行写入。

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

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

我的代码将阻塞,直到有新数据要读取。但有时(从另一个线程异步)我会有新数据要写入网络通信线程。所以,我希望我的选择定期唤醒并让我检查是否有数据要写入,例如:

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);

但发现然后客户端永远不会收到任何传入数据。

我还尝试将套接字 fd 设置为非阻塞,例如:

fcntl(sockfd, F_SETFL, O_NONBLOCK);

但这并不能解决问题:

  1. 如果我的客户select()没有struct timeval,则读取数据有效,但它永远不会解除阻止让我查找可写数据。
  2. 如果我的客户select()有一个timeval让它轮询,那么它永远不会发出有要读取的传入数据的信号,并且我的应用程序冻结认为没有建立网络连接(尽管所有其他函数调用都已成功)

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

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

4 回答 4

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 可能会出现问题(就像您似乎正在做的那样?)

编辑2。我记得我在退出选择后和再次输入之前没有清除读取集时遇到了一个奇怪的问题。我不得不做类似的事情:

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

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

于 2010-01-14T10:23:17.677 回答
7

我似乎想起了一个关于在网络线程和添加到 select 调用中的描述符的主线程之间创建和共享读/写文件描述符的技巧。当有东西要发送时,这个 fd 有一个字节由主线程写入它。write 从 select 调用中唤醒网络线程,然后网络线程从共享缓冲区中获取数据并将其写入网络,然后在 select 中返回睡眠。

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

于 2010-01-14T10:27:35.750 回答
1

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

于 2010-01-14T10:29:03.313 回答
0

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

于 2010-03-09T00:13:22.480 回答