标签:监控 最大数 sizeof 创建 发送数据 inux 函数 type mod
I/O多路转接(多路复用)又被称为“事件驱动”,是操作系统提供的一个功能,当你关心的文件(如socket)可读、可写时(称为事件就绪)采用某种方式通知你,只有收到通知时你才去执行read/write操作,这样在每次读或写时就不会阻塞,即I/O操作中等的部分交给操作系统内核去完成,而read/write之类的操作只需要在事件就绪时完成数据拷贝。等的过程由select/poll/epoll等系统调用触发,这些函数可同时监视多个描述符上的事件是否就绪,因此可以在一个线程内不发生阻塞的交替完成多个文件的I/O操作。复用是指复用同一个线程。
函数声明:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数解释:
函数返回值:
文件描述符集fd_set结构本质上是一个位图,其中比特位值为1/0代表对于该文件描述符的某种事件关注/不关注,对fd_set结构的操作有:
void FD_CLR(int fd, fd_set *set); //清除fd_set中相关的fd位 int FD_ISSET(int fd, fd_set *set);//检测fd位是否为真,事件是否就绪 void FD_SET(int fd, fd_set *set);//将fd添加到fd_set中 void FD_ZERO(fd_set *set);//将fd_set清0
注意:函数中文件描述符集参数既是输入型参数也是输出型参数,因此需要在每次调用select之前被重新设定。因此也就需要用户维护一个容器保存关注的所有文件描述符。
select的特点:
typedef struct fd_set { fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)]; } fd_set;
select缺点:
函数声明:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ };
函数参数:
返回值:
小于0表示出错,等于0表示超时,大于0,有几个事件就绪。
poll的优点:
poll的缺点:
以上缺点在监听的文件描述符数目增多时都会影响效率。
用例:poll_server
epoll是为了处理大批量句柄而做了优化的poll(man手册),几乎修正了select和poll的所有缺点。
相关系统调用:
#include <sys/epoll.h> int epoll_create(int size); //Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;
创建一个epoll的句柄,返回一个描述符指向这个epoll句柄。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll事件注册函数,epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改,做到了有变化才变更,将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理),避免了大量的内存拷贝。
struct epoll_event结构如下:
typedef union epoll_data { void *ptr; int fd; //关注的文件描述符 uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数events是由用户提前分配好的一个满足需求足够大小的结构体数组,maxevents为该结构体数组的容量,timeout同poll,当epoll_wait返回时内核将所有已就绪事件epoll_event拷贝至events数组内(epoll_event结构体内容与用户设置时相同),返回值为存在就绪事件描述符个数。
epoll工作原理
当调用epoll_create时,内核会创建一个eventpoll结构体,该结构体中有两个重要的成员:
当执行epoll_ctl时,除了将事件增加到红黑树中,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个事件就绪了,就将该事件添加到就绪链表中。因此在调用epoll_wait时,只需检查就绪链表内是否有数据,有数据立即返回,没有数据,就在timeout时间内阻塞。epoll_wait不同于select/epoll的是,不需要在被调用时遍历自己所关注的事件,查看是否有就绪的,因为提前注册事件,这些工作都被内核中回调函数与就绪链表替代,epoll_wait只需检测链表是否为空,等待就好。
epoll使用过程:
epoll优点:
epoll工作方式:以socket为例
水平触发LT
当读事件就绪,此时接受缓冲区内有数据,若不讲接受缓冲区内数据处理完,则会一直触发读事件就绪;
当写事件就绪,此时发送缓冲区中有空间,若未将发送缓冲区打满,则会一直触发写事件就绪。
边缘触发ET
当接收缓冲区为满或有新的数据到来,会触发一次读事件就绪,如果此后再也没有数据到来,就再也不会触发读事件就绪,即使缓冲区内依然有之前收到的数据未处理完;
当发送缓冲区为空或剩余空间变化时,会触发一次写事件就绪,如果发送缓冲区剩余空间一直不变,就一直不触发,即使还有剩余空间。
可见在ET模式下,在读事件就绪时,需要一次将所有数据全部读取,因为如果再无读事件就绪,就再也无法读到未读完的数据。所以需要循环式的读取缓冲区中内容,直到实际读取大小小于期待读取大小或读到空为止,因此在ET模式下,描述符必须设为非阻塞,如果读到空,可避免阻塞。对于单线程而言,倘若阻塞,一直没有数据到来,就挂了。
例:客户端向服务器发送10k数据(触发服务器一次读事件就绪),服务器处于ET模式下,服务器由于某种原因一次只读取了1k数据,剩余9k处于仍接受缓冲区内,只有下次客户端再向服务器发送数据(再触发读事件就绪),服务器才能读到那9k数据。由于服务器没能读到完整信息,无法给客户端响应,客户端没收到响应,也不会发送下一个请求,该连接将处于僵持状态。为避免上述状况(一次read未能将数据读完),可以采用非阻塞轮询的方式来读取缓冲区,确保一定能将数据全部读出。
select/poll工作在LT模式下,epoll默认工作方式为LT,也支持ET。
epoll的使用场景:
对于多连接的,且只有一部分连接比较活跃时,比较适合用epoll。
多路复用,事件驱动本质上还是IO事件,适宜于IO密集型工作,一个工作进程就可完成。但对于计算密集型事务,事件驱动并不合适,一个计算需要CPU耗时2秒,这两秒对于整个进程是完全阻塞的,即使有事件就绪也只能等待。这是多进程多线程就体现出优势,每个线程各做各的事情互补干扰。因此事件驱动适宜IO密集型业务,多进程多线程适宜计算密集型业务。
标签:监控 最大数 sizeof 创建 发送数据 inux 函数 type mod
原文地址:https://www.cnblogs.com/dabai56/p/11279835.html