网络编程之多路复用---mutilio
IO多路复用技术
目录
- 定义
- 作用
- 并发与并行
- IO模型
- 阻塞IO
- 非阻塞IO
- 信号驱动IO
- 并行模型
- IO多路复用
- select函数详解
- select/poll/epoll区别
- 作业要求
1. 定义
单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力
2. 作用
应用程序通常需要处理来自多条事件流中的事件,比如我现在用的电脑,需要同时处理键盘鼠标的输入、中断信号等等事件,再比如web服务器如nginx,需要同时处理来来自N个客户端的事件。
3. 并发与并行
逻辑控制流在时间上的重叠叫做 并发
并行
而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。
使用并发处理的成本:
- 线程/进程创建成本
- CPU切换不同线程/进程成本 Context Switch 上下文切换 页表,寄存器,缓存
- 多线程的资源竞争
有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是IO多路复用。
因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。
4. IO模型
- 阻塞IO
- 非阻塞IO EAGAIN 忙等待 errno
- 信号驱动IO SIGIO 用的相对少(了解)
- 并行模型 进程,线程
- IO多路复用 select、poll、epoll
1.阻塞IO ===》最常用 默认设置
2.非阻塞IO ===》在阻塞IO的基础上调整其为不再阻塞等待。
在程序执行阶段调整文件的执行方式为非阻塞:
===》fcntl() ===>动态调整文件的阻塞属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
功能:修改指定文件的属性信息。
参数:fd 要调整的文件描述符
cmd 要调整的文件属性宏名称
… 可变长的属性值参数。
返回值:成功 不一定,看cmd
失败 -1;
eg:修改文件的非阻塞属性:
int flag ;
flag = fcntl(fd,F_GETFL,0); ///获取fd文件的默认属性到flag变量中。
flag = flag | O_NONBLOCK; ///将变量的值调整并添加非阻塞属性
fcntl(fd,F_SETFL,flag); ///将新属性flag设置到fd对应的文件生效。
以上代码执行后的阻塞IO将变成非阻塞方式。
3.信号驱动io
文件描述符需要追加 O_ASYNC 标志。
设备有io事件可以执行时,内核发送SIGIO信号。
1.追加标志
int flag ;
flag = fcntl(fd,F_GETFL,0);
fcntl(fd,F_SETFL,flag | O_ASYNC);
2.设置信号接收者
fcntl(fd,F_SETOWN,getpid());//常用设置
3.对信号进行捕获
signal(SIGIO,myhandle);//
4.并行
1.进程
2.线程
5. IO多路复用 ===》并发服务器 ===》TCP协议
3.select循环服务器 ===> 用select函数来动态检测有数据流动的文件描述符
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout);
功能:完成指定描述符集合中有效描述符的动态检测。
该函数具有阻塞等待功能,在函数执行完毕后
目标测试集合中将只保留最后有数据的描述符。
参数:nfds 描述符的上限值,一般是链接后描述符的最大值+1;
readfds 只读描述符集
writefds 只写描述符集
exceptfds 异常描述符集
以上三个参数都是 fd_set * 的描述符集合类型
timeout 检测超时 如果是NULL表示一直检测不超时 。
返回值:超时 0
失败 -1
成功 >0
为了配合select函数执行,有如下宏函数:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select poll epoll的区别
- select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select的调用一般要注意几点:
① readfds等是指针结果参数,会被函数修改…
② 要注意计算nfds…
③ timeout如果为NULL表示阻塞等…
④ select返回-1表示错误…
⑤ Linux的实现中select返回时会将timeout修改为剩余时间…
select的缺点在于:
① 由于描述符集合set的限制…
② 返回的可读集合是个fdset类型…
③ nfds的存在就是为了解决select的效率问题…
- epoll
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);
epoll 解决了select和poll的几个性能上的缺陷…
epoll显著提高性能的前提是…
epoll的另外区别是…
作业
使用以上任意一个并发服务器模型,完成如下功能:
- 可以向服务器发送一个消息,并在服务器端打印输出该消息的来源ip+port
- 将所有的连接终端的个数统计出来
本小节知识总结:io多路复用
IO模型
1.阻塞io 闲等待 让出CPU
2.非阻塞 IO EAGAIN 忙等待errno,不会让出CPU 反复检测io的状态 ,使用fcntl O_NONBLG
3.并行io,进程或者线程,相对多路io而言,比较消耗系统资源
4.多路io,可以处理,单线程或者单进程中,多个阻塞io,select epoll;
多路io复用:由操作系统对io事件进行检测的机制
select:
1.创建集合
2.加入关心的文件描述符;
3.调用select 等待io事件(读或写,大部分情况是读)的到来
4.找出对应的fd’,进行读或者写的操作。
5.清除标志位(读写标志位)
epoll
1.创建集合(epoll_wait)
2.加入关心的文件描述符epoll-ctl
3.调用epoll_wait等待io事件(读或者写,大部分情况是读)的到来,并把就绪fd存入就绪集合(rev示例中的rev)
4.在rev集合中找出对应的fd,进行读写操作
对照优缺点
select:最多检测1024个fd;epoll 一般对于服务器而言<5000 fd
select:在检测文件fd式是轮询的方式,一个个文件包描述符询问检测;epoll检测方式:主动上报,通知了才会去:设备通知epoll,epoll通知a.out;
select:在找对应的fd时,需要在原始集合里面(就绪和未就绪在一起)找;epoll:rev集合里(只有就绪的fd)中找出对应的fd;