I/O复用

I/O复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常网络程序在下列情况下需要使用I/O复用技术。
1、客户端程序要同时处理多个socket。
2、客户端程序要同时处理用户输入和网络连接。
3、TCP服务器要同时处理监听socket和连接socket。这是I/O复用使用最多的场合。
4、服务器要同时处理TCP请求和UDP请求。
5、服务器要同时监听多个端口,或者处理多种服务。
        需要指出的是,I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个进程描述符,这使得服务器看起来是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。
Linux下实现I/O复用的系统调用主要有select、poll、epoll。
一、select系统调用
       
int select(int nfd,fd_set *readfd,fd_set* writefd,fd_set* except,struct timeout* timeout);
        select系统调用的主要用途是:在指定一段时间内,轮询监听用户感兴趣的文件描述符,即用户添加记录到fd_set中的文件描述符,当监听到任何文件描述符有可读、可写和异常事件是就返回。
        轮询:一遍一遍的循环监听从0到我们记录的这些文件描述符中的最大值。
        fd_set:是一个结构体中包含一个整型数组,这个整型数组的每个元素的每个位代表一个文件描述符,所以select能同时初六的文件描述符的最大数量由这个数组的总位数决定,这个最大值是<sys/select.h>头文件中定义的一个常量FD_SIZE。

参数::
        ①参数nfd,指定了被监听的文件描述符的总数,通常是readfd,writefd,exceptfd这三个描述符集中的最大的编号加一。
        ②readfd,writefd,exceptfd分别指向可读、可写、异常事件对应的文件描述符集合,当一开始使用select函数时,我们会在这三个文件描述符集合中记录我们感兴趣的文件描述符,轮询等待这些描述符有事件产生,select调用返回时,原先我们记录的文件描述符就消失,然后内核会把有可读、可写或异常事件发生的文件描述符在这三个描述符集。
        ③timeout参数:用来设置select函数的超时时间,表示select愿意等待多长时间。当timeout==NULL时,select会无限等待,直到监听的文件描述符有事件发生或捕捉到一个信号。

select系统调用的返回值::
① 返回-1,表示出错,比如在select超时时间内捕捉到一个信号。
②返回0,表示超时时间到,但没有一个描述符有事件发生,此时readfd,writefd,exceptfd三个描述符集会被内核置0.
③返回值大于零,表示有事件发生的文件描述符总数,如果一个文件描述符同时发生了读写事件,则会对其计数两次。

select的缺点::
①每次调用,都需要把fd集合从用户态拷贝到内核态,这个开销在fd数量很多时会很大。
②同时每次,都需要在内核遍历传递进来的所有fd,这个开销在fd数量跟多时会很大。
③select支持的文件描述符太少了,默认是1024.

二、poll系统调用
int poll(struct fdset[],nfds_t nfds,int timeout);

参数::
        ①fdset参数是pollfd的结构体类型的数组。
struct pollfd
{
    int fd;//文件描述符,如果小于0,此fd被忽略
    short events;//注册的事件,可以是一个事件,也可是多个事件按位或
    short revents;//实际发生的事件,由内核来赋值,需要与某种类型的事件按位与来确定某种事件是否发生
};
这个结构体指定了我们感兴趣的文件描述符上发生的可读、可写、异常事件。与select不同,poll不是为每个状态构造一个描述符集合,而是用pollfd结构体来指定用户感兴趣的文件描述符及其事件。
        ②nfds参数指定了pollfd结构体数组fdset的大小,nfds_t是一个无符号长整型。
        ③timeout参数指定poll的超时时间,单位是毫秒。
            timeout==-1, 永久等待,直到指定的描述符有事件发生,或捕捉到信号时返回。
            timeout==0,不等待,测试完所有描述符立即返回。
            timeout>0时,等待指定毫秒,在超时时间内,当指定的描述符有事件发生,或捕捉到信号时返回。
poll返回值与select相同。

 poll的实现的select非常相似,只是描述fd集合的方式不同,poll使用的pollfd结构体,不是select的fd_set。

三、epoll系统调用
       不同于select和poll,epoll使用三个函数来完成任务,并且把用户关心的文件描述符上的事件放在内核里的一个事件表中,成为内核时间表,同时epoll需要一个文件描述符来标识这个内核事件表,这个内核事件表用函数创建。
int epoll_create(int size);
        size参数表示这个内核事件表的大小,该函数返回的文件描述符作为其他两个epoll系统调用的第一个参数,用来指定要访问的内核事件表。
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
        epoll,内核事件表。
        fd参数是要操作的文件描述符,
        op参数则指定操作类型。操作类型有如下3种:
     (1)EPOLL_CTL_ADD,往事件表中注册fd上的事件。会把所有的fd拷贝到内核,整个过程拷贝一次。 
     (2)EPOLL_CTL_MOD,修改fd上的注册事件。
     (3)EPOLL_CTL_DEL,删除fd上的注册事件。
        event是一个epoll_event结构体指针,如下
struct epoll_event
{
    _uint32_t events;//epoll事件
    epoll_data_t data;//用户数据
};
            events表示事件类型,可以是一个事件类型,也可以是多个事件类型的按位或。
            data成员只用于存储用户数据,其类型为epoll_dara_t,是一个联合体。
typedef union epoll_data
{
    void* ptr;//用来指定与fd相关的用户数据
    int fd;//指定注册事件的文件描述符
    unint32_t u32;
    unint64_t u64;
};

int epoll_wait(int epfd,struct epoll_event events[],int maxevents,int timeout);

events 参数是一个数组,用来记录就绪事件,epoll_wait函数将所有就绪事件从内核事件表中复制到这个数组中。
maxevent参数指定最多监听事件个数,必须大于零。
timeout表示超时时间,与poll中相同。


        epoll是linux内核为处理大批量文件描述符而做了改进的poll,是linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,另一点原因就是,获取时间的时候,它无需遍历整个被侦听的描述符集,只要遍历那些内核IO事件异步唤醒而加入就绪队列的描述符集合就行了。
        优点::①支持一个进程打开大数目的socket描述符。
                      ②IO效率不随fd数目增加而线性下降。
                      ③使用mmap加速内核与用户空间的消息队列。

总结:
① select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替,而epoll其实也需要epoll_wait不断轮询就绪链表,也可能多次唤醒和睡眠交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll“醒着”的时候要遍历整个fd集合,epoll只要判断就绪链表是否为空就行了,这就节省了CPU时间。这就是回调机制带来的性能提升。
②select,poll每次调用都要把fd集合从用户态往内核拷贝一次,并且要把current往设备等待队列中接一次,而epoll只要拷贝一次,并且把current往等待队列上挂也只拷贝一次,这也能节省不少的开销。

LT和ET模式::
LT:是缺省的工作方式,并且同时支持block和noblock socket,在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪队列的fd进行IO操作,如果你不做任何操作,内核还是会继续通知你的。所以这种模式出错误的可能要小一点。
ET:是高速工作模式,只支持noblock socket,在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不再为那个文件描述符发送更多的就绪通知。直到你做了某些操作导致那个文件描述符不再为就绪状态了,但是请注意,如果一直不对这个fd做IO操作,内核就不会发送更多的通知。不过在TCP协议中ET模式的加速效用仍需要更多的benchmark确认。ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率比LT模式高。epoll工作在ET模式的时候必须使用非阻塞套接口,以避免于一个文件句柄的阻塞读/写操作把处理多个文件描述符的任务饿死。

区别:
  LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你,而ET则只是在事件发生之时通知你。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值