9 I/O复用
I/O复用使得程序能够同时监听多个文件描述符,适用于以下情况:
- 客户端同时处理多个socket,比如非阻塞connect
- 客户端同时处理用户输入和网络连接,比如聊天室程序
- TCP服务器同时处理监听socket和连接socket,这是IO复用最多的用法
- 服务器要同时处理TCP请求和UDP请求,比如回射服务器
- 服务器要同时监听多个端口,或者处理多种事物,比如xinetd服务器
linux下实现IO复用的系统调用主要有select poll epoll
9.1 select系统调用
#include <sys/socket.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
nfds参数指定被监听的文件描述符总数,痛陈设置为selcet监听的所有文件描述符的最大值加1
readfds writefds exceptfds分别指向可读,可写和异常等事件对应的文件描述符集合,调用select后,通过这三个参数传入自己感兴趣的文件描述符,select返回,内核将修改它们来通知应用程序哪些文件描述符就绪
fd_set通过一些列宏来访问
#include <sys/socket.h>
FD_ZERO(fd_set *fdset); //清除fdset所有位
FD_SET(int fd, fd_set *fdset); //设置fdset的位fd *
FD_CLR(int fd, fd_set *fdset); //清除fdset的位fd *
int FD_ISSET(int fd, fd_set *fdset); //测试fdset的位fd是否被设置
timeout用来设置select函数的超时时间
struct timeval {
long tv_sec;
long tv_usec;
};
文件描述符就绪条件:
socket可读:
- socket内核接收缓冲区中的字节数目大于或等于其低水平标记SO_RCVLOWAT。
- socket通信的对方关闭连接。此时对该socket的读操作将返回0
- 监听socket上有新的连接请求。
- socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
socket可写:
- socket内核发送缓冲区中的可用字节数大于或等于其低水平标记SO_SNDLOWAT。
- socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。
- socket使用非阻塞connect连接成功或者失败(超时)之后。
- socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。
网络程序中,select能处理的异常情况只有一种:socket上接收到带外数据。
9.2 poll系统调用
poll和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者
#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
struct pollfd{
int fd;
short events;//注册的事件
short revents;//实际发生的事件,由内核填充
};
ndfs参数指定被监听事件集合fds的大小
poll事件类型:
主要记住读 POLLIN 写 POLLOUT 关闭连接 PULLREHUP
通常,应用程序需要根据recv调用的返回值来区分socket上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,自linux内核2.6.17开始,GNU为poll系统调用增加了一个POLLRDHUP事件,它在socket上接收到对方关闭连接的请求之后触发。这为我们区分上述两种情况提供了一种更简单的方式。但是用POLLRDHUP事件时,我们需要在代码最开始处定义_GNU_SOURCE。
9.3 epoll系列系统调用
epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。
epoll三个函数epoll_create,epoll_ctl,epoll_wait
文件描述符使用epoll_create函数来创建:
#include <sys/epoll.h>
int epoll_create(int size);
size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大,返回值是文件描述符
epoll的时间注册函数epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op参数指定操作类型:
EPOLL_CTL_ADD //向事件表中注册fd上的事件
EPOLL_CTL_MOD //修改fd上注册事件
EPOLL_CTL_DEL //删除fd上注册事件
event参数指定事件
struct epoll_event{
__uint32_t events;//epoll事件
epoll_data_t data;//用户数据
};
events成员描述事件类型:
EPOLLIN 和 EPOLLOUT表示读写,EPOLLET和EPOLLONESHOT对epoll的高效运作非常关键
EPOLLIN //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT //表示对应的文件描述符可以写;
EPOLLPRI //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR //表示对应的文件描述符发生错误;
EPOLLHUP //表示对应的文件描述符被挂断;
EPOLLET //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(LevelTriggered)来说的。
EPOLLONESHOT //只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
data成员用于存储用户数据,其类型epoll_data_t的定义如下:
typedef union epoll_data{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
成员中使用最多的是fd, 它指定事件所从属的目标文件描述符
epoll_wait收集在epoll监控的事件中已经发生的事件
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
返回值:成功返回就绪的文件描述符个数,失败返回-1
timeout含义与poll相同,单位毫秒。
maxevents参数指定最多监听多少个事件,必须大于0
总结:epoll遍历就绪事件,selelct poll遍历所有事件
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大的提高了应用程序索引就绪文件描述符的效率
/*索引poll返回就绪文件描述符*/
int ret = poll(fds,MAX_EVENT_NUMBER,-);
for(int i = ; i < MAX_EVENT_NUMBER; i++)
{
if( fds[i].revents & POLLIN) //判断文件描述符是否就绪(可读)
{
int sockfd = fds[i].fd;
}
} /*索引epoll返回就绪文件描述符*/
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -);
for(int i = ; i<ret; i++)
{
int sockfd = event[i].data.fd; //肯定就绪,直接处理
}
LT和ET模式
epoll的两种工作模式LT和ET,默认情况是LT模式,ET是高效模式
LT水平触发:相当于一个高效的poll,LT模式下,当epoll_wait检测到有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样应用程序下次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理
ET边缘触发:ET模式下,epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。可见,ET模式降低了同一个epoll事件被重复触发的次数,因此效率比LT高。
使用ET模式每个文件描述符都应该是非阻塞的,如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态
nginx默认用ET模式
EPOLLONESHOT事件 (简单来说就是事件设置了这个属性只能被触发一次)
即使我们使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。如一个线程(或进程,下同)在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另一个线程被唤醒来读取这些新的数据。于是出现了两个线程同时操作一个socket的局面。这当然不是我们期望的。我们期望的是一个socket连接在任一时刻都只被一个线程处理。这一点可以使用epoll的EPOLLONESHOT事件实现。
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。
9.4 三组I/O复用函数的比较
自己总结:书中这一块写的很好
事件集合方面
这3组函数都通过某种结构体变量来告诉内核监听哪些文件描述符上的哪些事件,并使用该结构体类型的参数获取内核处理结果
- select参数fd_set没有将文件描述符和事件绑定,select提供了三种事件:可读、可写、异常。这样select不能处理更多类型的事件,而且由于内核对fd_set集合在线修改,每次使用select前需要重置(用FE_SET函数)fd_set集合
- poll把文件描述符和事件都定义于pollfd结构体中,内核修改的是pollfd结构体中的revents成员,而events成员保持不变,下次调用poll无须想select一样对文件描述符重置
- epoll 它在内核中维护一个事件表,采用独立的系统调用epoll_ctl来控制添加删除修改事件,这样epoll_wait系统调用events参数仅用来返回就绪的事件
监听的数目
select有最大限制,一般是1024,poll和epoll是系统允许打开的最大文件描述符的数目:65535
实现原理
select和poll是轮询方式,扫描所有注册的文件描述符集合,时间复杂度O(n);epoll_wait采用回调的方式,内核检测到就绪事件就出发回调函数,时间复杂度O(1)
9 I/O复用的更多相关文章
- 从I/O复用谈epoll为什么高效
上一篇文章中,谈了一些网络编程的基本概念.在现实使用中,用的最多的就是I/O复用了,无非就是select,poll,epoll 很多人提到网络就说epoll,认为epoll效率是最高的.单纯的这么认为 ...
- if __name__== "__main__" 的意思(作用)python代码复用
if __name__== "__main__" 的意思(作用)python代码复用 转自:大步's Blog http://www.dabu.info/if-__-name__ ...
- Atitit.java c#.net php项目中的view复用(jsp,aspx,php的复用)
Atitit.java c#.net php项目中的view复用(jsp,aspx,php的复用) 1.1. Keyword1 1.2. 前言1 2. Java项目使用.Net的aspx页面view1 ...
- SAP CRM 复用视图
在设计任何视图或组件的时候,我们需要以可复用的方式来设计它.UI组件设计的主要目标即可复用. 例如:几乎每个事务都要处理合作伙伴(客户).如果我们想要在Web UI显示那些合作伙伴,需要设计一个视图. ...
- Andriod 自定义控件之创建可以复用的组合控件
前面已学习了一种自定义控件的实现,是Andriod 自定义控件之音频条,还没学习的同学可以学习下,学习了的同学也要去温习下,一定要自己完全的掌握了,再继续学习,贪多嚼不烂可不是好的学习方法,我们争取学 ...
- Android 5.X新特性之RecyclerView基本解析及无限复用
说到RecyclerView,相信大家都不陌生,它是我们经典级ListView的升级版,升级后的RecyclerView展现了极大的灵活性.同时内部直接封装了ViewHolder,不用我们自己定义Vi ...
- UITableView cell复用出错问题 页面滑动卡顿问题 & 各杂七杂八问题
UITableView 的cell 复用机制节省了内存,但是有时对于多变的自定义cell,重用时会出现界面出错(例如复用出错,出现cell混乱重影).滑动卡顿等问题,这里只简单敲下几点复用出错时的解决 ...
- Linux网络编程-IO复用技术
IO复用是Linux中的IO模型之一,IO复用就是进程预先告诉内核需要监视的IO条件,使得内核一旦发现进程指定的一个或多个IO条件就绪,就通过进程进程处理,从而不会在单个IO上阻塞了.Linux中,提 ...
- 第3月第27天 uitableviewcell复用
1. 有需求cell一行放两个子view,也可以放3个.子view控件都是一样,只有大小区分.需要复用吗? 复用实现:创建时创建3个,拿到数据时是两个就设置两个的frame,是3个就设置3个的fram ...
- 前后端分离中,Gulp实现头尾等公共页面的复用
前言 通常我们所做的一些页面,我们可以从设计图里面看出有一些地方是相同的.例如:头部,底部,侧边栏等等.如果前后端分离时,制作静态页面的同学,对于这些重复的部分只能够通过复制粘贴到新的页面来,如果页面 ...
随机推荐
- 简单Spinner
mainActivity package com.zzw.spinner; import android.app.Activity; import android.content.Context; i ...
- 在 Windows 7 環境安裝 Python 2.6.6
目前 Python 的最穩定的版本是 2.7.3 及 3.2.3,因為 2.x 與 3.x 語法並不是完全相容,在各版本之間也有些差異,所以建議還是各自安裝需要的版本… 艾小克工作環境是使用 2.6 ...
- Python之Flask Web开发
下载python包管理工具Pip: 访问网址:https://pip.pypa.io/en/stable/installing/ 下载文件get-pip.py到本地计算机 定位到get-pip. ...
- SQL Server 一些关键字详解(一)
1.CROSS APPLY 和OUTER APPLY MSDN解释如下(个人理解不是很清晰): 使用 APPLY 运算符可以为实现查询操作的外部表表达式返回的每个行调用表值函数.表值函数作为右输入,外 ...
- EXCLE图形插入实例
根据所给名称找到相应图片,并进行导入. 代码如下: Sub Import_picture() For Each shap In Sheet1.Shapes '在sheet1中的图片对象进行循环 If ...
- jdk 1.5
1995.5.23 java语言诞生 sun公司推出java语言的同时,也推出了一系列的开发工具,比如JDK(java development Kit)JVMjava API 时间 版本 API 用途 ...
- 史上最佳 Mac+PhpStorm+XAMPP+Xdebug 集成开发和断点调试环境的配置
在上一篇 PHP 系列的文章<PHP 集成开发环境比较>中,我根据自己的亲身体验,非常简略的介绍和对比了几款常用的集成开发环境,就我个人而言,比较推崇 Zend Studio 和 PhpS ...
- H5 input type="search" 不显示搜索 解决方法
在IOS(ipad iPhone等)系统的浏览器里打开H5页面.如下写法: <input type="search" name="search” id=" ...
- Qt入门1---widget、mainwindow和Dialog区别
摘要: 看了一个月的Qt,居然没有理清Qt中 ------------------------------------ 1.QMainWindow A main window provides a f ...
- iOS 进阶 第二十一天(0531)
0531 - Autolayout 不仅可以做屏幕适配还可以做系统适配 uidynamic 做物理动画.能做的效果如下图: Autolayout Autolayout 是一种“自动布局”技术,专门用来 ...