服务器端使用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相比,最大不同在于:
- 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 得到; - select的FD_SETSIZE是有限止的,而epoll是没有限止的只与系统资源有关。
注:本文选自http://www.cnblogs.com/cipc/articles/2431042.html,
如有侵犯您的权益,请邮件通知我,我将会在收到通知后尽快删除相关内容。


浙公网安备 33010602011771号