select() 如何在 fd 变得“准备好"时发出警报? [英] how is select() alerted to an fd becoming "ready"?

查看:30
本文介绍了select() 如何在 fd 变得“准备好"时发出警报?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我不知道为什么我很难找到这个,但我正在查看一些我们使用 select() 等待文件描述符报告的 linux 代码准备好了.从选择的手册页:

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:

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 

所以,那太好了...我在某个描述符上调用 select,给它一些超时值并开始等待指示消失.文件描述符(或描述符的所有者)如何报告它已准备好"以便 select() 语句返回?

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?

推荐答案

它报告它已经准备好返回.

It reports that it's ready by returning.

select 等待通常不在程序控制范围内的事件.本质上,通过调用 select,您的程序会说我无事可做,直到……,请暂停我的进程".

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".

您指定的条件是一组事件,其中任何一个都会唤醒您.

The condition you specify is a set of events, any of which will wake you up.

例如,如果你正在下载一些东西,你的循环将不得不等待新数据的到来,如果传输卡住就会超时,或者用户中断,这正是 select确实如此.

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来查看连接当前是否接受数据.如果另一端正在拨号,它只会缓慢地确认数据,因此您的本地发送缓冲区始终已满,并且任何写入更多数据的尝试都会阻塞,直到缓冲区空间可用或失败.通过将我们要发送的文件描述符作为写入"描述符传递给 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.

总体思路是您的程序成为事件驱动,即它对来自公共消息循环的外部事件做出反应,而不是执行顺序操作.您告诉内核这是我想要为其做某事的一组事件",内核会为您提供一组已发生的事件.两个事件同时发生是相当常见的;例如,数据包中包含 TCP 确认,这可以使相同的 fd 可读(数据可用)和可写(已确认的数据已从发送缓冲区中删除),因此您应该准备好处理所有事件在再次调用 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.

更好的一点是 select 基本上给了你一个承诺,即一次调用 readwrite 不会阻塞,而不会使关于调用本身的任何保证.例如,如果有 1 个字节的缓冲区空间可用,您可以尝试写入 10 个字节,内核将返回并说我已经写入了 1 个字节",因此您也应该准备好处理这种情况.一个典型的做法是有一个缓冲区要写入这个fd的数据",只要不为空,就将该fd加入写集,通过尝试写入所有来处理可写"事件当前在缓冲区中的数据.如果之后缓冲区是空的,那很好,如果不是,就再次等待可写".

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 将立即返回.如果接收方将 fd 包含在异常"集中,并且发送方要求其 IP 堆栈发送一个带有紧急"数据的数据包,则接收方将被唤醒,并可以决定丢弃未处理的数据并与发送方重新同步.telnet 协议使用它,例如,用于 Ctrl-C 处理.除非您正在设计需要此类功能的协议,否则您可以轻松地将其忽略不计.

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.

强制性代码示例:

#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.

这篇关于select() 如何在 fd 变得“准备好"时发出警报?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆