第九章 IO复用
IO复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常,网络程序在下列情况下需要使用/O复用技术:
- 客户端程序要同时处理多个socket。.比如本章将要讨论的非阻塞connect技术。
- 客户端程序要同时处理用户输入和网络连接。比如本章将要讨论的聊天室程序。
- TCP服务器要同时处理监听socket和连接socket。这是I/O复用使用最多的场合。后续章节将展示很多这方面的例子。
- 服务器要同时处理TCP请求和UDP请求。比如本章将要讨论的回射服务器。
- 服务器要同时监听多个端口,或者处理多种服务。比如本章将要讨论的xinetd服务器。
需要指出的是,/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每一个文件描述符,这使得服务器程序看起来像是串行工作的。如果要实现并发,只能使用多进程或多线程等编程手段。
9.1 select系统调用
select系统调用的用途是: 在一段指定时间内, 监听用户感兴趣的文件描述符上的可读, 可写和异常等事件.
1 |
|
nfds 监听的文件描述符总数
readfds, writefds, exceptfds分别指向可读, 可写和异常事件对应的文件描述符集合. select调用返回时, 内核将修改他们来通知应用程序哪些文件描述符已经就绪.
- fd_set结构体仅包含一个整型数组, 该数组的每个元素的每一位标记一个文件描述符
1 | // 由于位操作过于烦琐, 可以使用一系列宏简化 |
- timeout可以设置超时时间(精确到微秒). 如果传入NULL, 则select将一直阻塞, 知道某个文件描述符就绪.
- select成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0。select失败时返回-l并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1, 并设置errno为EINTR。
文件描述符就绪条件
可读:
可写:
带外数据
socket收到带外数据也将使select返回, 不同于接收到普通数据的socket, 前者处于异常状态
1 | // 对于接收普通数据的就绪socket |
9.2 poll系统调用
poll与select类似, 也是在指定时间内轮询一定数量的文件描述符, 以测试其中是否有就绪者.
1 |
|
- timeout的单位是毫秒, 值为-1时, poll将永远阻塞, 直至某个事件发生.
9.3 epoll系列系统调用
epoll是Linux特有的IO复用函数, 在实现和使用上与select, poll有很大差异.
- epoll使用一组函数来完成任务,而不是单个函数。
- epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。
1 |
|
- size参数暂时不起作用, 只是提示内核这个事件表需要多大, 该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数, 以指定访问的内核事件表.
1 | int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); |
fd指明要操作的文件描述符, op则指定操作类型:
- EPOLL_CTL_ADD: 往事件表中注册fd上的事件
- EPOLL_CTL_MOD: 修改fd上的注册事件
- EPOLL_CTL_DEL: 删除fd上的注册事件
epoll支持的事件类型与poll类似, 在poll对应的宏前加上’E’. 但epoll有额外的两个事件类型(EPOLLET和EPOLLONESHOT)
epoll_data_t是一个联合体, 使用最多的fd, 用于指定事件所丛书的目标文件描述符; ptr指定与fd相关的用户数据.
epoll_wait函数
epoll系列的主要接口, 在一段超时时间内等待一组文件描述符上的事件
1 | int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout); |
- timeout单位是毫秒
- maxevents 指定最多监听多少个事件, 必须大于0
- epoll_wait函数如果检测到事件, 就将所有就绪的事件从内核事件表复制到events指向的数组中(这个数组只用于存储就绪事件, 不用于输入).
LT和ET模式
epoll对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和ET(Edge Trigger,边沿触发)模式。LT模式是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。
对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。
EPOLLONESHOT事件
在并发模式中, 可能出现某个连接的新数据被另一个线程操作的情况, 即两个线程同时操作一个socket. 为避免这种情况, 可以使用epoll的EPOLLONESHOT事件实现.
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。
工作线程的核心部分. 用休眠5s模拟处理socket之前的请求, 结束之后, 工作线程等待5s后仍然没有收到socket上的下一批客户数据, 则退出, 否则继续为它服务.
9.4 三组IO复用函数的比较
select, poll, epoll三组IO复用都能同时监听多个文件描述符.
epoll_wait采取回调方式来检测就绪事件, 其算法时间复杂度为O(1), 但是当活动连接较多时, epoll_wait的效率未必比select和poll高, 因为此时回调函数被触发地过于频繁. 所以epoll_wait适用于连接数量多, 但是活动连接比较少地情况.
9.5 IO复用地高级应用1: 非阻塞connect
当非阻塞地socket调用connect, 而连接又没有立即建立时, 会一个errno值为EINPROGRESS的错误. 这种情况下, 可以通过select, poll等监听这个连接失败的socket上的可写事件, 当函数返回后, 在利用getsockopt来读取错误码和清除该socket上的错误. 如果错误码是0, 则表示连接成功.
- 示例代理基于select系列实现
9.6 IO复用的高级应用2: 聊天室程序
该聊天室程序能让所有用户同时在线群聊,它分为客户端和服务器两个部分。
客户端程序有两个功能:
从标准输入终端读入用户数据,并将用户数据发送至服务器:
往标准输出终端打印服务器发送给它的数据。
服务器的功能: 接收客户数据,并把客户数据发送给每一个登录到该服务器上的客户端(数据发送者除外)。
- 示例代码基于poll系列实现
9.7 IO复用的高级应用3: 同时处理TCP和UDP服务
一个socket只能监听一个端口, 因此, 服务器如果要同时监听多个端口, 就必须创建多个socket, 并将它们分别绑定到各个端口上. 同理, 同一个端口, 同时处理TCP和UDP也需要创建两个socket.
- 示例代码基于epoll实现
- 使用epoll的事件表同时管理多个socket, 针对不同socket的就绪事件采取不同的措施.
9.8 超级服务xinetd
xinetd管理的子服务中有的是标准服务,比如时间日期服务daytime、回射服务echo和丢弃服务discard。xinetd服务器在内部直接处理这些服务。还有的子服务则需要调用外部的服务器程序来处理。xinetd通过调用fork和exec函数来加载运行这些服务器程序。比如telnet、fp服务都是这种类型的子服务。
- Title: 第九章 IO复用
- Author: Huan Lee
- Created at : 2023-08-20 08:08:09
- Updated at : 2024-02-26 04:53:15
- Link: https://www.mirthfullee.com/2023/08/20/notion-第九章 IO复用-b67d62ab/
- License: This work is licensed under CC BY-NC-SA 4.0.