linux多路io复用方法,linux系统特有的机制多路复用
套接字接收缓冲区中的字节数大于或等于其低水位线SO_RCVLOWAT。此时,我们可以无阻塞地读取套接字,读取操作返回的字节数大于0。
套接字的对方关闭连接。此时,对这个套接字的读操作将返回0。
侦听套接字上有新的连接请求。
套接字上未处理的错误。此时,我们可以使用getsockop读取并清除错误。
下列情况下可以编写套接字:
套接字内核的发送缓冲区中可用的字节数大于或等于其低水位线SO_SNDLOWAT。此时,我们可以无阻塞地写套接字,写操作返回的字节数大于0。
套接字的写操作被关闭。写入其写入操作被关闭的套接字将触发SIGPIPE信号。
使用套接字非阻塞连接成功或失败(超时)后。
套接字上有一个未处理的错误。这时,我们可以使用getsockopt读取并清除错误。
异常情况:
在网络中,select只能处理一种异常:在套接字上接收带外数据。
//两组文件描述符
fd_set readfds,testfds//readfds用来检测输出是否就绪的文件描述符的集合
server_sockfd=socket(AF_INET,SOCK_STREAM,0);//建立服务器套接字
server _地址. sin _家庭=AF _ INET
server _ address . sin _ addr . s _ addr=htonl(in addr _ ANY);
server _ address . sin _ port=htons(9000);
server _ len=sizeof(server _ address);
bind(server_sockfd,(struct sockaddr*) server_address,server _ len);
listen(server_sockfd,5);//侦听队列最多可以容纳5个。
FD _ ZERO(read FDS);//清除空缺0
FD_SET(server_sockfd,read FDS);//将服务器套接字添加到集合中
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct time val * time out);
while(1){
char ch
int fd
int nread
testfds=readfds//相当于做了一个备份拷贝,因为调用select后,传入的文件描述符集合会被修改。
struct timeval my _ time
my _ time . TV _ sec=2;
my _ time . TV _ usec=0;
printf(服务器等待\ n );
//监控server_sockfd和client_sockfd
//result=select(FD_SETSIZE,testfds,(fd_set*)0,(fd_set*)0,(struct time val *)0);//无限期阻塞,并测试文件描述符的更改。
result=select(FD_SETSIZE,testfds,(fd_set*)0,(fd_set*)0,my _ time);//按照my_time中设置的时间等待。如果超过,继续执行。
If(result 0){//有错误
perror( select errno );
出口(1);
}else if(result==0){//已经超过等待时间,没有响应。
FD _ ZERO(read FDS);//清除空缺0
FD_SET(server_sockfd,read FDS);//将服务器套接字添加回集合。
printf(无连接请求\ n );
继续;//如果没有响应,就不要往下遍历了
//扫描所有文件描述符(遍历所有文件句柄)是一件很耗时的事情,严重降低了效率。
for(FD=0;fd FD _ SETSIZEfd ){
//找到相关的文件描述符,并确定它是否在文件描述符testfds集合中。
if(FD_ISSET(fd,testfds)){
//判断是否是服务器套接字,如果是,说明客户端请求连接。
if(fd==server_sockfd){
client_len=sizeof(客户端地址);
client _ sockfd=accept(server _ sockfd,(struct sockaddr*) client_address,client _ len);
FD_SET(client_sockfd,read FDS);//将客户端套接字添加到集合中,以监视是否有数据传入。
printf(在fd %d上添加客户端\n ,client _ sockfd);
}else{//客户端有消息。
//获取接收缓冲区中的字节数
ioctl(fd,FIONREAD,n read);//即从fd中获取多少数据
//当客户端数据请求完成时,关闭套接字并从集合中清除相应的套接字描述符。
if(nread==0){
关闭(FD);
FD_CLR(fd,read FDS);//移除关闭的fd
printf(在fd %d\n 上删除客户端,FD);
}else{//处理客户号请求
read(fd,ch,1);
睡眠(5);
printf(在fd %d\n 上为客户端提供服务,FD);
ch;
write(fd,ch,1);
返回0;
}
client_sockfd=socket(AF_INET,SOCK_STREAM,0);//建立客户端套接字
address.sin _ family=AF _ INET
address . sin _ addr . s _ addr=inet _ addr( 127 . 0 . 0 . 1 );
address . sin _ port=htons(9000);
len=sizeof(地址);
result=connect(client_sockfd,(struct sockaddr*) address,len);
if (result==-1){
perror(哎呀:client 2 );
出口(1);
//第一次读写
write(client_sockfd,ch,1);
read(client_sockfd,ch,1);
printf( first time:char from server=% c \ n ,ch);
睡眠(5);
//第二次读写
write(client_sockfd,ch,1);
read(client_sockfd,ch,1);
printf(第二次:来自服务器的char=% c \ n ,ch);
close(client _ sockfd);
返回0;
}
轮询功能
FDS:一个PollFD类型的数组,它存储了我们感兴趣的文件描述符上发生的所有可读、可写和异常事件。有关结构定义的详细信息,请参见pollfd结构。
Nfds:数组fds中元素的数量,类型为nfds_t无符号整数。
超时:超时等待时间。
-1:保持阻塞,直到某个事件发生。
0:呼叫后不要等待立即返回。
0:表示超时。
\ 0:表示fds中这么多文件描述符都准备好了。即fds中具有非零revents字段的pollfd结构的数量。
struct poll FD FDS[MAX _ FD];
int cur _ max _ FD=0;//当前要侦听的最大文件描述符是1,减少了要遍历的数目。
int main(void){
int server_sockfd,client _ sockfd
int server_len,client _ len
struct sockaddr _ in server _ address,client _ address
int结果;
server_sockfd=socket(AF_INET,SOCK_STREAM,0);//服务器套接字
server _地址. sin _家庭=AF _ INET
server _ address . sin _ addr . s _ addr=htonl(in addr _ ANY);
server _ address . sin _ port=htons(9000);
server _ len=sizeof(server _ address);
bind(server_sockfd,(struct sockaddr*) server_address,server _ len);
listen(server_sockfd,5);
//将被监控文件的描述符添加到fds数组
fds[server_sockfd]。fd=server _ sockfd
fds[server_sockfd]。events=POLLIN
fds[server_sockfd]。revents=0;
if(cur_max_fd=server_sockfd){
cur _ max _ FD=server _ sockfd 1;
while(1){
char ch
int i,FD;
int nread
printf(服务器等待\ n );
结果=poll(fds,cur_max_fd,1000);
if(结果0){
perror( server 5 );
出口(1);
}else if(result==0){
printf(无连接,结束等待\ n );
}else{//大于0,返回fds中处于就绪状态的文件描述符的数量。
//扫描文件描述符
for(I=0;i cur _ max _ fdi ){
If(fds[i].revents){//有结果吗?没有结果表示文件描述符上没有发生任何事件。
fd=fds[i]。FD;
//判断是否是服务器套接字,如果是,说明客户端请求连接。
if(fd==server_sockfd){
client_len=sizeof(客户端地址);
client _ sockfd=accept(server _ sockfd,(struct sockaddr*) client_address,client _ len);
客户端_sockfd。fd=client _ sockfd
客户端_sockfd。events=POLLIN
客户端_sockfd。revents=0;
if(cur_max_fd=client_sockfd){
cur _ max _ FD=client _ sockfd 1;
printf(在fd %d上添加客户端\n ,client _ sockfd);
}else{//客户端套接字中有数据请求
If(fds[i].revents POLLIN){//Read
nread=read(fd,ch,1);
if(nread==0){
关闭(FD);
memset( fds[i],0,sizeof(struct poll FD));
printf(在fd %d\n 上删除客户端,FD);
}else{//写
睡眠(5);
printf(在fd %d上为客户端提供服务,接收:%c\n ,fd,ch);
ch;
fds[i]。events=POLLOUT//添加写事件监视器
}埃尔塞夫(FDS[我]。revents poll out){//写入
write(fd,ch,1);
fds[i]。events=POLLIN
返回0;
}
Epoll是Linux特有的I/O复用功能。它与select和poll在实现和使用上有很大的不同。
Epoll使用一组函数来完成一项任务,而不是单一函数。
Epoll将用户关心的文件描述符上的事件放在内核的一个事件表中,这样就不需要像select和poll一样每次都重复传入文件描述符集或者事件集。Epoll需要使用一个额外的文件描述符来唯一地标识内核中的这个事件表。
Fd:被操作的文件描述符。
Event:是指向epoll_event结构的指针。该结构的定义如以下event_event- structure所示:
成功:返回0。
失败:返回-1并设置错误号。
Epoll_data_t是一个联合体,所以我们只能使用fd或ptr成员中的一个。
如果希望将文件描述符与用户数据相关联,以实现快速数据访问,可以放弃在epoll_data_t中使用fd成员,而是将fd包含在ptr指向的自定义用户数据中。
当我们调用epoll_wait时,evlist数组中epoll_event的每个数据参数都是我们一开始指定的(即调用epoll_ctl)。比如上面提到的,我们指定了自定义数据ptr,最后一个fd产生了我们监控的事件,这个事件我们可以从它对应的epoll_event的数据中得到。例如下面epoll- simple web服务器中的_ConnectStat结构。
Epfd: epoll文件描述符,指定内核事件表。
Evlist:分配的epoll_event结构的数组,epoll会将事件复制到evlist数组中。
Maxevents:最大监听时间,必须大于0。
超时:表示没有检测到事件时的最大等待时间(毫秒)。
0:将立即返回,无需等待。
-1:表示无限期阻塞,直到事件发生。
\ 0:阻塞(等待)时间。
//单独拿出来声明typedef是因为下面的函数指针
typedef struct _ connect stat connect stat;
typedef void(* response _ handler)(connect stat * stat);
//保存自定义数据的结构,调用epoll时使用epoll_data_t中的ptr进行存储。
struct _ConnectStat {
int fd//文件描述符
字符名称[64];//名称
char年龄[64];//年龄
struct epoll _ event _ ev//当前文件句柄对应于epoll事件
int状态;//0-未登录,1-已登录
response_handler句柄;//不同页面的处理功能
};
相关的函数声明和全局变量
//初始化自定义数据存储结构
connect stat * stat _ init(int FD);
//将新链接的客户端fd放入当前epoll对应的内核事件表中。
void connect _ handle(int new _ FD);
//请求响应——指定相应的处理函数
void do _ http _ respone(connect stat * stat);
//处理http请求
void do _ http _ request(connect stat * stat);
//响应处理程序3354的请求,链接返回的内容。
void welcome _ response _ handler(connect stat * stat);
//响应处理函数——commit后返回的内容
void commit _ respone _ handler(connect stat * stat);
//将新链接的客户端fd放入当前epoll对应的内核事件表中。
void connect _ handle(int new _ FD);
//创建监听套接字-省略
int startup(char* _ip,int _ port);
//将fd-设置为非阻塞状态,即给指定的fd添加一个状态
void set _ non block(int FD);
//打印信息提示ip:port
void用法(const char * argv);
//响应标头
const char * main _ header= HTTP/1.0 200 OK \ r \ n Server:郭瑄瑄服务器\ r \ n content-Type:text/html \ r \ n connection:Close \ r \ n ;
静态int epfd=0;//epoll文件描述符,对应一个内核事件表。
初始化自定义数据存储结构
//初始化自定义数据存储结构
ConnectStat * stat_init(int fd) {
ConnectStat * temp=NULL
temp=(ConnectStat *)malloc(sizeof(ConnectStat));
如果(!温度){
fprintf(stderr, malloc失败。原因:% m \ n’);
返回NULL
memset(temp, \0 ,sizeof(ConnectStat));
temp-FD=FD;
temp-status=0;
}
处理http请求
//解析http请求
void do _ http _ request(connect stat * stat){
//读取和解析http请求
char buf[4096];
char * pos=NULL
ssize_t _s=read(stat- fd,buf,sizeof(buf)-1);
If (_s 0){//读取数据
buf[_ s]= \ 0 ;
//printf( receive from client:% s \ n ,buf);//GET/HTTP/1.1
pos=buf
//Demo只演示效果,没有详细的协议分析。
如果(!Rncasecmp (pos, get ,3)){//是Get请求吗?
stat-handler=welcome _ response _ handler;//设置执行功能
}else if(!Rncasecmp (pos, post ,4)){//是POST请求吗?
//获取uri
//printf(-Post-\ n );
pos=strlen( Post );
while(* pos== * pos==/)pos;
//提交/提交HTTP/1.1
如果(!Rncasecmp (pos, commit ,6)){//提交
int len=0;
//printf( post commit-\ n );
pos=strstr(buf, \ r \ n \ r \ n );//返回第一个匹配项的位置\r\n\r\n
char * end=NULL
//获取姓名和年龄
if (end=strstr(pos, name=){
pos=end strlen( name=);
end=pos
while(( A =* end * end= Z ) ( A =* end * end= Z ) ( 0 =* end * end= 9 ))end;
len=结束位置;
If (len 0) {//在自定义结构中存储名称
memcpy(统计名称、位置、结束位置);
stat-name[len]= \ 0 ;
if (end=strstr(pos, age=){
pos=end strlen( age=);
结束=位置
while (0=*end *end=9 )结束;
len=结束位置;
if (len 0) {//将年龄存入自定义结构体中
memcpy(状态、位置、结束位置);
stat-age[len]= \ 0 ;
stat-handle=commit _ respone _ handler;//设置响应函数
否则{
stat-handler=welcome _ response _ handler;//设置响应函数
否则{
stat-handler=welcome _ response _ handler;//设置响应函数
stat-_ evevents=epoll out//修改事件类型
epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);//修改,交给eoill监视。
}else if (_s==0){//没有读取到数据,客户端关闭。
printf(客户端:%d关闭\n ,统计-
epoll_ctl(epfd,EPOLL_CTL_DEL,stat- fd,NULL);//将对应软驱从对应使用的内核事件表中删除
关闭(统计- //关闭套接字
免费(stat);//释放内存
}else{//读取发生错误
perror(’读);
}
请求响应-根据指定的处理函数
void do _ http _ respone(connect stat * stat){
统计处理器(统计);//调用对应设置的函数
}
响应处理函数——请求链接返回的内容
void welcome _ response _ handler(connect stat * stat){
const char * welcome_content=\
html lang=zh-CN \n\
头部\n\
meta content= text/html;charset=utf-8 http-equiv= Content-Type \ n \
标题这是一个测试/title \n\
/head \n\
正文\n\
div align=center height= 500 px \ n \
br/br/br/\n\
h2 Hello World /h2 br/br/\n\
表单操作=提交方法=post \n\
姓名:输入type=text name=name/\n\
br/年龄:输入类型=密码名称=年龄/\n\
br/br/br/输入类型=提交值=提交/\n\
输入类型=重置值=重置/\n\
/form \n\
/div \n\
/body \n\
/html
char发送缓冲区[4096];
char content _ len[64];
strcpy(sendbuffer,main _ header);//拷贝响应头
snprintf(content_len,64, Content-Length: %d\r\n\r\n ,(int)strlen(welcome _ Content));
strcat(sendbuffer,content _ len);
strcat(sendbuffer,welcome _ content);
//printf(向客户端发送答复\n%s ,发送缓冲区);
//写给客户端-即发起请求的浏览器
write(stat- fd,sendbuffer,strlen(发送缓冲区));
stat-_ evevents=epoll in//修改关心的事件
//stat-_ ev。数据。ptr=stat
epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);
}
响应处理函数——委员会后返回的内容
void commit _ respone _ handler(connect stat * stat){
const char * commit_content=\
html lang=zh-CN \n\
头部\n\
meta content= text/html;charset=utf-8 http-equiv= Content-Type \ n \
标题这是一个测试/title \n\
/head \n\
正文\n\
div align=center height= 500 px \ n \
br/br/br/\n\
氘欢迎% s nbsp,年龄nbsp%s!/h2 br/br/\n\
/div \n\
/body \n\
/html
char发送缓冲区[4096];
字符内容[4096];
char content _ len[64];
int len=0;
len=snprintf(content,4096,commit_content,stat- name,stat-age);
strcpy(sendbuffer,main _ header);//响应头
snprintf(content_len,64, Content-Length: %d\r\n\r\n ,len);
strcat(sendbuffer,content _ len);
strcat(sendbuffer,content);
//printf(向客户端发送答复\n%s ,发送缓冲区);
write(stat- fd,sendbuffer,strlen(发送缓冲区));
stat-_ evevents=epoll in//修改关心的事件
epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);//交给使用来监视
}
打印信息提示ip:端口
空的用法(const char* argv){
printf(%s:[ip][port]\n ,argv);
}
将fd-设置为非阻塞状态
void set_nonblock(int fd){
//这里的文件状态标志旗即打开函数的第二个参数
int fl=fcntl(fd,F _ GETFL);//获取设置的旗
fcntl(fd,F_SETFL,fl O _ NONBLOCK);//设置旗
//fcntl函数https://blog.csdn.net/zhoulaowu/article/details/14057799
//O _ non块https://blog.csdn.net/cjfeii/article/details/115484558
}
创建一个监听套接字
int startup(char* _ip,int _port){
int sock=socket(AF_INET,SOCK_STREAM,0);
if (sock 0){
perror(’袜子);
出口(2);
int opt=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));
结构sockaddr _ in local
本地的。sin _ port=htons(_ port);
local.sin _ family=AF _ INET
local . sin _ addr . s _ addr=inet _ addr(_ IP);
if (bind(sock,(struct sockaddr*) local,sizeof(local)) 0){
perror( bind );
出口(3);
if (listen(sock,5) 0){
perror(‘听’);
出口(4);
退货袜子;//返回套接字
}
#include epoll_server.h
int main(int argc,char *argv[]){
如果(argc!=3){//检查输入参数的个数是否正确。
用法(argv[0]);
出口(1);
//创建服务器套接字
int listen_sock=startup(argv[1],atoi(argv[2]);
//创建一个epoll
epfd=epoll _ create(256);
If (epfd 0){//创建失败
perror( epoll _ create );
出口(5);
connect stat * stat=stat _ init(listen _ sock);//自定义数据存储
struct epoll _ event _ ev//epoll事件结构
_ ev.events=EPOLLIN//将关注的事件设置为读取事件。
_ ev . data . ptr=stat;//接收返回值
//在epfd中加入listen_sock,关心读取事件,有客户端请求链接。
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,_ ev);
struct epoll _ event revs[64];//接收生成响应的返回事件
int time out=-1;//-1无限期阻塞
int num=0;//就绪请求I/O的数量
while (1){
//检测事件
开关((num=epoll_wait(epfd,revs,64,timeout))){
0://Listen超时
printf( time out \ n );
打破;
案例1: //错误
perror( epoll _ wait );
打破;
默认:{ //0,即返回要处理的事件数。
//获取对应的文件描述符
struct sockaddr _ in peer
socklen _ t len=sizeof(peer);
for(int I=0;i numi ){//
//获取与该fd相关的链接信息
connect stat * stat=(connect stat *)revs[I]. data . ptr;
Int rsock=stat- //得到对应的fd,做出如下判断
If (rsock==listen _ sock (revs [i])。events)epollin){//有客户端链接
int new_fd=accept(listen_sock,(struct sockaddr*) peer,len);
if(new _ FD 0){//接受成功
printf(获取新客户端:%s:%d\n ,inet_ntoa(peer.sin_addr),ntohs(peer . sin _ port));
connect _ handle(new _ FD);//侦听传入的客户端fd
}else {//除服务器socket之外的其他fd都准备好了。
如果(revs [i].events Pollin){//有数据要读取
do _ http _ request((connect stat *)revs[I]. data . ptr);
} Elseif (revs [i]。事件poll out){//写入
do _ http _ respone((connect stat *)revs[I]. data . ptr);//完成响应后,我会再次关心EPOLLIN事件,等待下一个请求。
}否则{
打破;
返回0;
}
当被监控文件描述符上发生读写事件时,epoll_wait将通知处理程序进行读写。如果你这次没有一次性读写所有数据(比如读写缓冲区太小),那么下次调用epoll_wait时,它也会通知你在上次没有读写的文件描述符上继续读写。如果你不一直读写它,它会一直通知你。
如果系统中存在大量你不需要读写的就绪文件描述符,并且每次都会返回,这将大大降低处理器检索它所关心的就绪文件描述符的效率。
应用程序可能不会立即处理此事件,因为下次调用epoll_wait时,epoll_wait会再次向应用程序通告此事件。
设置方式:默认水平触发。
边沿触发(边沿触发):
当被监控文件描述符上发生读写事件时,epoll_wait将通知处理程序进行读写。如果这次没有读写完所有数据(比如读写缓冲区太小),就不会通知你,也就是只通知你一次,直到文件描述符上出现第二次读写事件才会通知你。
这种模式比水平触发更高效,系统不会被你不关心的就绪文件描述符淹没,大大减少了同一个epoll事件重复触发的次数。
同时,应用程序应该立即处理这个事件,因为后续的epoll_wait调用不会通知应用程序这个事件(后续的读写事件会被通知,而这个不会)。
设置方法(epoll):
相应文件描述符上要监视的事件设置为events =EPOLLET。
同时,文件描述符被设置为非阻塞模式。如上面的epoll- simple web服务器所示。
目的:确保套接字连接在任何时候都只由一个线程处理,从而确保连接的完整性并避免许多可能的争用情况。
可能的场景:当一个线程(或进程)读完套接字上的数据并开始处理时,在处理过程中可以在套接字上读取新的数据(EPOLLIN再次被触发),然后另一个线程被唤醒读取新的数据。所以就出现了两个线程操作一个套接字的情况。
用法:使用epoll_ctrl函数在此套接字(文件描述符)上注册EPOLLONESHOT事件。
一旦用EPOLLONESHOT事件注册的socket被一个线程处理了,这个socket上的EPOLLONESHOT事件就应该被立即复位,这样才能保证这个socket的EPOLLIN事件在下次可读的时候能够被触发,同时也给了其他线程处理这个socket的机会。
用于监控链接请求的Server_socket不能注册EPOLLONESHOT事件,否则应用程序只能处理一个客户链接,因为后续的客户链接请求将不再触发Server_socket上的EPOLLIN事件。
如果线程在处理完套接字上的请求后,在套接字上接收到新的客户端请求,则该线程将继续联系套接字。
struct sockaddr _ in client _ address;
socklen _ t client _ addrlength=sizeof(client _ address);
int connfd=accept( listenfd,(struct sockaddr* ) client_address,client _ addr length);
addfd( epollfd,connfd,true);
else if(事件[i].事件EPOLLIN ){
pthread_t线程;
fds fds _ for _ new _ worker
FDS _ for _ new _ worker . epollfd=epollfd;
FDS _ for _ new _ worker . sockfd=sockfd;
//创建一个线程来处理它
pthread_create( thread,NULL,worker,(void *)FDS _ for _ new _ worker);
else printf(发生了别的事\ n );
}
在相应的内核事件表中注册指定fd上的事件。
void addfd( int epollfd,int fd,bool oneshot ){
epoll_event事件;
event . data . FD=FD;
event.events=EPOLLIN EPOLLET
如果(一次性){
event.events =EPOLLONESHOT//注册EPOLLONESHOT事件
epoll_ctl( epollfd,EPOLL_CTL_ADD,fd,event);
setnonblocking(FD);//设置为非阻塞fd
}
将fd设置为非阻塞
int setnonblocking( int fd ){
int old_option=fcntl( fd,F _ GETFL);//获取这个fd之前设置的属性。
int new _ option=old _ option O _ non block;//追加O_NONBLOCK属性
fcntl( fd,F_SETFL,new _ option);//设置
返回old _ option//当前示例演示返回无意义且未使用的。
}
线程功能
void* worker( void* arg ){
int sockfd=((FDS *)arg)-sockfd;
int epollfd=((FDS *)arg)-epollfd;
printf(启动新线程以接收fd上的数据:%d\n ,sockfd);
char buf[BUFFER _ SIZE];
memset( buf, \0 ,BUFFER _ SIZE);
而(1 ){//因为是非阻塞的,所以要一次读完,也就是说要马上处理,因为epoll_wait只会提醒你一次。
int ret=recv( sockfd,buf,BUFFER_SIZE-1,0);
if( ret==0 ){
关闭(sockfd);
printf( foreiner关闭了连接\ n );
打破;
else if( ret 0 ){
If( errno==EAGAIN ){//读出
reset_oneshot( epollfd,sockfd);//重置注册事件
printf( read la ter \ n );
打破;
否则{
printf( get content: %s\n ,buf);
//睡眠5秒,模拟数据处理过程。
睡眠(5);
printf(结束在fd上接收数据的线程:%d\n ,sockfd);
}
复位fd上记录的事件
void reset_oneshot( int epollfd,int fd ){
epoll_event事件;
event . data . FD=FD;
event . events=EPOLLIN EPOLLET EPOLLONESHOT;
epoll_ctl( epollfd,EPOLL_CTL_MOD,fd,event);
三种I/O多路复用功能的比较
select的参数类型fd_set只是一组文件描述符,所以select需要这种类型的三个参数来区分可读、可写和异常事件。
一方面,select不能处理更多类型的事件;另一方面,内核在线修改fd_set,导致应用程序必须在下次调用select之前重置这三个fd_set集合。同时,我们也需要在使用前做好备份。
投票:
Pollfd的参数类型pollfd更智能。文件描述符和事件类型是一起定义的。调用后,PollfD结构中的events成员被修改。对于实际检测到的事件,我们设置的事件成员保持不变。再次被调用后,事件将被重置为空。
每次用SELECTPoll进行轮询后,无论其中的事件是否就绪,都需要遍历用户关心的整组事件,所以应用程序检索就绪文件描述符的时间复杂度为O(n)。
epoll:
Epoll管理用户注册事件的方式与上述两种完全不同。它在内核中维护一个事件表,并提供一个独立的系统调用epoll_ctl来添加、删除和修改事件,而不需要从用户空间中重复读取这些事件。
epoll_wait系统调用的events参数负责保存这些就绪事件,使得应用检索就绪文件描述符的时间复杂度达到O(1)。
Poll和epoll_wait使用nfds和maxevents参数来指定要侦听的文件描述符和事件的最大数量,这两个参数都可以达到系统允许打开的文件描述符的最大数量——65535。但是,select允许侦听的文件描述符的最大数量通常是有限的。尽管用户可以修改此限制,但这可能会导致意想不到的后果。
工作模式:
select poll和SELECT Poll都只能在相对低效的LT模式下工作,而epoll可以在高效的ET模式下工作。
内核实现:
Select poll采用轮询方式,每次扫描一整套注册的文件描述符,并将准备好的文件描述符返回给用户程序。检测就绪事件的时间复杂度为O(n)。
Epoll_wait采用回调方法。当内核检测到一个就绪文件描述符时,它将触发回调函数,该函数将文件描述符上的相应事件插入到内核就绪事件队列中。最后,内核在适当的时候将就绪事件队列的内容复制到用户空间。
当有很多活动连接时,epoll_wait的效率不一定比select和poll高,因为回调函数触发太频繁。因此,epoll_wait适用于连接多但活动连接少的情况。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。