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

发布于 2024-08-17 15:35:06 字数 1599 浏览 1 评论 0原文

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

服务器只是坐在阻塞的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
}

:)

We're writing a client and a server to do (what I thought was) pretty simple network communications. Mulitple clients connect to the server which then is supposed to send the data back to all other clients.

The server just sits in a blocking select loop waiting for traffic, and when it comes, sends the data to the other clients. This seems to work just fine.

The problem is the client. In response to a read, it will sometimes want to do a write.

However, I've found that if I use:

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

My code will block until there is new data to read. But sometimes (asynchronously, from another thread) I'll have new data to write on the network communication thread. So, I want my select to wake up periodically and let me check if there are data to write, like:

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

I tried setting the select to poll mode (or ridiculously short timeouts) with:

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

but have found that then then client never gets any incoming data.

I also tried setting the socket fd to be non-blocking, like:

fcntl(sockfd, F_SETFL, O_NONBLOCK);

but that doesn't solve the problem:

  1. if my client select() has no struct timeval, reading data works, but it never unblocks to let me look for writable data.
  2. if my client select() has a timeval to get it to poll, then it never signals that there are incoming data to read, and my app freezes thinking there is no network connection made (despite the fact that all other function calls have succeeded)

Any pointers at all as to what I could be doing wrong? Is it not possible to do read-write on the same socket (I can't believe that to be true).

(EDIT: The correct answer, and thing that I remembered on the server but not on the client, is to have a second fd_set, and copy master_list before each call to select():

// 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
}

)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

去了角落 2024-08-24 15:35:06

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

编辑。另外,我对超时有以下内容:

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

如果将其设置为 0 (就像您似乎正在做的那样?),也许会出现问题

。我记得当我在选择退出后和再次输入之前没有清除读取集时,遇到了一个奇怪的问题。 我必须执行以下操作:

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

在输入 select 之前, 但我不记得为什么。

Everything looks fine, except the line where you do if (FD_SET(sockfd, &master_list)). I have a very similar code structure and I used FD_ISSET. You're supposed to test if the list is set, not to set it again. Other than that, I see nothing else.

Edit. Also, I have the following for the timeout:

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

perhaps there's an issue if you set it to 0 (as you seem to be doing?)

Edit2. I remembered I ran into a strange problem when I wasn't clearing the read set after the select exited and before I entered it again. I had to do something like:

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

before I was entering select. I can't remember why though.

云淡月浅 2024-08-24 15:35:06

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

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

I seem to recall a trick about creating and sharing a read/write filedescriptor between the network thread and the main thread that is added to the descriptors in the select call.
This fd has one byte written to it by the main thread when it has something to send. The write wakes up network thread from the select call and the network thread is then grabs the data from a shared buffer and writes it to the network then go back to sleep in the select.

Sorry if that's a bit vague and lacking of code ... and my memory may be incorrect.. so others may have to guide you further.

溺ぐ爱和你が 2024-08-24 15:35:06

我没有发现你的代码有任何问题,所以它应该可以工作。如果您无法使其工作,解决此问题的一种方法是创建一个供您的读取线程和准备写入的线程使用的管道,并将管道的读取端添加到您的 select< /代码> 设置。然后,当另一个线程准备好要写入的数据时,它只是在管道上发送一些内容,您的读取线程将从 select 中唤醒,然后它就可以进行写入。根据读取或写入数据的频率,这也可能会更有效。

I don't see anything wrong with your code, so it should work. If you can't get it working, one way to work around it would be to create a pipe to be used by your reading thread and the thread that prepares things for writing, and add the pipe's reading end to your select set. Then, when the other thread has prepared data to write, it just sends something on the pipe, your reading thread gets woken up from the select, and it can then do the writing. Depending on how often there is data to read or write, this could also be more efficient.

暖树树初阳… 2024-08-24 15:35:06

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

2 threads should be able to work with the same socket at a time, so your main thread should be able to write to a client while the other one sleeps in select waiting for incoming data. This of course assumes that both threads have access to the client list.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文