epoll机制

本文介绍了一个基于Epoll机制的多线程网络服务器实现。该服务器通过Epoll监控大量客户端连接,并利用多线程处理客户端请求。文章详细展示了如何使用Epoll进行事件注册、等待及修改,并结合线程池实现并发处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/**
 * 该文件名为epoll.c
 *
 * 该测试代码是从http://blog.youkuaiyun.com/mote_li/archive/2004/12/08/209450.aspx修改来的. 只供学习使用.
 *
 * 我的测试环境AS4U3
 * [gan@localhost ~]$ uname -r
 * 2.6.9-34.EL
 */

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

#include <pthread.h>
 

#define MAXLINE 1024
#define OPEN_MAX 100
#define LISTENQ 20
#define INFTIM 1000

#define LOCAL_IP "192.168.1.101" /* 修改为自己本地机器就可以测试了 */
#define SERV_PORT 5555

/**
 * thread task link
 */

struct task
{
  int fd; /* file descriptor */
  struct task *next; /* next task */
};

struct user_data
{
  int fd;
  unsigned int n_size;
  char line[MAXLINE];
};

/* thread exec function */
void *readtask(void *args);
void *writetask(void *args);
 

/* declare epoll_event */
struct epoll_event ev, events[20];
int epfd;

pthread_mutex_t mutex; /* 线程安全使用 */
pthread_cond_t cond1; /* 线程条件等待使用 */

struct task *readhead = NULL,
        *readtail = NULL,
        *writehead = NULL;

void setnonblocking(int sock)
{
  int opts;

  opts = fcntl(sock, F_GETFL);
  if(opts<0)
   {
     perror("fcntl(sock,GETFL)");
     exit(1); /* 其实这样做不怎么好, 最好自己做好出错处理的工作, 不光是进程退出就可以了 */
   }

  if(fcntl(sock, F_SETFL, opts | O_NONBLOCK)<0)
   {
    perror("fcntl(sock,SETFL,opts)");
    exit(1);
   }
}

int main()
{
  int i, maxi, listenfd, connfd, sockfd, nfds;
  socklen_t clilen;
  pthread_t tid1,tid2;
  struct task *new_task=NULL;
  struct user_data *rdata=NULL;

  /* initialize the thread pool */
  pthread_mutex_init(&mutex, NULL);
  pthread_cond_init(&cond1, NULL);

   /* 创建线程, 最好做好错误处理工作, 自己也比较懒. 真正作东西千万别这样噢! */
  pthread_create(&tid1, NULL, readtask, NULL);
  pthread_create(&tid2, NULL, readtask, NULL);

   
/* 生成用于处理accept的epoll专用的文件描述符
    * 以前从没用过
    *

Create a new epoll file descriptor by requesting the kernel allocate an event backing store dimensioned[n. 尺寸, 尺度, 维(数), 度(数), 元] for size descriptors. The size is not the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures. The returned file descriptor will be used for all the subsequent calls to the epoll interface. The file descriptor returned by epoll_create must be closed by using POSIX::close.

When successful, epoll_create returns a positive integer identifying the descriptor. When an error occurs, epoll_create returns -1 and errno is set appropriately.

    *
    */

  epfd = epoll_create(256);

  struct sockaddr_in clientaddr;
  struct sockaddr_in serveraddr;

  listenfd = socket(AF_INET, SOCK_STREAM, 0);

  
//把socket设置为非阻塞方式

  setnonblocking(listenfd);

   
//设置与要处理的事件相关的文件描述符

  ev.data.fd = listenfd;

   
//设置要处理的事件类型

  ev.events = EPOLLIN | EPOLLET;

  
/*注册epoll事件

Control an epoll descriptor, $epfd, by requesting the operation op be performed on the target file descriptor, fd.

$epfd is an epoll descriptor returned from epoll_create.
$op is one of EPOLL_CTL_ADD, EPOLL_CTL_MOD or EPOLL_CTL_DEL.
$fd is the file desciptor to be watched.
$eventmask is a bitmask of events defined by EPOLLIN, EPOLLOUT, etc.

When successful, epoll_ctl returns 0. When an error occurs, epoll_ctl returns -1 and errno is set appropriately.
   */

  epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

  bzero(&serveraddr, sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;

  char *local_addr = LOCAL_IP;
  inet_aton(local_addr, &(serveraddr.sin_addr));

  serveraddr.sin_port = htons(SERV_PORT);
  bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
  listen(listenfd, LISTENQ);

  maxi = 0;
  for ( ; ; )
   {
     
/*等待epoll事件的发生

Wait for events on the epoll file descriptor $epfd.

$epfd is an epoll descriptor returned from epoll_create.
$maxevents is an integer specifying the maximum number of events to be returned.
$timeout is a timeout, in milliseconds

When successful, epoll_wait returns a reference to an array of events. Each event is a two element array, the first element being the file descriptor which triggered the event, and the second is the mask of event types triggered. For example, if epoll_wait returned the following data structure:

     */

     nfds = epoll_wait(epfd, events, 20, 500);

      
//处理所发生的所有事件

     for(i = 0; i < nfds; ++i)
      {
       if(events[i].data.fd == listenfd)
         {
         connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
         if(connfd < 0)
           {
          perror("connfd<0");
          exit(1);
           }

         setnonblocking(connfd);

         char *str = inet_ntoa(clientaddr.sin_addr);

         printf("connect_from >> %s /n", str);

         ev.data.fd = connfd;
//设置用于读操作的文件描述符

         ev.events = EPOLLIN | EPOLLET;
//设置用于注测的读操作事件


           
//注册ev

         epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
         }
       else if (events[i].events & EPOLLIN)
         {
          printf("reading!/n");

          if ((sockfd = events[i].data.fd) < 0)
            continue;

          new_task = (struct task *)malloc(sizeof(struct task));
          new_task->fd = sockfd;
          new_task->next = NULL;

          pthread_mutex_lock(&mutex);
//添加新的读任务


          if(readhead == NULL)
             {
             readhead = new_task;
             readtail = new_task;
             }
          else
             {
             readtail->next = new_task;
             readtail = new_task;
             }

              
//唤醒所有等待cond1条件的线程

           pthread_cond_broadcast(&cond1);
           pthread_mutex_unlock(&mutex);
           }
        else if (events[i].events & EPOLLOUT)
          {
           rdata = (struct user_data *)events[i].data.ptr;
           sockfd = rdata->fd;

         printf("thread.%u Write data fd.%d len.%d data.%s /n"
        , (uint32_t)pthread_self(), sockfd, rdata->n_size, rdata->line);

           write(sockfd, rdata->line, rdata->n_size);
           close(sockfd);

           free(rdata);

           ev.data.fd = sockfd;
//设置用于读操作的文件描述符

           ev.events = EPOLLIN | EPOLLET;
//设置用于注测的读操作事件


              
//修改sockfd上要处理的事件为EPOLIN

           epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
           }
       }
   }
}

/**
 * thread exec function
 */

void *readtask(void *args)
{
  int fd = -1;
  unsigned int n;

  
//用于把读出来的数据传递出去

  struct user_data *data = NULL;

  while (1)
   {
     pthread_mutex_lock(&mutex);

      
//等待到任务队列不为空

     while(readhead == NULL)
      {
       printf("thread.%u waiting, task is NULL ... /n", (uint32_t)pthread_self());
       pthread_cond_wait(&cond1, &mutex);
      }

      fd = readhead->fd;

       
//从任务队列取出一个读任务

      struct task *tmp = readhead;
      readhead = readhead->next;
      free(tmp);

      pthread_mutex_unlock(&mutex);

      data = (struct user_data *)malloc(sizeof(struct user_data));
      data->fd = fd;

      if ((n = read(fd, data->line, MAXLINE)) < 0)
        {
        if (errno == ECONNRESET)
          {
           close(fd);
          }
        else
          printf("readline error /n");

        if(data != NULL)
           free(data);
        }
      else if (n == 0)
       {
       close(fd);
       printf("Client close connect!/n");
       if(data != NULL)
             free(data);
       }
     else
       {
       data->n_size = n;

       printf("thread.%u read fd.%d len.%d data.%s /n"
        , (uint32_t)pthread_self(), fd, n, data->line);
        
       ev.data.ptr = data;
//设置需要传递出去的数据

       ev.events = EPOLLOUT | EPOLLET;
//设置用于注测的写操作事件


        
//修改sockfd上要处理的事件为EPOLLOUT

       epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
      }
   }
}


/**
 * 一个简单的Makefile
 */

all: epoll.c
        gcc -Wall -g -o epoll epoll.c -lpthread

clean:
        rm -f epoll

### Epoll机制原理 Epoll是一种高效的I/O多路复用技术,专为处理大量并发连接而设计。其核心原理在于通过事件驱动的方式管理文件描述符,从而避免了传统方法如`select`和`poll`中存在的性能瓶颈。 在调用`epoll_create()`时,Linux内核会创建一个`eventpoll`结构体实例[^2]。这个结构体内包含两个关键的数据结构:红黑树(rb_root)和双向链表(list_head)。红黑树用来存储所有添加到epoll中的事件,即该epoll监控的所有socket文件描述符;而双向链表则保存着那些已经准备好进行读写操作的事件[^3]。当用户程序调用`epoll_wait()`时,它将阻塞直到有事件发生,并且只会返回那些确实发生了I/O活动的文件描述符[^4]。 与传统的轮询方式不同,epoll采用了事件通知机制。这意味着只有当某个特定的文件描述符上发生了感兴趣的I/O事件时,才会被报告给应用程序。这种机制显著减少了不必要的系统调用开销,并提高了整体效率,尤其是在高并发场景下表现尤为突出[^4]。 ### 在Linux网络编程中的应用 - **服务器端开发**:对于需要同时处理成千上万客户端请求的服务来说,比如Web服务器或聊天服务,使用epoll可以极大提升服务器的吞吐量。 - **实时通信系统**:由于epoll能够快速响应活跃的连接,因此非常适合用于构建低延迟的消息传递系统。 - **异步非阻塞IO模型**:结合非阻塞socket使用epoll可以让开发者实现高度可扩展的应用程序架构。 - **资源敏感型环境**:没有最大文件描述符限制的特点使得epoll成为资源受限环境下理想的解决方案之一。 下面是一个简单的示例代码,演示如何利用epoll来监听并处理多个客户端的连接请求: ```c #include <sys/epoll.h> #include <netinet/in.h> #include <stdio.h> #include <unistd.h> #define MAX_EVENTS 10 #define PORT 8080 int main() { int listen_sock, conn_sock, epoll_fd; struct sockaddr_in server_addr; // 创建epoll实例 epoll_fd = epoll_create1(0); // 初始化服务器地址等细节... // 添加监听套接字到epoll struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 边缘触发模式 event.data.fd = listen_sock; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, &event); while (1) { struct epoll_event events[MAX_EVENTS]; int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < num_events; ++i) { if (events[i].data.fd == listen_sock) { // 接受新连接并将新的conn_sock加入epoll } else { // 处理已存在的连接上的数据读取/发送 } } } return 0; } ``` 此段代码展示了基本的工作流程:首先初始化必要的网络配置,然后创建一个epoll实例并通过`epoll_ctl()`函数注册对监听套接字的关注。接着进入循环等待状态,在这里通过调用`epoll_wait()`获取当前就绪的事件列表,并据此执行相应的处理逻辑。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值