`
lqy1987lqy
  • 浏览: 10924 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
最近访客 更多访客>>
社区版块
存档分类
最新评论

如何使用epoll? 一个C语言的简单例子

    博客分类:
  • unp
阅读更多
* How to use epoll? A complete example in C 译文

通常的网络服务器实现, 是对每一个连接使用一个单独的线程或进程。对高性能应用而言,由于需要同时处理非常多的客户请求, 所以这种方式并不能工作得很好,因为诸如资源使用和上下文切换所需的时间影响了在一时间内对多个客户端进行处理。另一个可选的途径是在一个单独的线程里采用非阻塞的I/O, 这样当可以从一个socket中读取或写入更多数据时,由一些已经准备就绪的通知方式来告知我们。

这篇文章介绍Linux 的 epoll方法, 它是Linux上最好的就绪通知方式。我们会写一个用C语言的TCP服务器的完全实现的简单程序。假设你已有C编程的经验,知道在Linux 下编译和运行程序, 并且会用 manpages 来查看所使用的 C 函数。

epoll 是在 Linux 2.6 才引进的,而且它并不适用于其它 Unix-like 系统。它提供了一个与select 和 poll 函数相似的功能:
+ select 可以在某一时间监视最大达到 FD_SETSIZE 数量的文件描述符, 通常是由在 libc 编译时指定的一个比较小的数字。
+ poll 在同一时间能够监视的文件描述符数量并没有受到限制,即使除了其它因素,更加的是我们必须在每一次都扫描所有通过的描述符来检查其是否存在己就绪通知,它的时间复杂度为 O(n) ,是缓慢的。

epoll 没有以上所示的限制,并且不用执行线性扫描。因此, 它能有更高的执行效率且可以处理大数量的事件。

一个 epoll 实例可以通过返加epoll 实例的 epoll_create 或者 epoll_create1 函数来创建。 epoll_ctl 是用来在epoll实例中 添加/删除 被监视的文件描述符的。 epoll_wait是用来等待所监听描述符事件的,它会阻塞到事件到达。 可以在 manpages上查看更多信息。

当描述符被添加到epoll实例中, 有两种添加模式: level triggered(级别触发) 和 edge triggered(边沿触发) 。 当使用 level triggered 模式并且数据就绪待读, epoll_wait总是会返加就绪事件。如果你没有将数据读取完, 并且调用epoll_wait 在epoll 实例上再次监听这个描述符, 由于还有数据是可读的,它会再次返回。在 edge triggered 模式时, 你只会得一次就绪通知。 如果你没有瘵数据读完, 并且再次在 epoll实例上调用 epoll_wait , 由于就绪事件已经被发送所以它会阻塞。

传递到 epoll_ctl 的epoll事件结构体如下所示。对每一个被监听的描述符,你可以关联到一个整数或一个作为用户数据的指针。
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 */
};


马上实践写代码。我们会实现一个小的TCP服务器,它会将所有SOCKET上收到的数据输出到标准输出。 首先写一个 create_and_bind() 函数,它创建并绑定一个TCP socket.

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;
}


create_and_bind函数包含了一种可移植方式来获取IPv4或IPv6套接字的标准代码段。它接受一个port的字符串参数,port是从argv\[1\]中传入的。其中,getaddrinfo函数返回一群addrinfo到result,其中它们跟传入的hints参数是兼容的。 addrinfo结构体如下:

struct addrinfo
{
  int              ai_flags;
  int              ai_family;
  int              ai_socktype;
  int              ai_protocol;
  size_t           ai_addrlen;
  struct sockaddr *ai_addr;
  char            *ai_canonname;
  struct addrinfo *ai_next;
};


我们依次遍历这些结构体并用其来创建结构体,直到我们可以同时创建和绑定到socket。如果我们成功,create_and_bind() 会返回一个socket描述符。失败则返回 -1.

接下来,我们写一个用来设置socket为非阻塞的函数。 make_socket_non_blocking() 设置 O_NONBLOCK 标志给传入的sfd描述符参数。
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;
}


接下来的main()函数中,它包含有一个事件循环。 下面是代码:

#define MAXEVENTS 64

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;
}


关于main函数里就不多说了。

下载 epoll-example.c 程序。

分享到:
评论

相关推荐

    epoll 服务端例子

    这个“epoll 服务端例子”是C语言实现的一个实例,旨在帮助初学者理解和掌握`epoll`编程。下面将详细解释`epoll`的核心概念、工作原理以及如何通过`test_epoll.cpp`和`test_cli.cpp`进行实践。 1. **epoll介绍** `...

    基于C语言编写的高并发Epoll服务器.zip

    【标题】中的“基于C语言编写的高并发Epoll服务器”指的是一个使用C语言实现的高性能、高并发的网络服务器,它利用了Epoll(Event Poll)机制来处理大量并发连接。Epoll是Linux内核提供的一种I/O多路复用技术,用于...

    用c语言写的daytime网络小程序

    标题中的“用C语言写的daytime网络小程序”指的是一个基于C语言实现的网络应用程序,它遵循TCP/IP协议栈,主要用于在网络环境中提供日期和时间信息。这个程序分为两个部分:服务端和客户端。服务端监听特定的网络...

    基于io复用的服务器开发例子

    在实现过程中,需要注意的是,Epoll事件的处理应当是异步的,这意味着在处理完一个事件后,应该立即将该事件从Epoll实例中删除,以免重复处理。此外,正确处理错误情况,如网络中断、客户端异常断开等,也是服务器...

    libevent源码和一个小例子

    标题"libevent源码和一个小例子"指的是一个关于libevent库的源代码分析以及如何使用它的具体示例。Libevent是一个事件通知库,它用于编写高性能的网络服务器,处理TCP、UDP、HTTP、SSL和其他IO事件。在给定的描述中...

    Linux下C语言应用编程代码范例

    在Linux操作系统中,C语言是一种常用的编程语言,用于开发系统级程序、库和各种应用程序。这份"Linux下C语言应用编程代码范例"是学习和理解Linux环境下C语言编程的宝贵资源。它提供了基础到进阶的各种示例,涵盖了...

    linux c语言 select函数的用法

    下面是一个使用`select`函数的简单示例代码: ```c #include #include #include int main() { int sock; FILE *fp; fd_set fds; struct timeval timeout = {3, 0}; char buffer[256] = {0}; // 假设已经...

    Linux C网络编程中最简单的例子(源码)

    这个"Linux C网络编程中最简单的例子"应该包含了一个简单的客户端(client.c)和服务器端(server.c)的源代码。下面我们将详细探讨这些关键知识点。 1. **套接字(Socket)**: 套接字是网络编程中的基本概念,它...

    DNS 解析协议 c 函数, 实例, 方便构造自己的非阻塞解析函数, 适用于任何平台

    下面将详细解释DNS解析协议的基础知识,并提供一个C语言实现的例子。 首先,了解DNS的工作流程: 1. **DNS查询**:当用户输入域名时,客户端会向本地DNS服务器发起查询请求。 2. **递归查询**:如果本地DNS服务器...

    关于C语言协程与网络编程的分析

    例如,线程池模型(thread per connection)就是一个同步模型的例子,每个连接对应一个线程,容易遇到性能瓶颈。 异步模型,如非阻塞IO配合多路复用技术(epoll, select, poll),允许一个线程处理多个IO事件,提高...

    linux_test.zip_CPU使用率_cpu test_定时器

    本项目名为"linux_test.zip",其中包含了利用C语言编写的一个程序,用于实现CPU使用率的检测,特别是关注整体(total)的CPU使用率,而非单独的用户或系统使用率。该程序运用了定时器功能,每30秒执行一次信息采集,...

    学习SOCK的例子

    - 使用`socket()`函数创建一个新的socket描述符。 - 使用`bind()`函数将socket与特定的IP地址和端口绑定。 - `listen()`函数设置服务器监听模式,等待连接请求。 - 当有客户端连接时,`accept()`函数会返回一个...

    李慧芹 linux c 例子和项目源码,完整版

    在这个压缩包中,"linux_program"目录可能包含了多个子目录和文件,每个都代表一个独立的C语言程序或者项目。这些项目可能涵盖以下知识点: 1. **文件操作**:如何在Linux下打开、读取、写入和关闭文件,包括使用...

    libevent.zip

    总之,“libevent.zip”提供了libevent库和一个实际应用的HTTP服务器实例,对于想要学习如何使用C语言构建高性能网络服务的开发者来说,这是一个宝贵的资源。通过研究这个例子,可以深入了解libevent的用法,以及...

    简单的聊天室程序

    总的来说,这个简单的聊天室程序展示了如何使用基本的网络编程技术实现一个功能齐全的通信系统。通过学习这个例子,开发者可以了解如何利用`select`和`fd_set`处理并发连接,这对于理解网络编程和开发服务器应用程序...

    freecplus_20200926.tgz

    "freecplus_20200926.tgz" 是一个压缩包文件,它包含的资源来自于C语言技术网,一个专注于C语言和C++技术分享的平台。这个压缩包可能是该网站的一个开源框架或者相关的学习资料,具体内容需要解压后查看。根据标签,...

    FTP客户端和服务器源代码(C语言)实现[参照].pdf

    在提供的C语言源代码中,可以看到一个简单的FTP客户端的实现。以下是一些关键知识点: 1. **Winsock库**: - `winsock2.h` 是Windows平台上的Socket库头文件,包含了进行网络通信所需的所有函数声明。 - `#pragma...

    IO多路复用之select实例

    而Qt是一个跨平台的C++库,虽然它提供了丰富的网络编程接口,但在演示select函数的使用时,我们可以直接使用C语言的系统调用。 **四、示例代码分析** 在名为`multiIO`的项目中,通常会有一个主循环,该循环调用...

    Server_ST.rar_C http server

    在这个项目中,开发者创建了一个基于C语言的简易HTTP服务器,旨在提供基本的网页服务功能,尽管目前它还是单线程版本,但已具备处理并发访问的能力。 在C语言编程方面,这个项目可能涉及到以下几个核心知识点: 1....

    IO模型编程实例

    以下是一个`epoll`的例子: ```c epoll_fd = epoll_create1(0); event.events = EPOLLIN; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event); while (true) { struct epoll_event events[EPOLL_MAX_EVENTS]; int ...

Global site tag (gtag.js) - Google Analytics