linux多路转接epoll---服务器代码

一、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 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 #include<sys/epoll.h>
  6 #include<sys/types.h>
  7 #include<sys/socket.h>
  8 #include<arpa/inet.h>
  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;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 }

 

四、运行截图
1、服务器端运行截图

 

 

2、客户端A,B运行截图
 
 
注释一:客户端没有写代码,使用的是nc命令来测试的
nc+ip地址+port端口号---可以用来模拟客户端
192.128.50.129是我本机的ip,127.0.0.1是测试ip
 
注释二:使用vim -r filename可以恢复,因为误操作导致没有保存的vim文档
 
 
 
一入编程深似海,多学多查多动手
 
 
 
posted @ 2021-02-05 03:12  阿斯顿之意  阅读(164)  评论(0)    收藏  举报