IO多路复用

多路复用

关于什么是I/O多路复用,在知乎上有个很好的回答,能够参考罗志宇前辈的回答
记录下本身的理解:忘记这个坑爹的中文翻译。记住I/O multiplexing
              图片描述linux

I/O multiplexing 这里面的 multiplexing 指的实际上是在单个线程经过记录跟踪每个Sock(I/O流)的状态来同时管理多个I/O流.
I/O多路复用这一技术。简单来讲,就是一个线程追踪多条io流(读,写,异常),但不使用轮询,而是由设备自己告知程序哪条流可用了,这样一来就解放了cpu,也充分利用io资源,下文主要讲解如何实现这一技术,linux下这一技术有三个实现,select,poll,epoll。数组

阻塞I/O模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下,咱们能够经过循环把集合中的流从头至尾问一遍,这样就能够处理多个流了,但这样的作法显然很差,由于若是全部流都没有数据,那么只会白白浪费CPU.
为了不CPU空转,能够引进了一个代理(一开始有一位叫作select的代理,后来又有一位叫作poll的代理,不过二者的本质是同样的)。这个代理比较厉害,能够同时观察许多流的I/O事件,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中醒来,因而咱们的程序就会轮询一遍全部的流安全

fcntl I/O多路复用

此种方法与select类似,不做介绍函数

I/O Multiplexing --> select

#include <sys/select.h>  
/**
 *select将更新这个集合,把其中不可读的套节字去掉只保留符合条件的套节字在这个集合里面    
 */
int select(int nfds, fd_set *readfds, fd_set *writefds,  
           fd_set *exceptfds, struct timeval *timeout);

参数readfds、writefds、exceptfds都是指向文件描述符的指针,数据类型为fd_set。而readfds是用来检测输入的,writefds是用来检测输出的,exceptfds使用检测是否异常的。有关fd_set一般有四个宏供咱们操做:FD_ZERO、FD_SET、FD_CLR、FD_ISSET。spa

  • FD_ZERO(fd_set *set); //fd_set所指向的集合清空
  • FD_SET(int fd,fd_set *set);//文件描述符fd添加到fd_set所指向的集合中
  • FD_CLR(int fd,fd_set *set);//文件描述符fd从fd_set所指向的集合中移除
  • int FD_ISSET(int fd,fd_set *set);//文件描述符fd是fd_set所指向的集合中的成员,则FD_ISSET返回true,不然返回false

文件描述符集合有一个最大容量限制,由常量FD_SETSIZE来决定,在Linux上,该常量值为1024。
参数timeout为超时时间。线程

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>

int main(void)
{
        int bytes_read, ready;
        char buffer[128];
        fd_set readfds;
        struct timeval timeout;

        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);

        timeout.tv_sec = 10;
        timeout.tv_usec = 0;

        ready = select(STDIN_FILENO+1, &readfds, NULL, NULL, &timeout);

        if (ready) {
                bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
                if (buffer[bytes_read - 1] == '\n')
                        buffer[bytes_read - 1] = '\0';
                printf("%s\n", buffer);
        } else {
                printf("No data to read\n");
        }

        return 0;
}

I/O Multiplexing --> poll

因为I/O Multiplexing->select存在如下问题翻译

  • select 会修改传入的参数数组,这个对于一个须要调用不少次的函数,是很是不友好的。
  • select 若是任何一个sock(I/O stream)出现了数据,select 仅仅会返回,可是并不会告诉你是那个sock上有数据,因而你只能本身一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就很有海天盛筵的豪气了。
  • select 只能监视1024个连接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
  • select 不是线程安全的,若是你把一个sock加入到select, 而后忽然另一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,若是你丧心病狂的居然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个但是写在文档中的哦.

所以14年后(1997)出现了POLL,修复select的如下问题设计

  • poll 去掉了1024个连接的限制
  • poll 从设计上来讲,再也不修改传入数组
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);  

struct pollfd{  
    int fd;         //文件描述符  
    short events;   //等待的事件  
    short revents;  //实际发生的事件  
};
  • 第一个参数:

 每一个pollfd结构体指定了一个被监视的文件描述符。第一个参数是一个数组,即poll函数能够监视多个文件描述符。每一个结构体的events是监视该文件描述符的事件掩码,由用户来设置。revents是文件描述符的操做结果事件,内核在调用返回时设置。events中请求的任何事件均可能在revents中返回
图片描述3d

  • 第二个参数nfds:
    要监视的描述符的数目。
  • 第三个参数timeout
    指定等待的毫秒数,不管 I/O 是否准备好,poll() 都会返回.

图片描述


I/O Multiplexing -->epoll

问题:使用select,咱们有O(n)的无差异轮询复杂度,随着处理的流越多,无差异轮询时间就越长
此时epoll产生,epoll能够理解为event poll,不一样于(select/poll)无差异轮询,epoll之会把哪一个流发生了怎样的I/O事件通知咱们。此时咱们对这些流的操做都是有意义的。代理

相关文章
相关标签/搜索