一、概念引入

I/O多路复用(multiplexing)的本质是通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。

Linux中基于socket的通信本质也是一种I/O,使用socket()函数创建的套接字默认都是阻塞的,这意味着当sockets API的调用不能立即完成时,线程一直处于等待状态,直到操作完成获得结果或者超时出错。会引起阻塞的socket API分为以下四种:

  • 输入操作:recv()、recvfrom()。以阻塞套接字为参数调用该函数接收数据时,如果套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。
  • 输出操作:send()、sendto()。以阻塞套接字为参数调用该函数发送数据时,如果套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。
  • 接受连接:accept()。以阻塞套接字为参数调用该函数,等待接受对方的连接请求。如果此时没有连接请求,线程就会进入睡眠状态。
  • 外出连接:connect()。对于TCP连接,客户端以阻塞套接字为参数,调用该函数向服务器发起连接。该函数在收到服务器的应答前,不会返回。这意味着TCP连接总会等待至少服务器的一次往返时间。

使用阻塞模式的套接字编写网络程序比较简单,容易实现。但是在服务器端,通常要处理大量的套接字通信请求,如果线程阻塞于上述的某一个输入或输出调用时,将无法处理其他任何运算或响应其他网络请求,这么做无疑是十分低效的,当然可以采用多线程,但大量的线程占用很大的内存空间,并且线程切换会带来很大的开销。而I/O多路复用模型能处理多个connection的优点就使其能支持更多的并发连接请求

Linux支持I/O多路复用的系统调用有select、poll、epoll,这些调用都是内核级别的。但select、poll、epoll本质上都是同步I/O,先是block住等待就绪的socket,再block住将数据从内核拷贝到用户内存空间。基于select调用的I/O复用模型如下:

二、select, poll, epoll系统调用详解

select,poll,epoll之间的区别如下图:

2.1 select详解

Linux提供的select相关函数接口如下:

#include <sys/select.h>
#include <sys/time.h>int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)
FD_ZERO(int fd, fd_set* fds)   //清空集合
FD_SET(int fd, fd_set* fds)    //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)  //将给定的描述符从文件中删除
FD_CLR(int fd, fd_set* fds)    //判断指定描述符是否在集合中
  1. select函数的返回值就绪描述符的数目,超时时返回0,出错返回-1。
  2. 第一个参数max_fd指待测试的fd个数,它的值是待测试的最大文件描述符加1,文件描述符从0开始到max_fd-1都将被测试。
  3. 中间三个参数readset、writeset和exceptset指定要让内核测试读、写和异常条件的fd集合,如果不需要测试的可以设置为NULL。

整体的使用流程如下图:

2.2 poll详解

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。

Linux提供的poll函数接口如下:

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);typedef struct pollfd {int fd;                         // 需要被检测或选择的文件描述符short events;                   // 对文件描述符fd上感兴趣的事件short revents;                  // 文件描述符fd上当前实际发生的事件*/
} pollfd_t;
  1. poll()函数返回fds集合中就绪的读、写,或出错的描述符数量,返回0表示超时,返回-1表示出错;
  2. fds是一个struct pollfd类型的数组,用于存放需要检测其状态的socket描述符,并且调用poll函数之后fds数组不会被清空;
  3. nfds记录数组fds中描述符的总数量;
  4. timeout是调用poll函数阻塞的超时时间,单位毫秒;
  5. 一个pollfd结构体表示一个被监视的文件描述符,通过传递fds[]指示 poll() 监视多个文件描述符。其中,结构体的events域 是监视该文件描述符的事件掩码,由用户来设置这个域,结构体的revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。

合法的事件如下:

  • POLLIN 有数据可读
  • POLLRDNORM 有普通数据可读
  • POLLRDBAND 有优先数据可读
  • POLLPRI 有紧迫数据可读
  • POLLOUT 写数据不会导致阻塞
  • POLLWRNORM 写普通数据不会导致阻塞
  • POLLWRBAND 写优先数据不会导致阻塞
  • POLLMSGSIGPOLL 消息可用 当需要监听多个事件时,使用POLLIN | POLLRDNORM设置 events域;当poll调用之后检测某事件是否发生时,fds[i].revents & POLLIN进行判断。

2.3 epoll详解

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select和poll来说,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。优点如下:

  1. 没有最大并发连接的限制,能打开的fd上限远大于1024(1G的内存能监听约10万个端口)
  2. 采用回调的方式,效率提升。只有活跃可用的fd才会调用callback函数,也就是说 epoll 只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
  3. 内存拷贝。使用mmap()文件映射内存来加速与内核空间的消息传递,减少复制开销。

epoll对文件描述符的操作有两种模式:LT(level trigger,水平触发)和ET(edge trigger)。

水平触发:默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件。

边缘触发:当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时通知一次)。

ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

Linux中提供的epoll相关函数接口如下:

#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  1. epoll_create函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。

  2. epoll_ctl函数注册要监听的事件类型。四个参数解释如下:

    • epfd表示epoll句柄;
    • op表示fd操作类型:EPOLL_CTL_ADD(注册新的fd到epfd中),EPOLL_CTL_MOD(修改已注册的fd的监听事件),EPOLL_CTL_DEL(从epfd中删除一个fd)
    • fd是要监听的描述符;
    • event表示要监听的事件

    epoll_event结构体定义如下:

    struct epoll_event {__uint32_t events;  /* Epoll events */epoll_data_t data;  /* User data variable */
    };typedef union epoll_data {void *ptr;int fd;__uint32_t u32;__uint64_t u64;
    } epoll_data_t;
    
  3. epoll_wait函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回 -1,等待超时返回 0。

    1. epfd是epoll句柄
    2. events表示从内核得到的就绪事件集合
    3. maxevents告诉内核events的大小
    4. timeout表示等待的超时事件

三、小结

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。目前流行的高性能web服务器Nginx正式依赖于epoll提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞I/O方式可能性能更好。

原文链接:https://zhuanlan.zhihu.com/p/22834126

linux I/O--I/O多路复用--介绍(二)相关推荐

  1. Linux 下UVCamp;V4L2技术简单介绍(二)

    通过前文Linux 下UVC&V4L2技术简单介绍(一)我们了解了UVC和V4L2的简单知识. 这里是USB设备的文档描写叙述:http://www.usb.org/developers/do ...

  2. Linux 服务器(二)-linux安装方法之Centos安装介绍——Windows中制作USB启动盘 MacOS中制作USB启动盘

    Linux 服务器(二)-linux安装方法之Centos安装介绍--Windows中制作USB启动盘 & MacOS中制作USB启动盘 安装方法 Centos安装方法: 下载镜像 按需选择下 ...

  3. Linux系统下基于IO多路复用的大规模可靠UDP服务器的实现(三)

    七.可靠性UDP的优化细节 4.5章节中,我们提到了KCP本身的优化提高,由于可靠性UDP是这个方案是否优秀的关键,而各种可靠UDP协议中都有TCP算法的影子,所以下面我们再仔细的谈一下这个部分.按照 ...

  4. Linux内核网络数据包发送(二)——UDP协议层分析

    Linux内核网络数据包发送(二)--UDP协议层分析 1. 前言 2. `udp_sendmsg` 2.1 UDP corking 2.2 获取目的 IP 地址和端口 2.3 Socket 发送:b ...

  5. Linux shell脚本基础学习详细介绍(完整版)一

    Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Li ...

  6. 【Linux开发】linux设备驱动归纳总结(十二):简单的数码相框

    linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  7. Linux shell脚本基础学习详细介绍(完整版)

    Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头.注释.变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提. 1. Li ...

  8. linux中各目录及详细介绍

    linux中各目录及详细介绍 一.Linux文件系统的层次结构 在Linux或UNIX操作系统中,所有的文件和目录都被组织成一个以根节点开始的倒置的树状结构,如图: 二.目录 1.目录的定义 目录相当 ...

  9. Linux内存管理之基本概念介绍(一)

    Linux内存管理之基本概念介绍(一) Linux内存管理之物理内存管理(二) Linux内存管理之内存管理单元(MMU)(三) Linux内存管理之分配掩码(四) Linux内存管理之伙伴系统(五) ...

  10. Linux下使用WPS做office的二次开发

    Linux下使用WPS做office的二次开发 序 上个版本WPS在Linux上就已经支持二次开发了,可以直接去看官网相关的介绍.https://open.wps.cn/ 我们选择WPS的客户端进行二 ...

最新文章

  1. [Spring MVC] - Spring MVC环境搭建
  2. JavaScript学习笔记(七)——函数的定义与调用
  3. Linux的IPC机制(三):Binder
  4. 第七章 Qt对象模型与容器类
  5. for循环的使用步骤 1104
  6. 使用 IntraWeb (41) - 数据控件速查
  7. python将字符串写入txt文件_python将字符串以utf-8格式保存在txt文件中的方法
  8. css 鼠标呈现手指型
  9. 利用JSP编程技术实现一个简单的购物车程序
  10. 基金指数温度怎么算_温度换算(指数基金温度计算器)
  11. python中双引号的作用_Python中单引号和双引号的作用
  12. 朱晔的互联网架构实践心得S2E3:品味Kubernetes的设计理念
  13. 删除 linux 回收站内容,Linux删除文件实现回收站功能
  14. JavaSE_语法基础
  15. java设置excel密码
  16. 计算机二级考试是考什么?
  17. 牧牛区块链培训,区块链对社会生产的五大好处
  18. 刚完成一个二手书信息发布网站 www.alluwant.cn
  19. c语言中加减乘除英文单词,求一个计算加减乘除的C语言程序
  20. 简单的wchar_t 和 char 转换类, 且包含与UTF8的转换

热门文章

  1. python可以自学吗-没学过编程可以自学python吗
  2. python3.7安装numpy模块-CENTOS7 Python3.7安装numpy
  3. python和php-PHP和Python如何选择?或许可以考虑这三个问题
  4. python在金融工程中的用途-金融工程现在用python多吗?
  5. python人脸识别毕业设计-用python3.6在电脑上实现用摄像头来人脸识别源程序
  6. python3读取excel数据-python3 读取Excel表格中的数据
  7. 在Linux上安装其他版本的cmake 或 升级cmake
  8. 2_初学者快速掌握主流深度学习框架Tensorflow、Keras、Pytorch学习代码(20181211)
  9. html编辑器渗透,渗透笔记40、web编辑器漏洞手册.pdf
  10. java 覆盖和隐藏_Java覆盖和隐藏2