Linux select() 如何在 fd 变得“准备好”时发出警报?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12625224/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
how is select() alerted to an fd becoming "ready"?
提问by Mike
I don't know why I'm having a hard time finding this, but I'm looking at some linux code where we're using select()
waiting on a file descriptor to report it's ready. From the man page of select:
我不知道为什么我很难找到这个,但我正在查看一些 linux 代码,我们正在使用select()
等待文件描述符来报告它已准备就绪。从选择的手册页:
select() and pselect() allow a program to monitor multiple file descriptors,
waiting until one or more of the file descriptors become "ready" for some
class of I/O operation
So, that's great... I call select on some descriptor, give it some time out value and start to wait for the indication to go. How does the file descriptor (or owner of the descriptor) report that it's "ready" such that the select()
statement returns?
所以,那太好了……我在某个描述符上调用 select,给它一些超时值并开始等待指示消失。文件描述符(或描述符的所有者)如何报告它“准备好”以便select()
语句返回?
采纳答案by Simon Richter
It reports that it's ready byreturning.
它通过返回报告它已准备就绪。
select
waits for events that are typically outside your program's control. In essence, by calling select
, your program says "I have nothing to do until ..., please suspend my process".
select
等待通常不在程序控制范围内的事件。本质上,通过调用select
,您的程序会说“我无事可做,直到……,请暂停我的进程”。
The condition you specify is a set of events, any of which will wake you up.
您指定的条件是一组事件,其中任何一个都会唤醒您。
For example, if you are downloading something, your loop would have to wait on new data to arrive, a timeout to occur if the transfer is stuck, or the user to interrupt, which is precisely what select
does.
例如,如果你正在下载一些东西,你的循环将不得不等待新数据的到达,如果传输被卡住就会超时,或者用户中断,这正是select
它所做的。
When you have multiple downloads, data arriving on any of the connections triggers activity in your program (you need to write the data to disk), so you'd give a list of all download connections to select
in the list of file descriptors to watch for "read".
当您有多个下载时,到达任何连接的数据会触发程序中的活动(您需要将数据写入磁盘),因此您需要select
在要注意的文件描述符列表中列出所有下载连接的列表“读”。
When you upload data to somewhere at the same time, you again use select
to see whether the connection currently accepts data. If the other side is on dialup, it will acknowledge data only slowly, so your local send buffer is always full, and any attempt to write more data would block until buffer space is available, or fail. By passing the file descriptor we are sending to to select
as a "write" descriptor, we get notified as soon as buffer space is available for sending.
当您同时将数据上传到某处时,您再次使用select
查看连接当前是否接受数据。如果另一端正在拨号,它只会缓慢地确认数据,因此您的本地发送缓冲区始终已满,并且任何写入更多数据的尝试都会阻塞,直到缓冲区空间可用或失败。通过将我们要发送到的文件描述符select
作为“写入”描述符传递,我们会在缓冲区空间可用于发送时立即收到通知。
The general idea is that your program becomes event-driven, i.e. it reacts to external events from a common message loop rather than performing sequential operations. You tell the kernel "this is the set of events for which I want to do something", and the kernel gives you a set of events that have occured. It is fairly common for two events occuring simultaneously; for example, a TCP acknowledge was included in a data packet, this can make the same fd both readable (data is available) and writeable (acknowledged data has been removed from send buffer), so you should be prepared to handle all of the events before calling select
again.
一般的想法是你的程序变成事件驱动的,即它对来自公共消息循环的外部事件做出反应,而不是执行顺序操作。您告诉内核“这是我想要为其做某事的一组事件”,内核会为您提供一组已发生的事件。两个事件同时发生是相当常见的;例如,数据包中包含 TCP 确认,这可以使相同的 fd 可读(数据可用)和可写(已确认的数据已从发送缓冲区中删除),因此您应该准备好处理所有事件在select
再次打电话之前。
One of the finer points is that select
basically gives you a promise that one invocation of read
or write
will not block, without making any guarantee about the call itself. For example, if one byte of buffer space is available, you can attempt to write 10 bytes, and the kernel will come back and say "I have written 1 byte", so you should be prepared to handle this case as well. A typical approach is to have a buffer "data to be written to this fd", and as long as it is non-empty, the fd is added to the write set, and the "writeable" event is handled by attempting to write all the data currently in the buffer. If the buffer is empty afterwards, fine, if not, just wait on "writeable" again.
更好的一点是,它select
基本上给你一个承诺,即一次调用read
或write
不会阻塞,而不对调用本身做任何保证。例如,如果有 1 个字节的缓冲区空间可用,您可以尝试写入 10 个字节,内核将返回并说“我已经写入了 1 个字节”,因此您也应该准备好处理这种情况。一个典型的做法是有一个缓冲区“要写入这个fd的数据”,只要不为空,就将该fd加入写集,通过尝试写入所有来处理“可写”事件当前在缓冲区中的数据。如果之后缓冲区是空的,那很好,如果不是,只需再次等待“可写”。
The "exceptional" set is seldom used -- it is used for protocols that have out-of-band data where it is possible for the data transfer to block, while other data needs to go through. If your program cannot currently accept data from a "readable" file descriptor (for example, you are downloading, and the disk is full), you do not want to include the descriptor in the "readable" set, because you cannot handle the event and select
would immediately return if invoked again. If the receiver includes the fd in the "exceptional" set, and the sender asks its IP stack to send a packet with "urgent" data, the receiver is then woken up, and can decide to discard the unhandled data and resynchronize with the sender. The telnet
protocol uses this, for example, for Ctrl-C handling. Unless you are designing a protocol that requires such a feature, you can easily leave this out with no harm.
“异常”集很少使用——它用于具有带外数据的协议,在这种情况下,数据传输可能会阻塞,而其他数据需要通过。如果您的程序当前无法接受来自“可读”文件描述符的数据(例如,您正在下载,并且磁盘已满),则您不希望将该描述符包含在“可读”集中,因为您无法处理该事件select
如果再次调用,将立即返回。如果接收方将 fd 包含在“异常”集合中,并且发送方要求其 IP 堆栈发送带有“紧急”数据的数据包,则接收方将被唤醒,并可以决定丢弃未处理的数据并与发送方重新同步. 这telnet
协议使用它,例如,用于 Ctrl-C 处理。除非您正在设计需要此类功能的协议,否则您可以轻松地将其忽略而不会有任何伤害。
Obligatory code example:
强制性代码示例:
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <stdbool.h>
static inline int max(int lhs, int rhs) {
if(lhs > rhs)
return lhs;
else
return rhs;
}
void copy(int from, int to) {
char buffer[10];
int readp = 0;
int writep = 0;
bool eof = false;
for(;;) {
fd_set readfds, writefds;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
int ravail, wavail;
if(readp < writep) {
ravail = writep - readp - 1;
wavail = sizeof buffer - writep;
}
else {
ravail = sizeof buffer - readp;
wavail = readp - writep;
}
if(!eof && ravail)
FD_SET(from, &readfds);
if(wavail)
FD_SET(to, &writefds);
else if(eof)
break;
int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL);
if(rc == -1)
break;
if(FD_ISSET(from, &readfds))
{
ssize_t nread = read(from, &buffer[readp], ravail);
if(nread < 1)
eof = true;
readp = readp + nread;
}
if(FD_ISSET(to, &writefds))
{
ssize_t nwritten = write(to, &buffer[writep], wavail);
if(nwritten < 1)
break;
writep = writep + nwritten;
}
if(readp == sizeof buffer && writep != 0)
readp = 0;
if(writep == sizeof buffer)
writep = 0;
}
}
We attempt to read if we have buffer space available and there was no end-of-file or error on the read side, and we attempt to write if we have data in the buffer; if end-of-file is reached and the buffer is empty, then we are done.
如果我们有可用的缓冲区空间并且在读取端没有文件结束或错误,我们会尝试读取,如果缓冲区中有数据,我们会尝试写入;如果到达文件尾并且缓冲区为空,那么我们就完成了。
This code will behave clearly suboptimal (it's example code), but you should be able to see that it is acceptable for the kernel to do less than we asked for both on reads and writes, in which case we just go back and say "whenever you're ready", and that we never read or write without asking whether it will block.
这段代码的行为显然是次优的(它是示例代码),但是您应该能够看到内核在读取和写入方面做的比我们要求的要少是可以接受的,在这种情况下,我们只需回过头来说“无论何时你准备好了”,而且我们从不问它是否会阻塞就读或写。
回答by Ignacio Vazquez-Abrams
From the same man page:
从同一个手册页:
On exit, the sets are modified in place to indicate which file descriptors actually changed status.
退出时,这些集合被修改到位以指示哪些文件描述符实际更改了状态。
So use FD_ISSET()
on the sets passed to select to determine which FDs have become ready.
因此FD_ISSET()
,在传递给 select 的集合上使用以确定哪些 FD 已准备就绪。