Linux网络编程(高级IO)-典型IO,多路IO复用

IO:输入输出   过程:等待IO就绪,进行数据拷贝

  • 四种典型IO方式:

(1)阻塞IO:发起IO调用,若IO未就绪(IO条件不具备)则一直等待

(2)非阻塞IO:发起IO调用,若IO未就绪(IO条件不具备)则立即报错返回,隔一段时间重新发起IO调用

(3)信号驱动IO:自定义IO信号处理,等待IO就绪,收到信号打断当前操作进行IO

(4)异步IO:自定义IO信号处理,发起异步IO调用,调用立即返回,但让系统完成IO,完成后通过信号通知进程

阻塞:为了完成一个功能,发起调用,若完成功能条件不具备,则一直等待

非阻塞:发起一个调用,若完成功能不具备,则立即报错返回

阻塞与非阻塞:通常用于描述某个接口发起调用后是否能够立即返回

同步:一个功能完成后,才能进行下一个操作,若不能立即完成则一直等待

异步:发起一个调用,让别人完成具体功能,不用等待功能完成后,可先进行下一个操作,功能完成顺序并非按照发起顺序

同步与异步:外部体现就是功能是否靠自己来完成,同步流程清晰简单但效率较低,异步流程较为复杂但资源利用率更高(利用率高意味着:效率高占据资源也多)

异步阻塞:发起一个调用,让系统完成任务,进程一直等待系统完成

异步非阻塞:发起一个调用,让系统完成任务,进程不等待,继续进程自己的事

  • 多路转接IO:IO多路复用

作用:针对大量描述符进行IO就绪事件监控,让进程仅仅针对已经就绪的IO事件描述符进行IO操作,避免了进程对未就绪的描述符进行操作所造成的的性能损失

实现:select,poll,epoll

IO就绪事件:可读,可写,异常

  • select模型:针对大量描述符进行IO就绪事件监控

1,定义指定IO事件的描述符集合,将需要监控指定事件的描述符添加到对应集合中

2,发起调用,将需要监控的事件描述符集合拷贝到内核,进行事件监控

若监控超时都没有描述符就绪则返回

若有描述符就绪了指定监控的事件则返回

在监控调用返回前,都会将描述符集合中没有就绪事件的描述符移除

在监控调用返回后,集合中只保留就绪的描述符

3,判断哪个描述符还在哪个集合中,就知道哪个描述符就绪了什么事件,进而进行对应的IO操作

接口:

  1. 定义集合:fd_set  rfds,wfds,efds;
  2. 清空集合: void FD_ZERO(fd_set *set)
  3. 将描述符添加到集合中:void FD_SET(int fd,fd_set *set)
  4. 发起监控调用 int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)

         nfds:所有集合中最大的描述符的值+1

         rfds,wfds,efds:可读可写异常  不监控则置空

         timeout:监控超时等待时间 阻塞置NULL,非阻塞置0

         返回值:返回实际就绪的描述符个数  出错返回-1,超时返回0

     5.调用返回后,判断哪个描述符在集合中确定哪个描述符定义什么事件 int FD_ISSET(int fd,fd_set *set)

    6.从指定描述符集合中移除指定描述符 void FD_CLR(int fd,fd_set *set)

  • 代码描述:

针对标准输入进行可读数据监控  当标准输入有数据可读,再对其进行read操作

将select机制封装 创建调用接口

修改客户端采用select监听机制,复用多路IO

 

 

即使不采用多线程技术, 创建多个客户端也可同时通信

 

综上:

应用于tcp服务端,针对大量套接字描述符进行监控,让程序能够仅仅针对就绪的描述符操作,提高处理效率  而udp服务端大多对单个套接字进行操作,意义不大,但可以通过多路复用IO设置超时等待时间

select特性总结:

优点:跨平台移植好

缺点:

1,select所能监控描述符个数上限取决于宏 _FD_SETSIZE

2,select每次监控都需要重新向集合添加描述符

3,select监控原理就是在内核中进行轮询遍历,性能随描述符增多而下降

        (1)将集合中描述符遍历一遍看是否有就绪的

        (2)有就直接移除未就绪后返回,没有则挂起等待

        (3)有描述符就绪/超时后被唤醒  重新遍历一遍移除未就绪后返回

4,select返回就绪集合,需要用户判断哪个描述符在集合中确定哪个描述符定义什么事件

  • poll:

接口:

int poll(struct pollfd *fds,nfds_t nfds,int timeout)
//fds:描述符事件结构体数组
//nfds:数组中有效节点
//返回值:返回0超时  小于0出错,大于0返回就绪个数

struct pollfd{
    int fd;//需要监控的描述符
    short events;//fd描述符要监控的事件 POLLIN-读  POLLOUT-写
    short revents;//监控调用返回后,用于记录实际就绪事件
}

操作流程:

1,定义事件结构体数组,为每个需要监控的描述符定义事件结构体

2,发起监控调用,将数组有效节点拷贝到内核进行监控,超时/就绪则调用返回,返回前将描述符就绪事件记录到对应节点的revents成员中

3,调用返回后,遍历描述符事件数组,通过每个节点的revents成员确定该节点描述符是否就绪某一个事件

优缺点:

优点:

1,poll能够监控的描述符数量没有上限

2,代码流程较select简单

缺点:

1,跨平台移植性较差

2,监控原理为遍历轮询,性能随描述符增多而下降

3,监控返回后,需要遍历事件结构体数组确定描述符进行就绪

  • epoll:目前最佳的IO多路转接方式

接口:

int epoll_create(int size)//在内核中创建epoll句柄
//size:监控数量上限,linux2.6版本之后大于0即可
//返回值:成功返回描述符,失败-1



int epoll_ctl(int epfd,int op,int fd,struct epoll_event *ev)
//epfd:epoll_create返回的epoll描述符
//op:EPOLL_CTL_ADD/EPOLL_CTL_DEL/EPOLL_CTL_MOD
//fd:针对op类型所操作的监控描述符
//ev:针对fd描述符定义的事件结构体
//返回值:成功返回0,失败返回-1

struct epoll_event{
    uint32_t events;//要监控事件(EPOLLIN可读/EPOLLOUT可写/EPOLLET触发方式)---监控返回后实际就绪事件
    epoll_data_t{void* ptr,int fd;}data;//联合体,通常用于保存事件节点对应要监控的描述符
};



int epoll_wait(int epfd,struct epoll_event *evs,int maxe,int to)
//epfd:epoll_create返回的epoll描述符
//evs:epoll_event数组首地址,用于保存返回的实际就绪的描述符对应事件
//maxe:evs数组的节点个数  指定要获取事件的最大个数
//to:超时事件 毫秒
//返回值:超时返回0,失败返回-1,有就绪返回就绪事件个数

操作流程:

1,调用epoll_create后,会在内核中创建epoll句柄结构

struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};

2,向内核epoll句柄结构中添加要监控的描述符以及对应事件结构

3,传入一个事件结构数组,开始监控,监控是一个异步阻塞操作

  1. 告诉系统开始监控,而描述符放入监控由系统完成
  2. 系统为每个描述符的就绪事件做了一个事件回调函数,一旦某个描述符就绪了指定事件,就会调用事件回调函数,将这个描述符对应事件结构添加到就绪事件双向链表
  3. epoll_wait接口每隔一段时间查看epoll句柄结构的rdllist-就绪双向链表是否为空,就可以判断有没有描述符就绪,超时则直接返回,如果有就绪,则将就绪的事件结构信息拷贝到传入的数组中
  4. 监控调用返回后,只需要遍历evs数组,逐个对节点中的描述符进行对应事件的处理即可

epoll事件触发方式:

IO事件就绪:

                  可写:描述符发送缓冲区中剩余空间大小大于低水位标记(基准值)

                  可读:描述符接收缓冲区中数据大小大于低水位标记(基准值)  

IO事件就绪触发方式:

(1)水平触发

        可读:只要缓冲区中有数据就会触发可读事件

        可写:只要缓冲区中有剩余空间就会触发可写事件

(2)边缘触发

        可读:只有新数据到来才会触发可读事件

        可写:只有缓冲区从没有有剩余空间变为有剩余才会触发可写事件

边缘触发可以提高处理效率:

若某个描述符有数据接收到,但数据不完整,如果使用水平触发,不从缓冲区取出数据则一直触发事件,而边缘触发只有新数据到来才会触发事件,查看数据是否完整,有效避免一种事件由于数据不完整不想处理但一直被不断触发的情况。

但边缘触发要求,一次事件触发就必须将要处理的数据完全取出处理,因为在没有新数据到来的情况下不会再次触发事件去处理剩余数据

如何将缓冲区所有数据全部取出?循环取出,直至无数据,但无数据继续读会阻塞

因此边缘触发IO必须使用非阻塞操作,无数据报错返回

epoll优缺点:

优点:

  1. 所能监控的描述符没有数量上限
  2. 描述符以及事件结构只需要向内核拷贝一次(epoll_event保存返回)
  3. 监控原理采用异步阻塞,监控由系统完成,性能不会随描述符增多而下降
  4. 直接返回接续描述符对应事件结构,减少空遍历

缺点:

  1. 跨平台移植性较差
  • 多路转接模型:

适用于大量描述符需要监控,但同一时间只有少量描述符就绪活跃的场景

poll/select适用于单个描述符的超时控制

单个描述符的临时超时控制不适用于epoll,而适用于大量监控中

多路转接模型搭配线程池使用,大量描述符监控,就绪事件抛入线程池进行处理

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HT . WANG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值