使用epoll管理多个客户端连接的TCP服务器
创始人
2024-11-14 23:04:38
0

关于epoll在TCP Server处理多任务(多FD)的解释:

  这段代码展示了如何使用 epoll 在 Linux 上进行高效的 I/O 多路复用,特别是在网络服务器中管理多个客户端连接。我逐步解释代码的工作原理和 epoll 的使用。

概览

  • 创建套接字并监听: 使用 socketbindlisten 创建并启动一个监听套接字。
  • 创建 epoll 实例: 使用 epoll_create 创建一个 epoll 实例。
  • 注册监听套接字到 epoll 使用 epoll_ctl 将监听套接字添加到 epoll 实例中。
  • 等待事件: 使用 epoll_wait 等待文件描述符上的事件。
  • 处理事件: 根据触发的事件类型(新的连接或现有连接上的数据)进行相应处理。

代码详解

套接字初始化
int sockfd_init() {     int sockfd, ret;     sockfd = socket(AF_INET, SOCK_STREAM, 0);     if(sockfd < 0)     {         perror("socket");         return -1;     }      //设置套接字端口复用选项     int opt = 1;     ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));     if(ret < 0)     {         perror("setsockopt");         return -1;     }      struct sockaddr_in seraddr;     seraddr.sin_family = AF_INET;     seraddr.sin_port = htons(8888);     inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);     ret = bind(sockfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr));     if(ret < 0)     {         perror("bind");         return -1;     }      ret = listen(sockfd, 5);     if(ret < 0)     {         perror("listen");         return -1;     }     return sockfd; } 
  • 创建套接字: 使用 socket 创建一个 IPv4 流套接字。
  • 设置端口复用: 使用 setsockopt 设置 SO_REUSEADDR 选项,允许端口复用。
  • 绑定地址和端口: 使用 bind 绑定本地地址和端口。
  • 监听: 使用 listen 使套接字进入监听状态,准备接受连接。
主函数
int main() {     int sockfd, ret, cfd, efd;     struct sockaddr_in cliaddr;     int addrlen =  sizeof(struct sockaddr_in);      //创建集合空间     efd = epoll_create(100);     if(efd < 0)     {         perror("epoll_create");         return -1;     }      //创建监听套接字     sockfd = sockfd_init();     if(sockfd < 0)     {         return -1;     }      //将套接字加入集合     struct epoll_event ev, evs[10];     ev.events = EPOLLIN;     ev.data.fd = sockfd;     epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &ev);      int count;     char buff[1024];     //监听集合中所有的文件描述符     while(1)     {         printf("wait..\n");         count = epoll_wait(efd, evs, 10, -1);         printf("wait  over..\n");         if(count < 0)         {             perror("epoll_wait");             break;         }          for(int i=0; i             int tfd = evs[i].data.fd;             if(tfd == sockfd) //有客户端请求连接             {                 //1、接收客户端                 printf("accept...\n");                 cfd = accept(sockfd, NULL, NULL);                 printf("accept  over...\n");                 if(cfd < 0)                 {                     perror("accept");                     continue;                 }                 //2、cfd加入集合中                 struct epoll_event  ev;                 ev.events = EPOLLIN;                 ev.data.fd = cfd;                 epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev);             }             else //建立连接的客户端发来数据             {                 printf("read...\n");                 ret = read(tfd, buff, 1024);                 printf("read  over...\n");                 if(ret < 0)                 {                     //1、打印错误信息                     perror("read");                     //2、关闭套接字                     close(tfd);                     //3、从集合中移除                     epoll_ctl(efd, EPOLL_CTL_DEL, tfd, NULL);                     continue;                 }                 else if(0 == ret)                 {                     //1、打印错误信息                     printf("tcp broken...\n");                     //2、关闭套接字                     close(tfd);                     //3、从集合中移除                     epoll_ctl(efd, EPOLL_CTL_DEL, tfd, NULL);                     continue;                 }                 buff[ret] = '\0';                 printf("buff: %s\n", buff);             }         }     }      return 0; } 

工作原理

  1. 创建 epoll 实例: 使用 epoll_create 创建一个 epoll 实例,该实例用于管理所有需要监视的文件描述符。
  2. 初始化监听套接字: 调用 sockfd_init 函数创建并配置监听套接字。
  3. 将监听套接字添加到 epoll 使用 epoll_ctl 将监听套接字添加到 epoll 实例中,监视 EPOLLIN 事件(表示有新的连接请求)。
  4. 事件循环:
    • 调用 epoll_wait 等待事件发生。
    • 对于监听套接字上的 EPOLLIN 事件,调用 accept 接受新的连接并将新连接的套接字添加到 epoll 实例中。
    • 对于客户端套接字上的 EPOLLIN 事件,调用 read 读取数据。如果读取失败或连接关闭,将该套接字从 epoll 实例中移除并关闭套接字。
  5. 处理读事件: 当客户端发送数据到服务器,read 函数读取数据并输出到控制台。

epoll 的优点

  • 高效管理大量文件描述符:epoll 可以处理成千上万的文件描述符,性能不会随着文件描述符数量的增加而线性下降。
  • 边缘触发和水平触发:epoll 支持两种事件触发模式,适应不同的应用场景。
  • 避免忙等待: 通过 epoll_wait 等待事件发生,避免了传统 selectpoll 的忙等待问题。

总的来说,这段代码展示了一个使用 epoll 进行高效 I/O 多路复用的小型服务器模型。


定义一个(无头)链表来装载在线客户端:

  在这个示例中,我们将定义一个(无头)链表来管理在线客户端。链表节点将包含客户端的文件描述符和其他必要信息,如客户端的地址信息。我们还会编写相关的函数来管理这个链表,包括添加客户端、移除客户端和遍历客户端列表。

首先,定义链表节点的结构:

#include  #include  #include  #include  #include  #include  #include  #include   // 链表节点结构 struct ClientNode {     int fd;     struct sockaddr_in addr;     struct ClientNode* next; };  // 添加新客户端到链表 void add_client(struct ClientNode** head, int fd, struct sockaddr_in addr) {     struct ClientNode* new_node = (struct ClientNode*)malloc(sizeof(struct ClientNode));     new_node->fd = fd;     new_node->addr = addr;     new_node->next = *head;     *head = new_node; }  // 从链表中移除客户端 void remove_client(struct ClientNode** head, int fd) {     struct ClientNode* temp = *head, *prev = NULL;      if (temp != NULL && temp->fd == fd) {         *head = temp->next;         free(temp);         return;     }      while (temp != NULL && temp->fd != fd) {         prev = temp;         temp = temp->next;     }      if (temp == NULL) return;      prev->next = temp->next;     free(temp); }  // 遍历链表并打印客户端信息 void print_clients(struct ClientNode* head) {     struct ClientNode* current = head;     while (current != NULL) {         char ip[INET_ADDRSTRLEN];         inet_ntop(AF_INET, ¤t->addr.sin_addr, ip, INET_ADDRSTRLEN);         printf("Client FD: %d, IP: %s, Port: %d\n", current->fd, ip, ntohs(current->addr.sin_port));         current = current->next;     } }  // 清理链表 void free_clients(struct ClientNode* head) {     struct ClientNode* tmp;     while (head != NULL) {         tmp = head;         head = head->next;         close(tmp->fd);  // 关闭套接字         free(tmp);     } }  int main() {     int sockfd, ret, cfd, efd;     struct sockaddr_in cliaddr;     socklen_t addrlen = sizeof(struct sockaddr_in);     struct ClientNode* clients = NULL;  // 链表头指针      efd = epoll_create(100);     if (efd < 0) {         perror("epoll_create");         return -1;     }      sockfd = sockfd_init();     if (sockfd < 0) {         return -1;     }      struct epoll_event ev, evs[10];     ev.events = EPOLLIN;     ev.data.fd = sockfd;     epoll_ctl(efd, EPOLL_CTL_ADD, sockfd, &ev);      int count;     char buff[1024];     while (1) {         printf("wait..\n");         count = epoll_wait(efd, evs, 10, -1);         printf("wait over..\n");         if (count < 0) {             perror("epoll_wait");             break;         }          for (int i = 0; i < count; i++) {             int tfd = evs[i].data.fd;             if (tfd == sockfd) {                 printf("accept...\n");                 cfd = accept(sockfd, (struct sockaddr*)&cliaddr, &addrlen);                 printf("accept over...\n");                 if (cfd < 0) {                     perror("accept");                     continue;                 }                 add_client(&clients, cfd, cliaddr);                 ev.events = EPOLLIN;                 ev.data.fd = cfd;                 epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &ev);             } else {                 printf("read...\n");                 ret = read(tfd, buff, 1024);                 printf("read over...\n");                 if (ret < 0) {                     perror("read");                     close(tfd);                     epoll_ctl(efd, EPOLL_CTL_DEL, tfd, NULL);                     remove_client(&clients, tfd);                     continue;                 } else if (ret == 0) {                     printf("tcp broken...\n");                     close(tfd);                     epoll_ctl(efd, EPOLL_CTL_DEL, tfd, NULL);                     remove_client(&clients, tfd);                     continue;                 }                 buff[ret] = '\0';                 printf("buff: %s\n", buff);             }         }          // 打印当前在线的所有客户端         print_clients(clients);     }      free_clients(clients);     return 0; } 

代码解释

  1. 链表节点结构定义

    • struct ClientNode 定义了链表节点结构,每个节点包含一个客户端的文件描述符 (fd)、地址信息 (addr) 和指向下一个节点的指针 (next)。
  2. 添加新客户端到链表

    • add_client 函数创建一个新节点并将其添加到链表头。
  3. 从链表中移除客户端

    • remove_client 函数根据文件描述符从链表中移除相应节点。
  4. 遍历链表并打印客户端信息

    • print_clients 函数遍历链表,打印每个节点中保存的客户端信息。
  5. 清理链表

    • free_clients 函数释放链表中的所有节点并关闭相应的套接字。
  6. 主函数修改

    • 在主函数中,定义了一个链表头指针 clients
    • 当接受到新的客户端连接时,将新客户端添加到链表。
    • 当客户端连接断开时,从链表中移除该客户端。
    • 在每次处理完事件后,调用 print_clients 打印当前在线的所有客户端。

这样,我们就实现了一个使用(无头)链表管理在线客户端的简单服务器模型。


相关内容

热门资讯

透视了解!aa poker辅助... 透视了解!aa poker辅助,本来存在有挂(透视)技巧教程(有挂细节)1、在aa poker辅助a...
透视插件!aapoker怎么开... 透视插件!aapoker怎么开辅助器,aa poker透视软件,详细教程(有挂插件)1、全新机制【a...
透视代打!拱趴大菠萝开挂方法,... 透视代打!拱趴大菠萝开挂方法,大菠萝免费辅助,解说技巧(有挂脚本)1、金币登录送、破产送、升级送、活...
透视免费!wepoker私人局... 透视免费!wepoker私人局俱乐部怎么进,一直有挂(透视)详细教程(有挂规律)1、wepoker私...
透视挂透视!aapoker怎么... 透视挂透视!aapoker怎么选牌,aapoker怎么设置提高好牌几率,大神讲解(有挂辅助);1、构...
透视代打!约局吧透视挂下载,拱... 透视代打!约局吧透视挂下载,拱趴大菠萝有挂吗,必胜教程(有挂教程)1、让任何用户在无需约局吧透视挂下...
透视好牌!wepoker亲友圈... 透视好牌!wepoker亲友圈有用吗,本然是有挂(透视)实用技巧(有挂细节)wepoker亲友圈有用...
透视玄学!aapoker脚本,... 透视玄学!aapoker脚本,aapoker透视插件,德州论坛(有挂揭秘)1、aapoker透视插件...
透视挂!哈糖大菠萝万能挂,哈糖... 透视挂!哈糖大菠萝万能挂,哈糖大菠萝辅助器,详细教程(有挂规律)1.哈糖大菠萝万能挂 ai辅助创建新...
透视能赢!wepoker私人局... 透视能赢!wepoker私人局辅助挂,一向真的是有挂(透视)曝光教程(有挂脚本);1、每一步都需要思...