tcp使用epoll进行实现并发

本文详细介绍了如何利用Epoll的ET和LT模式优化TCP服务器的并发管理,通过Epoll创建、添加事件、等待事件的方式处理连接请求和数据传输。文中展示了将监听套接字和连接套接字的事件处理分离,使用回调函数实现更灵活的事件处理,进一步讨论了Reactor模型的应用,以及如何通过双Epoll优化连接处理效率。

tcp使用epoll进行实现并发

tcp 服务器编写的步骤都是很熟悉的了。tcp由于每一个新的连接都会新建一个socket和客户端进行通信,但是新建立的连接在很多次之后就会管理就会出现问题,这个时候就可以使用epoll进行管理。
epoll是一种多路转接io,相比selete和poll在管理大量描述符的时候优势很明显。具体的优势我们后面可以慢慢说。
epoll的流程, 创建epoll描述符–> 添加事件–> wait;

 int epoll_create (int __size)
 __size: 这个值在早期时候用于确定返回列表的预留长度,现在由于返回的内容放置在一个双向链表中,这个		  实际上已经没什么作用了
 return 返回新创建的epoll描述符
extern int epoll_ctl (int __epfd, int __op, int __fd,
		      struct epoll_event *__event) 
__epfd: epoll的文件描述符
_op	:	EPOLL_CTL_ADD 添加监视节点, EPOLL_CTL_DEL 删除监视 , EPOLL_CTL_MOD 修改监视
__fd: 所关注的文件描述符
__event:关心的事件节点填充
    
typedef union epoll_data
{
   
   
  void *ptr; //预留的指针
  int fd;	//一般设置为当前的文件描述符
  uint32_t u32; //一般不用
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
   
   
  uint32_t events;	/* Epoll events */  //在这里设置所关心的事件
  epoll_data_t data;	/* User data variable */
} 
extern int epoll_wait (int __epfd, struct epoll_event *__events,
		       int __maxevents, int __timeout);
参数分别为: epoll文件描述符,返回事件存储的位置,数组的最大长度。超时时间, -1 阻塞等待有事件就返回,0不管有无事件都直接返回 , 大于0, 等待·超时时间。
return 小于零 出错, 大于0 事件发生的个数

epoll的两种模式:
ET:边沿触发, 只触发一次 , 只有文件描述符从不可读变为可读的时候才会被触发, EPOLLET 这个宏用于设置ET模式
LT:水平触发, 一直触发
这两种模式都是在调用epol_ctl添加事件之前进行设置的,epoll默认是使用LT模式,在高版本中的系统中没用提供设置LT模式的宏。

这个触发是什么意思呢?

假如说使用epoll监控tcp的listen套接字并且监控可读事件(可读事件代表有新的连接建立),如果我们设置LT模式,当新的连接到来的时候epoll的返回的事件数组中肯定会有listen描述符告诉我们可读事件发生了。但是此时如果我们没用获取新的连接,那么返回的文件描述符中还会有listen描述符。
如果是ET模式那么在返回的事件列表中就会没有。

可以看下述代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>


int main(int argc, char *argv[]){
   
   
    int socket_fd  = socket(AF_INET, SOCK_STREAM, 0); //创建流套接字
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET; //使用IPV4
    addr.sin_port = htons(8888); //绑定端口 8888
    addr.sin_addr.s_addr = INADDR_ANY; //绑定本机的所有网卡

    if(bind(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
   
   
        exit(-1);
    }

    /*全连接队列为 5 或者 全连接和半连接和为5 和版本有关*/
    if(listen(socket_fd, 5) < 0){
   
   
        exit(-1);        
    }

    int epfd = epoll_create(1);//只有0和1的区别,和之前epool实现有关系
    struct epoll_event ev, event[1024];
    ev.events = EPOLLIN; //监控可读事件
    ev.data.fd = socket_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, socket_fd, &ev); //添加事件

    while(1){
   
   
        int nready = epoll_wait(epfd, event, 1024, -1); //fd和过期时间, -1 相当于有数据就返回, 
        if(nready < -1){
   
                                   // 0那么就直接返回,  返回值为 正数字,
            break;                                      // 那么就是所有的事件
        }

        char buff[1024] = {
   
   0};
        for(int i = 0 ; i < nready; i++){
   
    //遍历返回事件
            if(event[i].data.fd == socket_fd){
   
    //如果是监听描述符那么就建立新的连接
                struct sockaddr_in addrs_client;
                int clinet_size = sizeof(struct sockaddr_in);
                int clientfd = accept(socket_fd, (struct sockaddr *)&addrs_client, (socklen_t*)&clinet_size);
                if(clientfd < 0){
   
    //连接建立失败
                    continue;
                }
                    //默认是LT , 没有定义EPOLLT, 设置为LT模式
                ev.events = EPOLLIN;   //EPPOLL 默认是LT, LT是水平触发,有数据就会一直触发。ET是边缘触发,是从没有数据变成有数据才 
               // ev.events = EPOLLIN | EPOLLET; // EPOLLET 设置为ET模式
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev); //将获取到的新连接加入到epoll中
            }else{
   
   
                int clientfd = event[i].data.fd; 	//获取一个连接
                int ret = recv(clientfd, buff, 1024, 0); //非阻塞如果没有数据那么就返回-1
                if(ret < 0){
   
   //现在是不可能-1的,但是如果一直while读,读空了就会返回-1
                    if(errno == EAGAIN || errno == EWOULDBLOCK){
   
   
        
<think>好的,用户需要查找使用epoll实现TCP并发服务器的代码示例。首先,我需要回忆一下epoll的相关知识。epoll是Linux下高效的I/O多路复用机制,适合处理大量并发连接。用户提供的引用中有几个相关的例子,比如引用1提到使用epoll()处理多客户连接的TCP服务器,引用4和5也涉及epoll使用。需要综合这些信息来构建一个示例代码。 接下来,我需要确保代码结构正确。通常,epoll服务器的步骤包括:创建socket,绑定地址,监听,创建epoll实例,注册监听事件,然后进入事件循环,处理epoll_wait返回的事件。在事件处理中,需要区分新的连接和已连接的数据读写。 注意用户提到的引用中提到了一些关键点,比如非阻塞socket(引用4)、EPOLLET边沿触发模式(引用4、5)、线程池和资源回收(引用2、3)。不过用户可能只需要一个基础示例,所以先保持简单,可能不需要线程池,但需要处理连接和数据接收。 同时,用户提供的引用5中有epoll_create、epoll_ctl、epoll_wait的函数说明,这些需要正确应用在代码中。例如,epoll_ctl使用EPOLL_CTL_ADD添加新的socket,EPOLL_CTL_DEL删除失效的socket。引用1提到在连接中断后关闭socket并从epoll中删除,这点需要注意。 另外,引用3中的客户端代码示例可能对用户有帮助,但用户主要需要服务器端代码。不过用户的问题明确要求服务器示例,所以客户端部分可以简要提及或省略,除非用户需要。 需要检查代码的正确性,比如错误处理、socket选项设置(如SO_REUSEADDR)、非阻塞模式设置(如果使用边沿触发必须是非阻塞的,引用4提到)。边沿触发模式需要循环读取直到EAGAIN,而水平触发则不需要,但用户引用4用的是边沿触发,所以代码中需要体现这一点。 可能还需要处理EPOLLIN和EPOLLOUT事件,但基础示例可能只处理接收连接和读取数据。此外,在接收数据时,需要处理对方关闭连接的情况(recv返回0),此时应关闭socket并从epoll中移除。 综上,示例代码的大致结构如下: 1. 创建监听socket,设置非阻塞,绑定并监听。 2. 创建epoll实例,注册监听socket的EPOLLIN事件。 3. 进入循环,调用epoll_wait。 4. 处理事件:如果是监听socket,接受新连接,设置非阻塞,并注册到epoll。 5. 如果是已连接的socket,读取数据,处理关闭或错误,回显数据。 6. 错误处理和资源释放。 需要确保代码中使用正确的函数和参数,比如epoll_create的参数在较新内核中可以忽略,但传统上可能需要指定大小。事件结构体的设置,如events字段为EPOLLIN | EPOLLET,data.fd设置为对应的socket。 另外,注意在边沿触发模式下,读取数据时必须循环读取直到没有更多数据,否则可能会丢失事件。因此,在处理EPOLLIN事件时,需要while循环调用recv,直到返回-1且errno为EAGAIN或EWOULDBLOCK。 最后,根据用户提供的引用,特别是引用5中的函数说明和结构体定义,确保代码中的epoll相关调用正确无误。例如,epoll_ctl的参数顺序和结构体初始化是否正确。</think>以下是使用$epoll$实现TCP并发服务器的代码示例,采用边沿触发模式和非阻塞Socket[^4][^5]: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <fcntl.h> #define MAX_EVENTS 1024 #define BUFFER_SIZE 4096 #define PORT 8080 // 设置文件描述符为非阻塞模式 void setnonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, flags | O_NONBLOCK); } int main() { int server_fd, epoll_fd; struct sockaddr_in server_addr; // 创建TCP Socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置地址重用 int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 绑定地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, SOMAXCONN) == -1) { perror("listen"); exit(EXIT_FAILURE); } // 创建epoll实例 if ((epoll_fd = epoll_create1(0)) == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); } // 注册服务器socket到epoll struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 边沿触发模式 ev.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev) == -1) { perror("epoll_ctl"); exit(EXIT_FAILURE); } struct epoll_event events[MAX_EVENTS]; printf("Epoll server listening on port %d\n", PORT); while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (int i = 0; i < nfds; ++i) { // 处理新连接 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t addrlen = sizeof(client_addr); int client_fd; // 接受所有待处理连接 while ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addrlen)) != -1) { setnonblocking(client_fd); // 设置非阻塞模式 // 注册客户端socket到epoll ev.events = EPOLLIN | EPOLLET; ev.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) { perror("epoll_ctl client"); close(client_fd); } printf("New connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); } if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept"); } } // 处理客户端数据 else if (events[i].events & EPOLLIN) { int client_fd = events[i].data.fd; char buffer[BUFFER_SIZE]; ssize_t n; // 边沿触发需要循环读取所有数据 while ((n = recv(client_fd, buffer, BUFFER_SIZE, 0)) > 0) { // 简单回显数据 send(client_fd, buffer, n, 0); } // 连接关闭或出错 if (n == 0 || (n == -1 && errno != EAGAIN)) { printf("Connection closed\n"); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL); close(client_fd); } } } } close(server_fd); return 0; } ``` 关键实现要点: 1. 使用$epoll_create1()$创建epoll实例,比传统$epoll_create()$更推荐[^5] 2. 通过$EPOLLET$标志设置边沿触发模式,避免重复通知[^4] 3. 非阻塞I/O处理确保边沿触发模式正常工作[^4] 4. 使用$epoll_ctl()$动态管理事件注册[^1][^5] 5. 采用循环处理$accept()$和$recv()$以适应边沿触发特性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值