`
javatoyou
  • 浏览: 1013634 次
  • 性别: Icon_minigender_2
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

用epoll实现异步的Echo服务器

 
阅读更多

用epoll实现异步的Echo服务器

epoll是Kernel 2.6后新加入的事件机制,在高并发条件下,远优于select.

用个硬件中的例子吧,可能不太恰当:epoll相当于I/O中断(有的时候才相应),而select相当于轮询(总要反复查询)。

其实epoll比slect好用很多,主要一下几个用法。

struct epoll_event ; epoll事件体,事件发生时候你可以得到一个它。其中epoll_event.data.fd可以存储关联的句柄,epoll_event.event是监听标志,常用的有EPOLLIN (有数据,可以读)、EPOLLOUT(有数据,可以写)EPOLLET(有事件,通用);

(1)创建epoll句柄

int epFd = epoll_create(EPOLL_SIZE);

(2)加入一个句柄到epoll的监听队列

ev.data.fd = serverFd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev);

上面的fd是你要绑定给事件发生时候使用的fd,到时候只能操作这个,下面是事件类型。

使用epoll_ctl添加到之中,EPOLL_CTL_ADD是epoll控制类型,这里监听的fd和给event的fd一般相同。

(3)等待event返回

int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1);

传入的evs是epoll_event的数组,EVENT_ARR应当是不超过这个数组的长度。返回nfds的是不超过EVENT_ARR的数值,表示本次等待到了几个事件。

(4)遍历事件

注意,这里遍历的事件是肯定已经发生了的,而select中遍历的是每个fd,而fd不一定在FDSET中(即不一定有读事件发生)!这是效率最大的差别所在!

for (int i = 0; i < nfds; i++)

{

//do something

}

(5)其他技巧

对事件是否是serverFd判断,如果是,进行accept并加入epoll监听队列,要设置异步读取。

如果evts[i]&EPOLLIN,表示可读,使用read进行试探,如果>0表示连接没有关闭,否则连接已经关闭(出发事件又读取不到东西,表示socket关闭!)。如果<0出错。如果>0,需要继续读取直到为0,但是注意这里的为0是在第一次read不为0的前提下,毕竟我们设置了异步读取,暂时没有数据可以读就返回0了!而如果第一次返回0,那么就是关闭吧!

注意关闭要移出出epoll并且close(clientFd)

罗嗦了好多,看代码!

#相关代码, [四号程序员] http://www.coder4.com
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
* main.cc
*
* Created on: 2009-11-30
* Author: liheyuan
* Describe: epoll实现阻塞模式服务器(Echo服务器)
*
* Last Date: 2009-11-30
* CopyRight: 2009 @ ICT LiHeyuan
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <errno.h>
#define EPOLL_SIZE 10
#define EVENT_ARR 20
#define BACK_QUEUE 10
#define PORT 18001
#define BUF_SIZE 16
void setnonblocking(int sockFd) {
int opt;
//获取sock原来的flag
opt = fcntl(sockFd, F_GETFL);
if (opt < 0) {
printf("fcntl(F_GETFL) fail.");
exit(-1);
}
//设置新的flag,非阻塞
opt |= O_NONBLOCK;
if (fcntl(sockFd, F_SETFL, opt) < 0) {
printf("fcntl(F_SETFL) fail.");
exit(-1);
}
}
int main() {
int serverFd;
//创建服务器fd
serverFd = socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(serverFd);
//创建epoll,并把serverFd放入监听队列
int epFd = epoll_create(EPOLL_SIZE);
struct epoll_event ev, evs[EVENT_ARR];
ev.data.fd = serverFd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev);
//绑定服务器端口
struct sockaddr_in serverAddr;
socklen_t serverLen = sizeof(struct sockaddr_in);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(PORT);
if (bind(serverFd, (struct sockaddr *) &serverAddr, serverLen)) {
printf("bind() fail./n");
exit(-1);
}
//打开监听
if (listen(serverFd, BACK_QUEUE)) {
printf("Listen fail./n");
exit(-1);
}
//死循环处理
int clientFd;
sockaddr_in clientAddr;
socklen_t clientLen;
char buf[BUF_SIZE];
while (1) {
//等待epoll事件的到来,最多取EVENT_ARR个事件
int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1);
//处理事件
for (int i = 0; i < nfds; i++) {
if (evs[i].data.fd == serverFd && evs[i].data.fd & EPOLLIN) {
//如果是serverFd,表明有新连接连入
if ((clientFd = accept(serverFd,
(struct sockaddr *) &clientAddr, &clientLen)) < 0) {
printf("accept fail./n");
}
printf("Connect from %s:%d/n", inet_ntoa(clientAddr.sin_addr),
htons(clientAddr.sin_port));
setnonblocking(clientFd);
//注册accept()到的连接
ev.data.fd = clientFd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epFd, EPOLL_CTL_ADD, clientFd, &ev);
} else if (evs[i].events & EPOLLIN) {
//如果不是serverFd,则是client的可读
if ((clientFd = evs[i].data.fd) > 0) {
//先进行试探性读取
int len = read(clientFd, buf, BUF_SIZE);
if (len > 0) {
//有数据可以读,Echo写入
do {
if (write(clientFd, buf, len) < 0) {
printf("write() fail./n");
}
len = read(clientFd, buf, BUF_SIZE);
} while (len > 0);
} else if (len == 0) {
//出发了EPOLLIN事件,却没有可以读取的,表示断线
printf("Client closed at %d/n", clientFd);
epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
close(clientFd);
evs[i].data.fd = -1;
break;
} else if (len == EAGAIN) {
continue;
} else {
//client读取出错
printf("read() fail.");
}
}
} else {
printf("other event./n");
}
}
}
return 0;
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics