【发布时间】:2019-02-04 10:21:49
【问题描述】:
我有一个服务器程序,它使用 select 和 10 秒超时来等待多个非阻塞客户端连接上的活动。每次select 调用表明有输入要读取时,服务器将读取并处理多达 1K 的数据。如果有要发送的响应,它将发送它。然后它会回到select。
fcntl(clientFd, F_SETFL, fcntl(clientFd, F_GETFL, 0) | O_NONBLOCK);
while (true) {
FD_ZERO(&readfds);
FD_SET(clientFd, &readfds);
timeout = (struct timeval){.tv_sec = 10};
select(clientFd + 1, &readfds, NULL, NULL, &timeout);
if (FD_ISSET(clientFd, &readfds)) {
uint8_t recv_buffer[1024];
fread(recv_buffer, 1, 1024, clientFile);
// process & maybe respond / fflush
}
}
客户端消息的范围从小型(10 或 100 字节)消息到大型(3-10K 消息)。客户端将在挂断之前等待最多 30 秒的响应。当他们挂断时,他们会发送一个 10 字节的小挂断消息。
一个场景正在上演,打破了我对select 应该如何工作的理解。客户端发送的消息小于读取缓冲区,因此服务器读取整个内容并做出响应。然后客户端发送一条 3K 消息。服务器读取第一个 1K,然后随后的select 调用超时。我希望select 立即返回并表示如果内核为该文件描述符缓冲了数据,则有可用的数据。客户端超时后,发送挂断消息。当挂断消息到达时,服务器突然能够select该文件描述符,它读取客户端较大消息的第二和第三块,然后是客户端较小的消息。
我非常确定这些事件的发生时间,因为 (1) 涉及的长时间超时,(2) 确认 3K 消息作为单个 TCP 段到达的对话的 tcpdump。
使用pipe 的简单演示程序不会出现这种行为,另一个使用TCP 套接字的简单演示程序也不会出现这种情况。所以我必须在服务器程序中做一些愚蠢的事情。我应该检查什么?
我正在检查:
- 客户端的read FD在select之前的read FD set中
- 客户端的read FD是否在select后的read FD set中(不在)
- 读取数据的大小(如果至少有这么多可用,则始终为 1K)
- 对
fread的第二次调用是否会阻塞(它不会。我在调试器中尝试过,我还使用大小为 4K 的recv_buffer进行了不同的运行,它读取了整个 3K 消息) - 通过
tcpdump的数据包分段(无)
【问题讨论】:
-
fread是一个缓冲 I/O 函数。你已经放弃了所有的控制权。您不知道从底层文件描述符中真正读取了多少字节。您可以尝试使用setvbuf使您的文件无缓冲。 -
你应该使用 read() 并实际检查 select 的返回值。
-
@n.m.我没有考虑过
fread会在libc内部缓冲而不使用内核的缓冲区。 -
@Huckle 哦,我的错。我将删除评论。我倾向于在大多数时间使用民意调查而不是选择并误读手册。
-
然后你必须问自己
fread与read有什么不同,以及为什么它首先存在于上帝的绿色地球上。
标签: c select linux-kernel posix nonblocking