服务器端使用epoll监听大量并发链接

首先看一下epoll的几个函数的介绍。

1、epoll_create函数

/** 
 * @brief 该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存
* 放你想关注的socket fd上是否发生以及发生了什么事件。 * @param size: size就是你在这个epoll fd上能关注的最大socket fd数 * @return 生成的文件描述符
*/ int epoll_create(int size);

 2、epoll_ctl函数

/** 
 * @brief 该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删
* 除事件。 * @param epfd : 由 epoll_create 生成的epoll专用的文件描述符 * @param op : 要进行的操作例如注册事件,可能的取值 * EPOLL_CTL_ADD 注册
* EPOLL_CTL_MOD 修改 * EPOLL_CTL_DEL 删除 * @param fd : 关联的文件描述符 * @param event : 指向epoll_event的指针 * @return 0 if success, -1 if fail
*/ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *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 */ };

 

常用的事件类型:

EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;

例:

struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);

 3、epoll_wait函数

/** 
 * @brief 该函数用于轮询I/O事件的发生
 * @param epfd      : 由epoll_create 生成的epoll专用的文件描述符
 * @param events    : 用于回传代处理事件的数组
 * @param maxevents : 每次能处理的事件数
 * @param timeout   : 等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可
 * @return if >=0, 返回发生事件数, if -1, 错误
 */
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);

 epoll_wait运行的原理: 

等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。 并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。

 

好了,其实在epoll的使用中无非就用到了上面介绍的几个函数,下面贴一段用epoll实现的服务器代码:

服务器代码
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 #include <sys/types.h>
  6 #include <netinet/in.h>
  7 #include <sys/socket.h>
  8 #include <sys/wait.h>
  9 #include <unistd.h>
 10 #include <arpa/inet.h>
 11 #include <openssl/ssl.h>
 12 #include <openssl/err.h>
 13 #include <fcntl.h>
 14 #include <sys/epoll.h>
 15 #include <sys/time.h>
 16 #include <sys/resource.h>
 17 #define MAXBUF 1024
 18 #define MAXEPOLLSIZE 10000
 19 /*
 20    setnonblocking - 设置句柄为非阻塞方式
 21    */
 22 int setnonblocking(int sockfd)
 23 {
 24     if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
 25     {
 26         return -1;
 27     }
 28     return 0;
 29 }
 30 /*
 31    handle_message - 处理每个 socket 上的消息收发
 32    */
 33 int handle_message(int new_fd)
 34 {
 35     char buf[MAXBUF + 1];
 36     int len;
 37     /* 开始处理每个新连接上的数据收发 */
 38     bzero(buf, MAXBUF + 1);
 39     /* 接收客户端的消息 */
 40     len = recv(new_fd, buf, MAXBUF, 0);
 41     if (len > 0)
 42     {
 43         printf("%d接收消息成功:'%s',共%d个字节的数据\n",
 44              new_fd, buf, len);
 45     }
 46     else
 47     {
 48         if (len < 0)
 49             printf
 50                 ("消息接收失败!错误代码是%d,错误信息是'%s'\n",
 51                  errno, strerror(errno));
 52         close(new_fd);
 53         return -1;
 54     }
 55     /* 处理每个新连接上的数据收发结束 */
 56     return len;
 57 }
 58 int main(int argc, char **argv)
 59 {
 60     int listener, new_fd, kdpfd, nfds, n, ret, curfds;
 61     socklen_t len;
 62     struct sockaddr_in my_addr, their_addr;
 63     unsigned int myport, lisnum;
 64     struct epoll_event ev;
 65     struct epoll_event events[MAXEPOLLSIZE];
 66     struct rlimit rt;
 67     myport = 5000;
 68     lisnum = 2; 
 69     /* 设置每个进程允许打开的最大文件数 */
 70     rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
 71     if (setrlimit(RLIMIT_NOFILE, &rt) == -1) 
 72     {
 73         perror("setrlimit");
 74         exit(1);
 75     }
 76     else 
 77     {
 78         printf("设置系统资源参数成功!\n");
 79     }
 80     /* 开启 socket 监听 */
 81     if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
 82     {
 83         perror("socket");
 84         exit(1);
 85     }
 86     else
 87     {
 88         printf("socket 创建成功!\n");
 89     }
 90     setnonblocking(listener);
 91     bzero(&my_addr, sizeof(my_addr));
 92     my_addr.sin_family = PF_INET;
 93     my_addr.sin_port = htons(myport);
 94     my_addr.sin_addr.s_addr = INADDR_ANY;
 95     if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1) 
 96     {
 97         perror("bind");
 98         exit(1);
 99     } 
100     else
101     {
102         printf("IP 地址和端口绑定成功\n");
103     }
104     if (listen(listener, lisnum) == -1) 
105     {
106         perror("listen");
107         exit(1);
108     }
109     else
110     {
111         printf("开启服务成功!\n");
112     }
113     /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
114     kdpfd = epoll_create(MAXEPOLLSIZE);
115     len = sizeof(struct sockaddr_in);
116     ev.events = EPOLLIN | EPOLLET;
117     ev.data.fd = listener;
118     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0) 
119     {
120         fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
121         return -1;
122     }
123     else
124     {
125         printf("监听 socket 加入 epoll 成功!\n");
126     }
127     curfds = 1;
128     while (1) 
129     {
130         /* 等待有事件发生 */
131         nfds = epoll_wait(kdpfd, events, curfds, -1);
132         if (nfds == -1)
133         {
134             perror("epoll_wait");
135             break;
136         }
137         /* 处理所有事件 */
138         for (n = 0; n < nfds; ++n)
139         {
140             if (events[n].data.fd == listener) 
141             {
142                 new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);
143                 if (new_fd < 0) 
144                 {
145                     perror("accept");
146                     continue;
147                 } 
148                 else
149                 {
150                     printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n",
151                             inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
152                 }
153                 setnonblocking(new_fd);
154                 ev.events = EPOLLIN | EPOLLET;
155                 ev.data.fd = new_fd;
156                 if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)
157                 {
158                     fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",
159                             new_fd, strerror(errno));
160                     return -1;
161                 }
162                 curfds++;
163             } 
164             else
165             {
166                 ret = handle_message(events[n].data.fd);
167                 if (ret < 1 && errno != 11)
168                 {
169                     epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
170                     curfds--;
171                 }
172             }
173         }
174     }
175     close(listener);
176     return 0;
177 }

epoll用到的所有函数都是在头文件sys/epoll.h中声明,有什么地方不明白或函数忘记了可以去看一下。 

epoll和select相比,最大不同在于: 

  1. epoll返回时已经明确的知道哪个socket fd发生了事件,不用再一个个比对。这样就提高了效率。
    在select 中,要去判断每个socket, 通过 if(FD_ISSET(socket, &集合));
    在 epoll 中, for (int i=0; i<nfds; i++) 其中的nfds 是 epoll_wait 返回的结果,即是发生事件的socket的数目, 具体的socket 可以通过 m_events[i].data.fd 得到;
  2. select的FD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。

 

 

注:本文选自http://www.cnblogs.com/cipc/articles/2431042.html,
如有侵犯您的权益,请邮件通知我,我将会在收到通知后尽快删除相关内容。

posted @ 2012-11-13 14:37  ruihong  阅读(710)  评论(0)    收藏  举报