epoll实现并发服务器

打印 上一主题 下一主题

主题 1016|帖子 1016|积分 3048

1、epoll是什么

epoll 是 Linux 上一种高性能的多路复用机制,用于监视大量文件形貌符并在它们停当时关照应用程 序。它是在 select 和 poll 的底子上进一步优化和改进而来的。
2、epoll的特点

1.没有文件形貌符数量限制:
与 select 和 poll 差别,epoll 接纳了基于事件的停当关照机制, 没有预界说的文件形貌符数量限制,可以支持更大规模的并发连接。
2.高效的事件关照:
epoll 使用了内核和用户空间共享的事件数据结构,将文件形貌符的事件注册到内 核空间,当事件停当时,内核直接将停当的事件关照给用户空间,制止了每次调用都须要遍历整个文件 形貌符数组的性能开销。
3.分离的停当事件集合:
epoll 将停当的事件从内核空间复制到用户空间,形成一个分离的停当事件集 合,用户可以直接遍历这个集合来处置惩罚停当的事件,而不须要遍历整个文件形貌符数组。
4.支持边沿触发和程度触发:
epoll 提供了两种模式来处置惩罚事件,一种是边沿触发模式 (EPOLLET),只在状态发生变革时关照应用程序,另一种是程度触发模式(默认),在事件停当期间一直关照应用程序。
5.更低的内存拷贝开销:
epoll 使用内存映射技能,制止了每次调用都须要将事件数据从内核复制到用 户空间的开销,从而淘汰了系统调用的次数和内存拷贝的开销。
6.支持较高精度的超时控制:
与 poll 差别,epoll 的超时参数以毫秒和纳秒为单位,提供了较高精 度的超时控制。
总体来说:epoll 在性能上相比于 select 和 poll 有较大的上风,特别适用于高并发场景下的网 络编程。它的高效事件停当关照、支持大规模并发连接、较低的内存拷贝开销以及较高的超时精度,使 得它成为开辟高性能服务器和网络应用的首选机制。
3、epoll实现高并发原理

在Linux内核中,epoll使用红黑树作为其重要的数据结构,用于维护注册的文件形貌符集合。红黑树是 一种自平衡的二叉搜索树,具有较快的插入、删除和搜索操作的时间复杂度。通过使用红黑树,epoll能 够高效地检索和管理大量的文件形貌符。
当文件形貌符发生事件时,epoll通过红黑树的查找操作快速定位到相应的结点,并触发注册的回调函数 举行事件处置惩罚。使用红黑树的原因是它可以或许保持良好的平衡性,保证搜索、插入和删除操作的最坏情况时间复杂度为O(log n),从而保证了epoll的高性能和可伸缩性。
总结来说,epoll是利用红黑树作为其底层数据结构实现的,这使得它在处置惩罚大量并发连接时可以或许提供高 效的事件关照机制。
4、epoll相关函数

1、epoll_create

   (1)epoll_create函数原型:
int epoll_create(int size);
功能:epoll_create 函数创建一个 epoll 实例,并返回一个文件形貌符,用于标识该 epoll 实
例。
  2、epoll_ctl

   (2)epoll_ctl函数原型:
  int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  功能:操作epoll实例
  3、epoll_wait

   (3)epoll_wait()函数原型:
  int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  功能:等候epoll 文件形貌符的IO事件
  5、epoll实现并发服务器的步调

当使用epoll实现并发服务器时,通常的步调包罗以下几个重要环节:
1.创建socket:使用socket函数创建一个监听套接字,用于接受客户端的连接请求。
2.绑定socket:使用bind函数将监听套接字绑定到一个特定的IP所在和端口。
3.监听连接:使用listen函数开始监听连接请求,指定服务器可接受的最大连接数。
4.创建epoll实例:使用epoll_create函数创建一个epoll实例,返回一个文件形貌符。
5.将监听套接字添加到epoll实例:使用epoll_ctl函数将监听套接字添加到epoll实例中,并注册对 读事件的关注(默以为程度触发)。
6.进入事件循环:循环调用epoll_wait函数来等候事件的发生,该函数会壅闭当进步程直至有事件发 生。一旦有事件发生,它将返回一个停当事件的列表。
7.处置惩罚停当事件:遍历停当事件列表,对每个事件举行处置惩罚。根据事件类型,可以举行接受连接、读取 数据、发送数据或关闭连接等操作。
8.根据须要添加或删除文件形貌符:在处置惩罚完一个事件后,可以根据须要使用epoll_ctl函数动态地添 加或删除文件形貌符,以便继续监听其他事件。
9.重复步调6-8:继续循环实行步调6-8,处置惩罚新的停当事件,直到服务器主动关闭或出现错误条件为 止。
6、使用epoll实现并发服务器的代码

1、net.h

  1. #ifndef _NET_H_
  2. #define _NET_H_
  3. //定义枚举,表示返回值
  4. enum VALUE_RET
  5. {
  6.         QUIT = -9,
  7.         SOCK_ERROR = -8,
  8.         BIND_ERROR,
  9.         LISTEN_ERROR,
  10.     ACCEPT_ERROR,
  11.         SEND_ERROR,
  12.         RECV_ERROR,
  13.         CONNECT_ERROR,
  14.         ERROR,
  15.         OK
  16. };
  17. //添加所需的头文件以及函数声明
  18. #include <stdio.h>
  19. #include <sys/types.h>
  20. #include <sys/socket.h>
  21. #include <string.h>
  22. #include <netinet/in.h>
  23. #include <arpa/inet.h>
  24. #include <stdlib.h>
  25. #include <strings.h>
  26. #include <unistd.h>
  27. #include <sys/stat.h>
  28. #include <fcntl.h>
  29. int server_initial_func(const char *IP, const char *Port);
  30. int client_initial_func(const char *IP, const char *Port);
  31. int server_com_func(int newfd);
  32. void parseString_func(char *buf, char **result);
  33. #endif
复制代码
2、client.c

  1. #include "./net.h"
  2. int client_initial_func(const char *IP, const char *Port)
  3. {
  4.         //创建套接字
  5.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  6.         if(sockfd < 0)
  7.         {
  8.                 perror("socket error");
  9.                 return SOCK_ERROR;
  10.         }
  11.         printf("socket ok!\n");
  12.         //定义地址信息结构,存储服务器的IP地址和端口号
  13.         struct sockaddr_in serverAddr;
  14.         //清空
  15.         memset(&serverAddr, '\0', sizeof(serverAddr));
  16.         //赋值
  17.         serverAddr.sin_family = AF_INET;
  18.         serverAddr.sin_port = htons((short)atoi(Port));
  19.         serverAddr.sin_addr.s_addr = inet_addr(IP);
  20.        
  21.         //发起连接请求
  22.         int ret = connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
  23.         if(ret < 0)
  24.         {
  25.                 perror("connect error");
  26.                 return CONNECT_ERROR;
  27.         }
  28.         printf("connect ok!\n");
  29.         return sockfd;
  30. }
  31. int client_com_func(int sockfd)
  32. {
  33.         int send_count = 0, recv_count = 0;
  34.         int wr_count = 0, rd_count = 0;
  35.     //请求客户端,完成业务办理
  36.         char func[20] = {0};
  37.         char data[20] = {0};
  38.         char buf[1024] = {0};
  39.         char Data[1024] = {0};
  40.         printf("请输入办理的业务和数据:");
  41.         scanf("%s%*c", func);
  42.         scanf("%s", data);
  43.        
  44.         //将func和data写入到buf中,发送给服务器
  45.         sprintf(buf, "%s#%s",func, data);
  46.        
  47.         send_count = send(sockfd, buf, strlen(buf), 0);
  48.         if(send_count < 0)
  49.         {
  50.                 perror("send func+data to server error");
  51.                 return ERROR;
  52.         }
  53.         printf("send func+data to server ok!\n");
  54.         //挑选业务执行的空间
  55.         if(0 == strncasecmp("download", func, 8))
  56.         {
  57.                 //下载
  58.                 recv_count = recv(sockfd, Data, sizeof(Data), 0);
  59.                 if(recv_count < 0){
  60.                         perror("recv error");
  61.                         return ERROR;
  62.                 }
  63.                 //解析Data
  64.                 char *result[4] = {NULL};
  65.                 parseString_func(Data, result);
  66.                 printf("服务器回应:业务编号%s 业务数据%s\n",result[0], result[1]);
  67.                 if(0 == strncasecmp("FILESIZE", result[0], 8))
  68.                 {
  69.                         //可以接收result[1]作为所需下载资源的大小
  70.                         long filesize = atol(result[1]);
  71.                         printf("服务器发送的资源大小为:%ld个字节!\n",filesize);
  72.                        
  73.                         //只写方式打开下载的文件
  74.                         int fw = open(data, O_WRONLY | O_CREAT | O_TRUNC, 0664);
  75.                         if(fw < 0){
  76.                                 perror("open error");
  77.                                 return ERROR;
  78.                         }
  79.                         printf("open_write %s ok!\n",data);
  80.                         char Message[512] = {0};
  81.                         while(1)
  82.                         {
  83.                                 //判断可读字节数是否为0
  84.                                 if(0 == filesize)
  85.                                 {
  86.                                         printf("下载成功!\n");
  87.                                         break;
  88.                                 }
  89.                                 memset(Message, 0, sizeof(Message));
  90.                                 //接收
  91.                                 recv_count = recv(sockfd, Message, sizeof(Message), 0);
  92.                                 if(recv_count < 0){
  93.                                         perror("recv error");
  94.                                         return ERROR;
  95.                                 }
  96.                                 else{
  97.                                         printf("成功接收服务器发送%d个字节,即将写入...\n", recv_count);
  98.                                         //写入
  99.                                         wr_count = write(fw, Message, recv_count);
  100.                                         if(wr_count < 0)
  101.                                         {
  102.                                                 perror("write error");
  103.                                                 return ERROR;
  104.                                         }
  105.                                         else if(0 == wr_count){
  106.                                                 printf("nothing was be written...\n");
  107.                                                 return ERROR;
  108.                                         }
  109.                                         else{
  110.                                                 printf("本次写入到文件成功,共计写入%d个字节!\n",wr_count);
  111.                                         }
  112.                                     //更新filesize的可读字节数
  113.                                         filesize -= wr_count;
  114.                                         printf("剩余%ld个字节未被读取...\n", filesize);
  115.                                 }
  116.                                 printf("**************************************\n");       
  117.                         }
  118.                
  119.                 }
  120.         }
  121.         return OK;
  122. }
  123. //void parseString_func(char buf[], char *result[]);
  124. void parseString_func(char *buf, char **result)//download#1.txt
  125. {
  126.         //将buf的首地址存储在result[0]
  127.         int index = 0;
  128.         result[index++] = buf;
  129.         while(*buf)
  130.         {
  131.                 //判断*buf是否为'#'
  132.                 if('#' == *buf)
  133.                 {
  134.                         *buf = '\0';
  135.                         buf++;
  136.                         result[index++] = buf;
  137.                 }
  138.                 else
  139.                 {
  140.                         buf++;       
  141.                 }
  142.         }
  143.         return;
  144. }
  145. int main(int argc, const char *argv[])
  146. {
  147.         //搭建TCP客户端
  148.        
  149.     //客户端端初始化
  150.         int sockfd = client_initial_func(argv[1], argv[2]);
  151.         if(sockfd < 0)
  152.         {
  153.                 return ERROR;
  154.         }
  155.         printf("server_initial success!\n");
  156.         //通信
  157.         while(1)
  158.         {
  159.                 if(client_com_func(sockfd) < 0)
  160.                 {
  161.                         break;
  162.                 }
  163.         }
  164.         //关闭套接字
  165.         close(sockfd);
  166.         return 0;
  167. }
复制代码
3、server.c

  1. #include "./net.h"
  2. int server_initial_func(const char *IP, const char *Port)
  3. {
  4.         //创建套接字
  5.         int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  6.         if(sockfd < 0)
  7.         {
  8.                 perror("socket error");
  9.                 return SOCK_ERROR;
  10.         }
  11.         printf("socket ok!\n");
  12.         //定义地址信息结构,存储服务器的IP地址和端口号
  13.         struct sockaddr_in serverAddr;
  14.         //清空
  15.         memset(&serverAddr, '\0', sizeof(serverAddr));
  16.         //赋值
  17.         serverAddr.sin_family = AF_INET;
  18.         serverAddr.sin_port = htons((short)atoi(Port));
  19.         serverAddr.sin_addr.s_addr = inet_addr(IP);
  20.        
  21.         //绑定IP地址和端口
  22.         if(bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
  23.         {
  24.                 perror("bind error");
  25.                 return BIND_ERROR;
  26.         }
  27.         printf("bind IP+Port ok!\n");
  28.         //创建监听队列
  29.         if(listen(sockfd, 5) < 0)
  30.         {
  31.                 perror("listen error");
  32.                 return LISTEN_ERROR;
  33.         }
  34.         printf("listening......\n");
  35.         return sockfd;
  36. }
  37. int server_com_func(int newfd)
  38. {
  39.         //处理客户端的业务
  40.         char buf[1024] = {0};
  41.         char Data[1024] = {0};
  42.         int send_count = 0, recv_count = 0;
  43.         int wr_count = 0, rd_count = 0;
  44.         int ret = recv(newfd, buf, sizeof(buf), 0);
  45.         if(ret < 0){
  46.                 perror("recv function+data error");
  47.                 return ERROR;
  48.         }
  49.         printf("客户端业务+数据:%s\n",buf);
  50.         //解析buf,拿到业务编号以及数据
  51.         char *result[4] = {NULL};
  52.         parseString_func(buf, result);
  53.         //解析结果呈现
  54.         printf("业务:%s\t业务数据:%s\n", result[0],result[1]);
  55.         //根据业务进入不同操作空间
  56.         if(0 == strncasecmp("logon", result[0], 5))
  57.         {
  58.                 //登陆
  59.                 printf("登陆中...\n");
  60.         }
  61.         else if(0 == strncasecmp("register", result[0], 8))
  62.         {
  63.                 //注册
  64.                 printf("注册中...\n");
  65.         }
  66.         else if(0 == strncasecmp("download", result[0], 8))
  67.         {
  68.                 //上传
  69.                 //获取客户端指定下载文件的大小
  70.                 struct stat MyStat;
  71.                 long filesize = 0;
  72.                 if(lstat(result[1], &MyStat) < 0)
  73.                 {
  74.                         perror("lstat error");
  75.                         return ERROR;
  76.                 }
  77.                 printf("测试%s文件属性OK!\n", result[1]);
  78.                 //获取大小,赋值给filesize
  79.                 filesize = MyStat.st_size;
  80.                 printf("成功获取的文件%s大小为:%ld个字节\n", \
  81.                                 result[1], filesize);
  82.                 //将filesize发送给客户端(带上业务编号:FILESIZE)
  83.                 memset(Data, '\0', sizeof(Data));
  84.                 sprintf(Data, "FILESIZE#%ld", filesize);
  85.                 //发送Data的内容(发送有效字符个数-->15个)
  86.                 send_count = send(newfd, Data, strlen(Data), 0);
  87.                 //send_count = send(newfd, Data, sizeof(Data), 0);
  88.                 if(send_count < 0)
  89.                 {
  90.                         perror("send filesize to client error");
  91.                         return ERROR;
  92.                 }
  93.                 printf("send filesize to client success!\n");
  94.                 //延时1秒
  95.                 sleep(1);
  96.                 //只读打开result[1]
  97.                 int fr = open(result[1], O_RDONLY);
  98.                 if(fr < 0){
  99.                         perror("open_read error");
  100.                         return ERROR;
  101.                 }
  102.                 printf("open_read ok!\n");
  103.                 //循环,边读边发
  104.                 char Message[512] = {0};
  105.                 while(1)
  106.                 {
  107.                         memset(Message, 0,sizeof(Message));
  108.                         //读取
  109.                         rd_count = read(fr, Message, sizeof(Message));
  110.                         if(rd_count < 0){
  111.                                 perror("read error");
  112.                                 return ERROR;
  113.                         }
  114.                         else if(0 == rd_count){
  115.                                 //读取到文件末尾
  116.                                 printf("上传成功!\n");
  117.                                 break;
  118.                         }
  119.                         else{
  120.                                 printf("本次读取成功,共计读取%d个字节!\n",rd_count);
  121.                                 //发送读取的字节内容给客户端
  122.                                 send_count = send(newfd, Message, rd_count, 0);
  123.                                 if(send_count < 0)
  124.                                 {
  125.                                         perror("send data to client error");
  126.                                         return ERROR;
  127.                                 }
  128.                                 printf("send ok! 共计发送%d个字节!\n",send_count);
  129.                         }
  130.                         printf("*************************************\n");
  131.                 }
  132.         }
  133.         else
  134.         {
  135.                 printf("业务无法响应~\n");
  136.                 return ERROR;
  137.         }
  138.         return OK;
  139. }
  140. //void parseString_func(char buf[], char *result[]);
  141. void parseString_func(char *buf, char **result)//download#1.txt
  142. {
  143.         //将buf的首地址存储在result[0]
  144.         int index = 0;
  145.         result[index++] = buf;
  146.         while(*buf)
  147.         {
  148.                 //判断*buf是否为'#'
  149.                 if('#' == *buf)
  150.                 {
  151.                         *buf = '\0';
  152.                         buf++;
  153.                         result[index++] = buf;
  154.                 }
  155.                 else
  156.                 {
  157.                         buf++;       
  158.                 }
  159.         }
  160.         return;
  161. }
  162. int main(int argc, const char *argv[])
  163. {
  164.         //搭建TCP服务器
  165.        
  166.     //服务器端初始化
  167.         int listenfd = server_initial_func(argv[1], argv[2]);
  168.         if(listenfd < 0)
  169.         {
  170.                 return ERROR;
  171.         }
  172.         printf("server_initial success!\n");
  173.         //epoll实现并发服务器
  174.         int epfd = epoll_create(12);
  175.     if(-1 == epfd)
  176.     {
  177.         perror("epoll_create error");
  178.         return -1;
  179.     }
  180.     printf("epoll_create ok!\n");
  181.    
  182.     //5、将监听套接字加入到epoll实例中(注册在红黑树中)
  183.     //定义epoll事件结构体变量
  184.     struct epoll_event event;
  185.     //赋值
  186.     event.events = EPOLLIN;//EPOLLIN表示对于其可读事件感兴趣  默认为水平触发
  187.     event.data.fd = listenfd;
  188.     if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event) == -1)
  189.     {
  190.         perror("add listenfd error");
  191.         return -1;
  192.     }
  193.     printf("add lisenfd ok!");
  194.    
  195.     //6、进入事件循环
  196.     //定义数组,存储就绪的所有描述符
  197.     struct epoll_event events[MAXEVENTS];
  198.    
  199.     //定义保存连接成功的新的客户端的地址信息
  200.     struct sockaddr_in newClientAddr;
  201.     memset(&newClientAddr, 0, sizeof(newClientAddr));
  202.     int len_NewClient = sizeof(newClientAddr);
  203.    
  204.     int newfd;//存储每一连接成功的客户端对应的套接字
  205.    
  206.     while(1)
  207.     {
  208.         //等待就绪事件发生
  209.         int numEventsReady = epoll_wait(epfd, events, MAXEVENTS, -1);//-1代表阻塞等待
  210.         if(-1 == numEventsReady)
  211.         {
  212.             perror("epoll_wait error");
  213.             return -1;
  214.         }
  215.         printf("有事件就绪,准备处理...\n");
  216.         
  217.         //根据返回值numEventsReady去遍历数组events
  218.         int i;
  219.         for(i=0; i<numEventsReady; i++)
  220.         {
  221.             //判断事件类型(监听套接字就绪or通信套接字就绪)
  222.             if(listenfd == events[i].data.fd)
  223.             {
  224.                 //有新的客户端发来了连接请求
  225.                 //获取与新的客户端通信的套接字newfd;
  226.                 newfd = accept(events[i].data.fd, (struct sockaddr *)&newClientAddr, &len_NewClient);
  227.                 if(newfd < 0)
  228.                 {
  229.                     perror("accept new client error");
  230.                     return -1;
  231.                 }
  232.                 printf("新客户端newfd = %d连接服务器成功, IP = %s\tPort = %d\n", \
  233.                       newfd, inet_ntoa(newClientAddr.sin_addr), ntohs(newClientAddr.sin_port));
  234.                
  235.                 //将新的newfd注册
  236.                 event.events = EPOLLIN;//EPOLLIN表示对于其可读事件感兴趣  默认为水平触发
  237.                 event.data.fd = newfd;
  238.                 if(epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &event) == -1)
  239.                 {
  240.                     perror("add newfd error");
  241.                     return -1;
  242.                 }
  243.                 printf("注册newfd = %d 到epoll实力中 ok!\n", newfd);
  244.             }
  245.             else
  246.             {
  247.                 //有客户端需要业务处理
  248.                                 printf("客户端newfd = %d发来了业务,请求处理~\n", events[i].data.fd);
  249.                 if(server_com_func(events[i].data.fd) < 0)
  250.                 {
  251.                     //将该客户端的套接字从epoll实例(红黑树中)删除
  252.                     if(epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &events[i]) == -1)
  253.                     {
  254.                         perror("delete error");
  255.                         return -1;
  256.                     }
  257.                     printf("从epoll实例中删除fd = %d的客户端套接字成功!\n", events[i].data.fd);
  258.                     //关闭套接字
  259.                     close(events[i].data.fd);
  260.                 }
  261.             }
  262.         }
  263.     }
  264.        
  265.     return 0;
  266. }
  267.        
复制代码
恁可以在Linux下建立一个epoll文件,再创建两个子文件。一个为server文件夹,存放server.c和net.h;另一个为client文件夹,存放client.c和net.h。
在server文件夹下,gcc server.c -o s,天生可实行文件s,再运行:./s 127.0.0.1 6666
127.0.0.1为ip 6666可以在1024~49151里随意选择。
在client文件夹下,gcc client.c -o c,天生可实行文件c,再运行:./c 127.0.0.1 6666
127.0.0.1为ip 6666可以在1024~49151里随意选择。
可以运行多个客户端,实现并发服务器。上面代码实现了文件的下载!
在客户端中使用download 文件名,下载server文件夹内已有的文件。
如图




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表