How to use epoll? A complete example in C

本文提供了一个使用epoll进行并发连接处理的完整C语言示例。该示例展示了如何创建非阻塞套接字、绑定端口、监听连接,并通过epoll机制高效地管理多个客户端连接。

reference: How to use epoll? A complete example in C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#define MAXEVENTS 64

static int
make_socket_non_blocking (int sfd)
{
  int flags, s;

  flags = fcntl (sfd, F_GETFL, 0);
  if (flags == -1)
    {
      perror ("fcntl");
      return -1;
    }

  flags |= O_NONBLOCK;
  s = fcntl (sfd, F_SETFL, flags);
  if (s == -1)
    {
      perror ("fcntl");
      return -1;
    }

  return 0;
}

static int
create_and_bind (char *port)
{
  struct addrinfo hints;
  struct addrinfo *result, *rp;
  int s, sfd;

  memset (&hints, 0, sizeof (struct addrinfo));
  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
  hints.ai_flags = AI_PASSIVE;     /* All interfaces */

  s = getaddrinfo (NULL, port, &hints, &result);
  if (s != 0)
    {
      fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));
      return -1;
    }

  for (rp = result; rp != NULL; rp = rp->ai_next)
    {
      sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if (sfd == -1)
        continue;

      s = bind (sfd, rp->ai_addr, rp->ai_addrlen);
      if (s == 0)
        {
          /* We managed to bind successfully! */
          break;
        }

      close (sfd);
    }

  if (rp == NULL)
    {
      fprintf (stderr, "Could not bind\n");
      return -1;
    }

  freeaddrinfo (result);

  return sfd;
}

int
main (int argc, char *argv[])
{
  int sfd, s;
  int efd;
  struct epoll_event event;
  struct epoll_event *events;

  if (argc != 2)
    {
      fprintf (stderr, "Usage: %s [port]\n", argv[0]);
      exit (EXIT_FAILURE);
    }

  sfd = create_and_bind (argv[1]);
  if (sfd == -1)
    abort ();

  s = make_socket_non_blocking (sfd);
  if (s == -1)
    abort ();

  s = listen (sfd, SOMAXCONN);
  if (s == -1)
    {
      perror ("listen");
      abort ();
    }

  efd = epoll_create1 (0);
  if (efd == -1)
    {
      perror ("epoll_create");
      abort ();
    }

  event.data.fd = sfd;
  event.events = EPOLLIN | EPOLLET;
  s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);
  if (s == -1)
    {
      perror ("epoll_ctl");
      abort ();
    }

  /* Buffer where events are returned */
  events = calloc (MAXEVENTS, sizeof event);

  /* The event loop */
  while (1)
    {
      int n, i;

      n = epoll_wait (efd, events, MAXEVENTS, -1);
      for (i = 0; i < n; i++)
    {
      if ((events[i].events & EPOLLERR) ||
              (events[i].events & EPOLLHUP) ||
              (!(events[i].events & EPOLLIN)))
        {
              /* An error has occured on this fd, or the socket is not
                 ready for reading (why were we notified then?) */
          fprintf (stderr, "epoll error\n");
          close (events[i].data.fd);
          continue;
        }

      else if (sfd == events[i].data.fd)
        {
              /* We have a notification on the listening socket, which
                 means one or more incoming connections. */
              while (1)
                {
                  struct sockaddr in_addr;
                  socklen_t in_len;
                  int infd;
                  char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

                  in_len = sizeof in_addr;
                  infd = accept (sfd, &in_addr, &in_len);
                  if (infd == -1)
                    {
                      if ((errno == EAGAIN) ||
                          (errno == EWOULDBLOCK))
                        {
                          /* We have processed all incoming
                             connections. */
                          break;
                        }
                      else
                        {
                          perror ("accept");
                          break;
                        }
                    }

                  s = getnameinfo (&in_addr, in_len,
                                   hbuf, sizeof hbuf,
                                   sbuf, sizeof sbuf,
                                   NI_NUMERICHOST | NI_NUMERICSERV);
                  if (s == 0)
                    {
                      printf("Accepted connection on descriptor %d "
                             "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                    }

                  /* Make the incoming socket non-blocking and add it to the
                     list of fds to monitor. */
                  s = make_socket_non_blocking (infd);
                  if (s == -1)
                    abort ();

                  event.data.fd = infd;
                  event.events = EPOLLIN | EPOLLET;
                  s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
                  if (s == -1)
                    {
                      perror ("epoll_ctl");
                      abort ();
                    }
                }
              continue;
            }
          else
            {
              /* We have data on the fd waiting to be read. Read and
                 display it. We must read whatever data is available
                 completely, as we are running in edge-triggered mode
                 and won't get a notification again for the same
                 data. */
              int done = 0;

              while (1)
                {
                  ssize_t count;
                  char buf[512];

                  count = read (events[i].data.fd, buf, sizeof buf);
                  if (count == -1)
                    {
                      /* If errno == EAGAIN, that means we have read all
                         data. So go back to the main loop. */
                      if (errno != EAGAIN)
                        {
                          perror ("read");
                          done = 1;
                        }
                      break;
                    }
                  else if (count == 0)
                    {
                      /* End of file. The remote has closed the
                         connection. */
                      done = 1;
                      break;
                    }

                  /* Write the buffer to standard output */
                  s = write (1, buf, count);
                  if (s == -1)
                    {
                      perror ("write");
                      abort ();
                    }
                }

              if (done)
                {
                  printf ("Closed connection on descriptor %d\n",
                          events[i].data.fd);

                  /* Closing the descriptor will make epoll remove it
                     from the set of descriptors which are monitored. */
                  close (events[i].data.fd);
                }
            }
        }
    }

  free (events);

  close (sfd);

  return EXIT_SUCCESS;
}
在嵌入式服务端开发中选择 I/O 多路复用机制时,需要综合考虑连接数规模、性能需求、系统资源以及可移植性等因素。对于嵌入式设备来说,通常资源有限,因此需要在性能和实现复杂度之间做出权衡。 ### ### select 的适用场景 `select` 是一种较为传统的 I/O 多路复用机制,其优势在于良好的可移植性,几乎在所有 Unix-like 系统上都支持。在连接数较少(例如小于 1024)的情况下,`select` 的性能表现是可接受的,且实现简单[^4]。对于嵌入式设备而言,如果服务端只需要处理少量连接(如低于 50),使用 `select` 可以避免引入复杂的编程模型和额外的依赖,从而节省系统资源。 然而,`select` 存在一些固有的限制,例如: - 每次调用都需要将文件描述符集合从用户态拷贝到内核态,带来额外开销; - 每次调用后需要重新设置文件描述符集合; - 最大支持的文件描述符数量受限(通常为 1024)[^4]; - 无法直接获取就绪的文件描述符,必须遍历整个集合进行检查。 这些限制使得 `select` 在连接数较多或性能要求较高的场景中表现不佳。 ### ### epoll 的适用场景 `epoll` 是 Linux 特有的 I/O 多路复用机制,相较于 `select` 和 `poll`,其在性能和扩展性方面有显著优势。`epoll` 通过以下机制提升了效率: - 使用红黑树管理文件描述符,仅在添加、修改或删除时更新结构; - 返回的是已经就绪的事件集合,避免了不必要的遍历; - 使用双向链表存储就绪事件,减少了内存分配和释放的开销。 这些特性使得 `epoll` 在连接数较多、I/O 活动频繁的场景中表现出色。对于嵌入式服务端来说,如果预期连接数较高(例如超过 1000)或需要长期运行、处理大量并发请求,`epoll` 是更合适的选择[^2]。 此外,`epoll` 支持两种事件触发模式:边缘触发(Edge Triggered, ET)和水平触发(Level Triggered, LT),可以根据具体需求选择更高效的事件通知方式[^5]。 ### ### 综合建议 在嵌入式服务端开发中,若连接数长期保持在较低水平(如低于 50),且对性能要求不高,使用 `select` 是一种简单、稳定的选择,能够有效降低开发和维护成本。但如果服务端需要处理更多连接,或者对响应时间和吞吐量有较高要求,推荐使用 `epoll` 来提升性能和扩展性。 例如,一个基于 `epoll` 的 TCP 服务器可以更高效地处理大量并发连接,同时减少 CPU 和内存的占用,适用于资源受限的嵌入式环境。 ```c #include <sys/epoll.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_EVENTS 10 #define PORT 8080 int main() { int server_fd, new_socket; struct sockaddr_in address; int addrlen = sizeof(address); int epoll_fd; struct epoll_event event, events[MAX_EVENTS]; // 创建 socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); // 绑定 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) { perror("bind"); close(server_fd); exit(EXIT_FAILURE); } // 监听 if (listen(server_fd, 3) == -1) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } // 创建 epoll 实例 epoll_fd = epoll_create1(0); if (epoll_fd == -1) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加监听 socket 到 epoll event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) { perror("epoll_ctl: add"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); continue; } for (int i = 0; i < nfds; ++i) { if (events[i].data.fd == server_fd) { // 接受新连接 new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen); if (new_socket == -1) { perror("accept"); continue; } // 将新连接添加到 epoll event.events = EPOLLIN | EPOLLET; event.data.fd = new_socket; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) == -1) { perror("epoll_ctl: add"); close(new_socket); } } else { // 处理客户端数据 char buffer[1024]; int n = read(events[i].data.fd, buffer, sizeof(buffer)); if (n <= 0) { close(events[i].data.fd); } else { write(events[i].data.fd, buffer, n); } } } } close(server_fd); close(epoll_fd); return 0; } ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值