一、epoll多路转接简介
1、什么是多路转接:举个例子—如果有很多人要联系老板,需要先联系秘书,然后每隔一段时间,秘书就告知老板这段时间内有多少人联系了他,以及这些联系人的信息。
2、多路转接的三种实现方式的优缺点(注释:多路转接的三种实现方式的优缺点参考于:https://blog.csdn.net/angeldg/article/details/107203052)
(1)select多路转接:
优点:遵循posix标准,跨平台移植性比较好
缺点:
文件描述符的最大数量有限制(最大1024,可以进行修改);
通过轮询遍历判断实现的,性能会随着文件描述符的增多而下降;
只返回就绪的文件描述符集合,需要进行遍历才能得知哪个文件描述符就绪了哪个事件
(2)poll多路转接:
优点:
简化了select中三种事件的操作流程
文件描述符的最大数量没有限制
缺点:
通过轮询遍历判断实现的,性能会随着文件描述符的增多而下降
平台移植性差
(3)epoll多路转接
优点:
文件描述符的最大数量没有限制
监控使用一步阻塞操作完成,性能不会随着文件描述符的增多而下降(前半句我也不太理解,就直接照搬结论了)
返回就绪事件的文件描述符,以及每个文件描述符的信息。
缺点:
跨平台移植性差
(4)三种多路转接方式的简单对比
二、epoll多路转接的函数原型
1、int epoll_creat(int size);
作用:生成一个epoll专用的文件描述符
size:epoll上能关注的最大描述符数(如果不够用,函数会自动扩展)
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
作用:用于控制某个epoll文件描述符事件,可以注册、修改、删除
epfd:epoll_create生成的epoll专用描述符—即epoll的返回值
op:
EPOLL_CTL_ADD —注册
EPOLL_CTL_MOD —修改
EPOLL_CTL_DEL —删除
fd:关联的文件描述符
event:告诉内核要监听什么事件
EPOLLIN—读
EPOLLOUT—写
EPOLLERR—异常
相关结构体介绍
struct epoll_event{
uint32_t events;
epoll_data_t data;//联合体
}
typedef union epoll_data{
void *ptr;//描述更多的信息,用指针,
int fd;//只描述单一的信息,
uint32_t u32;
uint64_t u64;
}epoll_data_t;
3、int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
作用:等待IO事件发生—可以设置阻塞的函数,功能对应select/poll函数
epfd:要检测的句柄
events:用于回传待处理事件的数组
maxevents:告诉内核这个events的大小
timeout:为超时时间
-1:永久阻塞
0:立即返回
0:阻塞时长,单位毫秒
三、epoll多路转接服务器代码
1 #include2 #include 3 #include 4 #include<string.h> 5 #include 6 #include 7 #include 8 #include 9 10 11 int main(int argc,const char* argv[]) 12 { 13 if(argc<2) 14 { 15 printf("please input:./a.out port\n"); 16 return -1; 17 } 18 int port=atoi(argv[1]); 19 //1、创建套接字 20 int lfd=socket(AF_INET,SOCK_STREAM,0);//AF_INET表示ipv4协议,SOCK_STREAM使用tcp通信,0使用对应的默认协议 21 if(lfd==-1)//失败返回-1,系统会检测到errno错误 22 { 23 perror("socket error"); 24 exit(1);//在main函数中,exit(1)等价于return 1 25 } 26 //2、绑定 27 struct sockaddr_in server;//存储使用的协议,port和ip 28 server.sin_family=AF_INET;//ipv4协议 29 server.sin_port=htons(port);//htons将端口号,从本机的小端顺序转化为网络的大端顺序 30 server.sin_addr.s_addr=htonl(INADDR_ANY);//同理,INADDR_ANY表示本机任意可用IP,值为0,也可用在等式右边直接写一个0 31 int ret=bind(lfd,(struct sockaddr*)&server,sizeof(server)); 32 if(ret==-1) 33 { 34 perror("bind error"); 35 exit(1); 36 } 37 38 //3、监听 39 listen(lfd,128);//128同时监听的最大客户端数量 40 41 //4、epoll_create创建红黑树,返回根节点的文件描述符 42 int epfd=epoll_create(3333);//3333创建红黑树的节点数,如果全部用完,系统会自动分配更多的节点 43 44 //5、初始化红黑树,将用于监听的文件描述符lfd挂在epol树上 45 struct epoll_event ev; 46 ev.events=EPOLLIN;//默认为水平触发模式,还有边缘触发模式和边缘非阻塞触发模式 47 ev.data.fd=lfd; 48 epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev); 49 50 struct epoll_event events[3333];//创建结构体数组,用于存储红黑树的节点信息 51 //6、循环中委托内核检测事件,epoll_wait 52 while(1) 53 { 54 //epoll_wait最后一个参数设置为-1--永久阻塞,当有新的连接或者通信,则放弃阻塞 55 int num=epoll_wait(epfd,events,sizeof(events)/sizeof(events[0]),-1); 56 if(num==-1)//失败返回-1,并设置errno错误 57 { 58 perror("epoll_wait error"); 59 exit(1); 60 } 61 for(int i=0;i //成功,num返回准备好的文件描述符数量-官方解释 62 //num返回二叉树上文件描述符的数量-自己的理解 63 {//循环遍历二叉树节点 64 int fd=events[i].data.fd; 65 66 if(fd==lfd) //7、如果有新的连接 67 { 68 //accpet接收连接请求,此处的accpet不阻塞,已经监听成功 69 struct sockaddr_in cli_addr;//可以定义在循环外,为了偷懒定义这了 70 socklen_t cli_len=sizeof(cli_addr); 71 int cfd=accept(fd,(struct sockaddr*)&cli_addr,&cli_len); 72 if(cfd==-1) 73 { 74 perror("accept error"); 75 exit(1); 76 } 77 //将新建立连接文件描述符cfd挂在树上,使用epoll_ctl 78 struct epoll_event event; 79 event.events=EPOLLIN; 80 event.data.fd=cfd; 81 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event); 82 83 //打印新建立简介的客户端ip和端口port 84 char ip[64]={0}; 85 printf("New client IP:%s,port:%d\n", 86 inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr,ip,sizeof(ip)), 87 ntohs(cli_addr.sin_port)); 88 89 90 } 91 else //8、如果有新的通信 92 { 93 //读数据 94 char buf[1024]={0}; 95 int len=recv(events[i].data.fd,buf,sizeof(buf),0); 96 if(len==-1)//读错误 97 { 98 perror("recv error"); 99 exit(1); 100 } 101 if(len==0)//客户端关闭了连接 102 { 103 close(fd); 104 //将该客户端的文件描述符从二叉树上删除 105 epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);//NULL 106 printf("client disconnect\n"); 107 }else 108 { 109 printf("recv buf:%s\n",buf); 110 //写数据 111 send(events[i].data.fd,buf,sizeof(buf),0); 112 } 113 114 } 115 116 } 117 } 118 //关闭文件描述符 119 close(lfd); 120 return 0; 121 } ;i++)
四、运行截图
1、服务器端运行截图
2、客户端A,B运行截图
注释一:客户端没有写代码,使用的是nc命令来测试的
nc+ip地址+port端口号—可以用来模拟客户端
192.128.50.129是我本机的ip,127.0.0.1是测试ip
注释二:使用vim -r filename可以恢复,因为误操作导致没有保存的vim文档
一入编程深似海,多学多查多动手
Original: https://www.cnblogs.com/asdzy/p/14375769.html
Author: 阿斯顿之意
Title: linux多路转接epoll—服务器代码
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/683294/
转载文章受原作者版权保护。转载请注明原作者出处!