目录
2,int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式。

在内核将数据未准备好之前,系统调用仍然会返回,并且返回EWOULDBLOCK错误码,非阻塞IO一般需要程序员以循环的方式反复读写文件描述符,这个过程称为轮询,这对CPU是极大的浪费,一般特定的场景使用。

在内核将数据准备好以后,通过SIGIO信号通知应用程序进行IO操作

虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件 描述符的就绪状态。

在内核将数据拷贝完成后,通知应用程序(而信号驱动是告诉应用程序何时开始拷贝数据).

小结:
任何IO过程中都包含两个步骤,第一是等待,第二是拷贝,而在实际应用的过程中,等待时间远远高于拷贝的过程。让IO更高效,最核心的办法就是让等待的时间尽可能的长。
同步和异步关心的是消息通信机制
1,同步,在系统调用时,在没有的得到结果之前,该调用不返回,直到调用返回结果。
2,异步,调用在发出以后,这个调用就直接返回了,没有返回结果,调用者通过信号,状态,来通知调用者。
//此同步非互斥和同步里面的同步
1,进程/线程同步也是进程/线程之间直接的制约关系
2,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、 传递信息所产生的制约关系. 尤其是在访问临界资源的时候。
一个文件描述符,默认都是阻塞IO。
通过fcntl设置文件描述符,函数原型如下
- #include
- #include
-
- int fcntl(int fd, int cmd, ... /* arg */ );
传入的cmd的值不同, 后面追加的参数也不相同. fcntl函数有5种功能:
复制一个现有的描述符(cmd=F_DUPFD).
获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞
实现函数setnoblock()
- void setnoblock(int fd)
- {
- int f1=fctl(fd,F_GETFL);
- if(f1<0)
- {
- cerr<<"fcntl"<
- return ;
- }
- fcntl(fd,f_SETFL,f1|O_NONBLOCK);
-
- }
a,使用F_GETFL将当前的文件描述符的属性取出来(这是一个位图).
b,然后再使用F_SETFL将文件描述符设置回去. 设置回去的同时, 加上一个O_NONBLOCK参数
- 1 #include
- 2 #include
- 3 #include
- 4
- 5 void setnoblock(int fd)
- 6 {
- 7 int f1=fcntl(fd,F_GETFD);
- 8 if(f1<0)
- 9 {
- 10 std::cerr<<"fcntl"<
- 11 return ;
- 12 }
- 13 fcntl(fd,F_SETFD,f1|O_NONBLOCK);
- 14
- 15 }
- 16 int main()
- 17 {
- 18 setnoblock(0);
- 19 while (1) {
- 20 char buf[1024] = {0};
- 21 ssize_t read_size = read(0, buf, sizeof(buf) - 1);
- 22 if (read_size < 0) {
- 23 std::cout<<"read"<
- 24 sleep(1);
- 25 continue;
- 26 }
- 27 std::cout<
- 28 }
- 29 return 0;
- 30 }
3,I/O多路转接之select
1,系统提供select函数来实现多路复用输入/输出模型.
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
select时间复杂度---》O(n)
2,函数原型
- #include
- #include
- #include
-
- int select(int nfds, fd_set *readfds, fd_set *writefds,
- fd_set *exceptfds, struct timeval *timeout);
1,nfds,代表最大文件描述符+1。
2,rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描 述符的集合;
3,参数timeout为结构timeval,用来设置select()的等待时间
a,参数timeout取值
NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件; 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
b,fd_set结构,其准确的来说是一个位图,是采用对应的位监视某一个文件描述符,
听过一组函数来操作位图。
- void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
-
- int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
-
- void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
-
- void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位
3,select特点
可监控的文件描述符个数取决与sizeof(fd_set)的值. 我这边服务器上sizeof(fd_set)=512,每bit表示一个文件 描述符,则我服务器上支持的最大文件描述符是512*8=4096
将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd, 一,是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。
二,是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数
4,select服务端举例
- #include"sock.hpp"
- #define NUM 1024
- #define DFL_FD -1
- namespace so_sever
- {
- class Select_Sever
- {
- private:
- int listen_sock;
- unsigned short port;
- public:
- Select_Sever(unsigned short _port):port(_port)
- {}
- //先初始化fd,起任务,填充select变量
- void InitSelectSever()
- {
- listen_sock= so_Sock::so_sock::Socket();
- so_Sock::so_sock::Bind(listen_sock,port);
- so_Sock::so_sock::Listen(listen_sock);
- }
- void Run()
- {
- fd_set rfds;
- int fd_array[NUM]={0};
- //让数组中所有数据都变成-1,然后填充对应的监听套接字
- clearArrar(fd_array,NUM,DFL_FD);
- fd_array[0]=listen_sock;
- for( ; ;)
- {
- //select时间也需要设置,输入输出型参数
- struct timeval timeout={5,0};
-
- //对所有的合法fd重新设置
- int maxfd=DFL_FD;
- FD_ZERO(&rfds);//对select中读描述符进行重新设置
- //对文件描述符数组进行判断
- for(int i=0;i
- {
- if(fd_array[i]==DFL_FD)
- {
- continue;
- }
- //合法的文件描述符
- FD_SET(fd_array[i],&rfds);
- if(fd_array[i]>maxfd)
- {
- maxfd=fd_array[i];
- }
- }
- //select 阻塞等待
-
- switch (select(maxfd+1,&rfds,nullptr,nullptr,/*&timeout*/ nullptr))
- {
- case 0:
- std::cout<<"timeout....... "<
- break;
- case -1:
- std::cout<<"select error "<
- break;
- default:
- // std::cout<<"select wait success"<
- Hander(rfds,fd_array,NUM);
- break;
- }//end switch
- }//end for
- }
- void Hander(const fd_set &rfds,int fd_array[],int num)
- {
- //读取套接字
- //如何判断套接字已经等待成功 在fd数组里&&rfds里面这个已经存在
- for(int i=0;i
- {
- if(fd_array[i]==DFL_FD)
- {
- continue;
- }
- //说明这个文件描述符已存在
- if(FD_ISSET(fd_array[i],&rfds) && fd_array[i]==listen_sock)
- {
- //说明等待成功
- //接受套接字等待成功,读事件还没有就绪
- struct sockaddr_in peer;
- socklen_t len=sizeof(peer);
- //这里会不会阻塞,不会,已经有套接字加入到数组里面,
- int sock=accept(fd_array[i],(struct sockaddr*)&peer,&len);
- if(sock<0)
- {
- std::cout<<"accept error"<
- continue;
- }
- //端口转换
- uint16_t peer_port=htons(peer.sin_port);
- //ip转换
- std::string peer_ip=inet_ntoa(peer.sin_addr);
-
- std::cout<<"get a new link "<<" port "<
" ip "< - //走到这里 能否读取数据?? 不能 recv是IO,select只是等
- //要将文件描述符添加到fd——fd_array
- if(!AddFdTorray(fd_array,num,sock))
- {
- //说明没添加成功
- close(sock);
- std::cout << "select server is full, close fd : " << sock << std::endl;
- }
- }//end if
- else
- {
- //说明可以读取数据了
- if(FD_ISSET(fd_array[i],&rfds))
- {
- //是一个合法的fd,并且可以读取了
- //是一个合法的fd,并且已经就绪了,是读数据事件就绪
- //实现读写,会阻塞吗??绝对不会
- char buffer[1024];
- //能确定你读完了请求吗???
- //如果我一条链接给你发了多个请求数据,但是每个都只有10字节, 粘包?
- //如果没有读到一个完整的报文,数据可能丢失
- //这里我们怎么保证自己能拿到完整的数据呢??
- //1. 定制协议
- //2. 还要给每一个sock定义对应的缓冲区
- //ssize_t s=read(fd_array[i],buffer,sizeof(buffer)-1);
- ssize_t s=recv(fd_array[i],buffer,sizeof(buffer)-1, 0);
- if(s>0)
- {
- buffer[s]=0;
- std::cout<
- }
- else if(s == 0)
- {
- std::cout<<" client close "<
- //对端关闭
- close(fd_array[i]);
- fd_array[i]=DFL_FD;//清除文件描述符
- }
- else
- {
- std::cout<<" recv error "<
- close(fd_array[i]);
- fd_array[i]=DFL_FD;
- }
- }
- else
- {
- //todo
- }
- }//end if
-
- }//end for
- }
- ~Select_Sever()
- {}
- private:
- void clearArrar(int fd_array[],int num,int default_fd)
- {
- for(int i=0;i
- {
- fd_array[i]=default_fd;
- }
- }
- bool AddFdTorray(int fd_array[],int num,int sock)
- {
- for(int i=0;i
- {
- if(fd_array[i]==DFL_FD)
- {
- fd_array[i]=sock;
- return true;
- }
- }
- return false;
- }
-
- };
- }
5,select缺点
每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select支持的文件描述符数量太小
4,poll多路转接
poll==>时间复杂度O(n),
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
poll与select函数基本一致,只是把事件集合封装了一下,使得提前处理参数,后续也不用每次轮询时查找。
- #include"sock.hpp"
- #include
-
- namespace Poll_etta
- {
- class poll_sever
- {
- private:
- int listen_sock;
- unsigned short port;
- public:
- poll_sever(int _port):port(_port)
- {}
- //初始化
- void InitSever()
- {
- listen_sock=so_Sock::so_sock::Socket();
- so_Sock::so_sock::Bind(listen_sock,port);
- so_Sock::so_sock::Listen(listen_sock);
- }
- //RUN 任务
- void Run()
- {
- struct pollfd rfds[64];
- //初始化参数
- for(int i=0;i<64;i++)
- {
- rfds[i].fd=-1;
- rfds[i].events=0; //我所关心的事件
- rfds[i].revents=0; //操作系统对我关心的事件 做出回应
- }
- //填充我所关心的事件
- rfds[0].fd=listen_sock;
- rfds[0].events=POLLIN;//关心读事件
- rfds[0].revents=0;//内核填充
- for(; ;)
- {
- switch(poll(rfds,64,-1))
- {
- case 0:
- std::cout<<"time out"<
- break;
- case -1:
- std::cerr<<"poll error"<
- break;
- default:
- //处理逻辑,有事件到来
- for(int i=0;i<64;i++)
- {
- if(rfds[i].fd==-1)
- {
- continue;
- }
- if(rfds[i].revents&POLLIN)
- {
- //能accept吗 不能 要填充就绪事件
- if(listen_sock==rfds[i].fd)
- {
- std::cout<<" get a new link"<
- }
- else
- {
- //recv数据
- }
- }
- }
- break;
-
- }
- }
-
- }
- ~poll_sever()
- {}
- };
- }
5,epoll多路转接
手册上说:为处理大批量句柄而作改进的POLL,
其主要有三个函数接口。
1,int epoll_create(int size)
创建出一个epoll的句柄,size一般被忽略。
调用完成以后,必须close掉。
2,int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll的事件注册函数.
它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注 册要监听的事件类型.
第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示.
第三个参数是需要监听的fd.
第四个参数是告诉内核需要监听什么事.
第二个参数的取值:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD :修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd;
struct epoll_event结构如下:

events可以是以下几个宏的集合:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来); EPOLLERR : 表示对应的文件描述符发生错误;
EPOLLHUP : 表示对应的文件描述符被挂断;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里.
3.epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件.
参数events是分配好的epoll_event结构体数组. epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个
events数组中,不会去帮助我们在用户态中分配内存). maxevents告之内核这个events有多 大,这个 maxevents的值不能大于创建epoll_create()时的size.
参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).
如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小 于0表示函数失败
6.epoll工作原理

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有 两个成员与epoll的使用方式密切相关。
每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象添 加进来的事件
这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来 (红黑树的插入时间效率是lgn,其中n为树的高度).
而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的 事件发生时会调用这个回调方法.
这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
在epoll中,对于每一个事件,都会建立一个epitem结构体
- struct epitem{
- struct rb_node rbn;//红黑树节点
- struct list_head rdllink;//双向链表节点
- struct epoll_filefd ffd; //事件句柄信息
- struct eventpoll *ep; //指向其所属的eventpoll对象
- struct epoll_event event; //期待发生的事件类型
- }
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.
如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度 是O(1)
总结一下, epoll的使用过程就是三部曲:
调用epoll_create创建一个epoll句柄;
调用epoll_ctl, 将要监控的文件描述符进行注册;
调用epoll_wait, 等待文件描述符就绪
epoll的优点(和 select 的缺点对应)
1,接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文 件描述符, 也做到了输入输出参数分离开 数据拷贝轻量: 只在合适的时候调用 2,EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频 繁(而select/poll都是每次循环都要进行拷贝)
3,事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述 符数目很多, 效率也不会受到影响.
4,没有数量限制: 文件描述符数目无上限
7,epoll代码示例
- #include"sock.hpp"
- #include
-
- #define MAX_NUM 64
- namespace ns_epoll
- {
- class EpollSever
- {
- private:
- int epfd; //epoll_ctl 的第一个参数,表示当前有几个文件描述符存在epoll里面
- int listen_sock;
- uint16_t port;
- public:
- //构造
- EpollSever(uint16_t _port):port(_port)
- {}
- //初始化套接字
- void InitSever()
- {
- listen_sock=so_Sock::so_sock::Socket();
- so_Sock::so_sock::Bind(listen_sock,port);
- so_Sock::so_sock::Listen(listen_sock);
- //监听完毕,打印出来kankan
- std::cout<<"deBug test "<<" Listen sock "<
- //初始化epoll函数的第一个参数
- if((epfd=epoll_create(256))<0)
- {
- //创建失败
- std::cout<<"epoll create fail"<
- exit(4);
- }
- }
- void AddEvent(int sock,uint16_t event)
- {
- struct epoll_event ev;
- ev.events=0;//初始化
- ev.events|=event;
- ev.data.fd=sock;
-
- //添加等待队列失败,继续等待
- if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev)<0)
- {
- std::cout<<" epoll_ctl add fail "<<" sock "<
- }
- }
- void DeleteEvent(int sock)
- {
- if(epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr)<0)
- {
- std::cout<<" delete events fail "<
- }
- }
- //事件跑起来
- void Run()
- {
- //走到这里,至少有一个套接字,把套接字加到等待队列中,让epoll_wait “等”个文件描述符就绪
- AddEvent(listen_sock,EPOLLIN);
- int timeout=-1;
- struct epoll_event revs[MAX_NUM];//定义最大等待数
- //循环等待
- for(;;)
- {
- //返回值代表有几个事件准备好
- int num=epoll_wait(epfd,revs,MAX_NUM,timeout);
- if(num>0)
- {
- //说明等待成功,但是那个文件描述符就绪 不知道 遍历
- for(int i=0;i
- {
- int sock = revs[i].data.fd;
- if(revs[i].events & EPOLLIN)//读事件就绪
- {
- if(sock==listen_sock)
- {
- //连接事件就绪
- struct sockaddr_in peer;
- socklen_t len=sizeof(peer);
- int sk=accept(sock,(struct sockaddr*)&peer,&len);
- if(sk<0)
- {
- std::cout<<"accept fail "<
- continue;
- }
- //得到一个新连接
- std::cout<<"get a new link "<
- AddEvent(sk,EPOLLIN);
- }
- //彻底就绪,直接读取文件
- else
- {
- char buffer[1024];
- ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
- if(s>0)
- {
- buffer[s]=0;
- std::cout<
- }
- else
- {
- std::cout<<"Client close "<
- close(sock);
- //移除这个套接字在epfd中
- DeleteEvent(sock);
- }
- }
- }
- else if(revs[i].events&EPOLLOUT)
- {
- // todo
- }
- else
- {
- //do some thing
- }
- }
- }
- else if(num==0)
- {
- std::cout<<" time out"<
- }
- else
- {
- std::cout<<" epoll error "<
- }
- }
-
- }
- ~EpollSever()
- {
- if(listen_sock>=0)close(listen_sock);
- if(epfd>=0)close(epfd);
- }
-
- };
- }
-
相关阅读:
HDFS High Availability(HA)高可用配置
八股文之git
DFT specification file & string
域名信息收集
【浅学Java】Bean的作用域和生命周期
https 原理与实践
C#——字符串
【687. 最长同值路径】
拉格朗日多项式
PS(Photoshop)去水印的4个方法
-
原文地址:https://blog.csdn.net/m0_63111921/article/details/126832424