Why select() timeouts sometimes when the client is busy receiving data
我已经编写了简单的C/S应用程序来测试非阻塞套接字的特性,这里有一些关于服务器和客户端的简要信息:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
//On linux The server thread will send
//a file to the client using non-blocking socket void *SendFileThread(void *param){ CFile* theFile = (CFile*) param; int sockfd = theFile–>GetSocket(); set_non_blocking(sockfd); set_sock_sndbuf(sockfd, 1024 * 64); //set the send buffer to 64K //get the total packets count of target file //get packet data by packet no. //send_non_blocking_sock_data will loop and send
…… |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//On windows, the client thread will do something like below
//to receive the file data sent by the server via block socket void *RecvFileThread(void *param){ int sockfd = (int) param; //blocking socket set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256 while (1){ fd_set rds; //actually, the first parameter of select() is } |
令我惊讶的是,由于套接字缓冲区已满,服务器线程经常失败,例如要发送一个 14M 大小的文件,它会报告 50000 次失败,且 errno = EAGAIN。但是,通过日志记录我观察到传输过程中有数十次超时,流程如下:
为什么在接收过程中会出现交错的超时?谁能解释一下这个现象?
[更新]
1.上传一个14M的文件到服务器只需要8秒
2. 使用与1)相同的文件,服务器需要将近30秒的时间将所有数据发送到客户端。
3. 客户端使用的所有套接字都是阻塞的。服务器使用的所有套接字都是非阻塞的。
关于#2,我认为超时是#2比#1花费更多时间的原因,我想知道为什么客户端忙于接收数据时会有这么多超时。
[更新2]
感谢@Duck、@ebrobe、@EJP、@ja_mesa 的评论,我今天会做更多的调查
然后更新这篇文章。
关于为什么我在服务器线程中每个循环发送 512 个字节,这是因为我发现服务器线程发送数据的速度比客户端线程接收它们的速度快得多。我很困惑为什么客户端线程会发生超时。
- 为什么 GetFilePacketsCount() 与服务器端的 CurrPacket 有任何关系? 512 字节缓冲区的长度是不是有点随意?此外,在服务器端,您似乎会得到一大堆 EAGAIN ,但是当您正确处理它们时应该没问题。也许在 EAGAIN 上进行某种睡眠会是个好主意?等等,GetPacketData 似乎在消耗数据,所以您可能会因为 EAGAIN 多次调用它而造成间隙?
- 您如何声明/处理 rds?我认为在调用 select(…) 之前,每次循环都需要 FD_SET / FD_ZERO
- @ebyrob 谢谢,我更新了源代码。令我惊讶的是,在客户端线程忙于接收数据而服务器线程报告数千次EAGAIN失败期间,select调用发生了超时!
- 如果您使用的是非阻塞套接字,那么它不会等待实际发送数据。数据不会立即传输,有一些速度限制,这就是你得到这些超时的原因
- @西蒙是的!客户端和服务器都在(慢得多的)网络上等待,而它们的处理器在许多等待状态下竞争……
- 在写入的情况下,EAGAIN 不是”失败”:这意味着套接字发送缓冲区已满。这表明您正在以最大速度书写。类似地,等待套接字变为可写的 select() 超时意味着同样的事情。
认为这更像是一个长评论而不是答案,但正如一些人所指出的那样,网络比您的处理器慢几个数量级。非阻塞 i/o 的关键在于差异如此之大,以至于您实际上可以使用它来完成实际工作而不是阻塞。在这里,您只是在按电梯按钮,希望有所作为。
我不确定你的代码有多少是真实的,有多少是为了发布而被砍掉的,但在服务器中你没有考虑 (ret == 0),即对等方的正常关闭。
客户端中的select 错误。同样,不确定这是否是草率的编辑,但如果不是,那么参数的数量是错误的,但更令人担忧的是,第一个参数 – 即应该是 select 查看的最高文件描述符加一 – 为零。根据 select 的实现,我想知道这是否实际上只是将 select 变成了一个花哨的 sleep 语句。
- 如果他使用的是 MS WinSock,则忽略第一个参数并仅保留与 Berkeley 套接字的兼容性。无论如何,参数的数量是错误的,我也想知道为什么他使用 1 秒超时并且在 select() 超时时什么都不做。当服务器发送 512 个字节时,他还为每个 read() 创建一个 256K 缓冲区。我认为我们有工具可以使用智慧和良心,否则我们最终可能会得到这种神秘的东西。
您应该先调用 recv(),然后仅当 recv() 告诉您这样做时才调用 select()。不要先调用select(),那是浪费处理。 recv() 知道数据是立即可用还是必须等待数据到达:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
void *RecvFileThread(void *param){
int sockfd = (int) param; //blocking socket set_sock_rcvbuf(sockfd, 1024 * 256); //set the send buffer to 256 char buffer[1024 * 256]; while (1){ int ret = 0; struct timeval timeout; fd_set rds; //actually, the first parameter of select() is if (ret == 0) { // socket is readable so try read again if (len == 0) { //log the number of data it received |
在发送端也做类似的事情。先调用 send(),然后调用 select() 等待可写性,前提是 send() 告诉你这样做。
来源:https://www.codenong.com/17406155/