0.序
1.为什么要采用epoll事件处理机制呢?也就是Epoll的优点。
2.epoll事件处理机制有两种触发方式:ET和LT。有何区别?
3.epoll相关函数
1)epoll_create函数
2)epoll_ctl函数
3)epoll_wait函数
4.程序
5.小结
6.参考文章
|
NAME epoll_create, epoll_create1 - open an epoll file descriptor SYNOPSIS #include int epoll_create(int size); int epoll_create1(int flags); DESCRIPTION epoll_create() creates an epoll "instance", requesting the kernel to allocate an event backing store dimensioned for size descriptors. The size is not the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures. (Nowadays, size is ignored; see NOTES below.) epoll_create() returns a file descriptor referring to the new epoll instance. This file descriptor is used for all the subsequent calls to the epoll interface. When no longer required, the file descrip‐ tor returned by epoll_create() should be closed by using close。 epoll_create()创建一个epoll句柄,内核会分配一个空间用来存放你想关注的文件描述符上是否发生以及发生了什么事件。 epoll_create()返回一个代指新epoll句柄的fd。这个fd作为epoll的接口,会在接下来的所有epoll调用中使用这个fd。 |
NAME
epoll_ctl - control interface for an epoll descriptor SYNOPSIS #include int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); DESCRIPTION This system call performs control operations on the epoll instance referred to by the file descriptor epfd.
1)int op
It requests that the operation op be performed for the target file descriptor fd.
epoll_ctl执行在epfd代表的epoll句柄上的控制操作。操作op将会在目标文件描述符fd上进行操作。 Valid values for the op argument are : EPOLL_CTL_ADD Register the target file descriptor fd on the epoll instance referred to by the file descriptor epfd and associate the event event with the internal file linked to fd.
EPOLL_CTL_ADD:将目标文件描述符fd注册到epoll句柄上,并使事件类型event与文件描述符fd相关联。
EPOLL_CTL_MOD
Change the event event associated with the target file descrip‐ tor fd.
EPOLL_CTL_MOD:改变目标文件描述符fd的事件类型。
EPOLL_CTL_DEL
Remove (deregister) the target file descriptor fd from the epoll instance referred to by epfd. The event is ignored and can be NULL (but see BUGS below). EPOLL_CTL_DEL:从epoll句柄epfd中删除目标文件描述符fd。fd对应的事件将会被忽略。
2)
struct epoll_event *event
The event argument describes the object linked to the file descriptor fd.
与文件描述符fd相对应的事件类型event,其结构体如下: The struct epoll_event is defined as : 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 */ }; The events member is a bit set composed using the following available event types: 常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:表示对应的文件描述符有事件发生;
3)举例说明其使用方式
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符 ev.data.fd=listenfd; //设置要处理的事件类型 ev.events=EPOLLIN|EPOLLET; //注册epoll事件 epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); |
epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor 作用:等待在epoll 文件描述符上的一个I/O事件。 SYNOPSIS #include int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); DESCRIPTION The epoll_wait() system call waits for events on the epoll instance referred to by the file descriptor epfd. The memory area pointed to by events will contain the events that will be available for the caller. Up to maxevents are returned by epoll_wait(). The maxevents argument must be greater than zero. epoll_wait系统调用等待epoll句柄中events事件。由events指向的内存区域包含了对于调用者而言可选的事件,即如果注册在epoll句柄上的fd的事件发生,那么就会将发生的fd以及事件类型放入events数组中。当到了最大事件maxevents时,epoll_wait会返回。maxevents必须大于0. The call waits for a maximum time of timeout milliseconds. Specifying a timeout of -1 makes epoll_wait() wait indefinitely, while specifying a timeout equal to zero makes epoll_wait() to return immediately even if no events are available (return code equal to zero). 该系统调用epoll_wait等待timeout ms。如果timeout=-1,则是无限等待;如果timeout=0,即使没有事件可供选择,epoll_wait也会立即返回。 The struct epoll_event is defined as : 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 */ }; The data of each returned structure will contain the same data the user set with an epoll_ctl(2) (EPOLL_CTL_ADD,EPOLL_CTL_MOD) while the events member will contain the returned event bit field. 注意:epoll_wait会将注册在epfd上的已经发生事件的fd的事件类型清空,所以如果下一个循环还要关注这个fd的话,就必须通过epoll_ctl(epfd,EPOLL_CTL_MOD,xx,xxxx)来重新设置fd的事件类型。这时不用EPOLL_CTL_ADD,因为fd没有被清空,只是事件类型被清空。 |
/*
** 这是服务器端程序
**本程序来自于
http://www.cppblog.com/converse/archive/2008/04/29/48482.html
**
本程序为采用
Epoll
事件的简单程序实现。我添加了#define ET 1 如果#define ET 0 ,那么epoll采用LT触发,如果#define ET 1,那么采用ET触发
** 采用LT触发时,
只要还有数据留在
buffer
中
,server
就会继续得到通知
*/
#define
ET 0
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define
MAXLINE 5
#define
OPEN_MAX 100
#define
LISTENQ 20
#define
SERV_PORT 5000
#define
INFTIM 1000
void
setnonblocking(
int
sock)
{
int
opts;
opts=fcntl(sock,F_GETFL);
if
(opts<0)
{
perror(
"fcntl(sock,GETFL)"
);
exit(1);
}
opts = opts|O_NONBLOCK;
if
(fcntl(sock,F_SETFL,opts)<0)
{
perror(
"fcntl(sock,SETFL,opts)"
);
exit(1);
}
}
int
main()
{
int
i, maxi, listenfd, connfd, sockfd,epfd,nfds;
ssize_t n;
char
line[MAXLINE];
socklen_t clilen ;
//
声明
epoll_event
结构体的变量
,ev
用于注册事件
,
数组用于回传要处理的事件
struct
epoll_event ev,events[20];
//
生成用于处理
accept
的
epoll
专用的文件描述符
epfd=epoll_create(256);
struct
sockaddr_in clientaddr;
struct
sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//
把
socket
设置为非阻塞方式
#if
ET
setnonblocking(listenfd);
#endif
//
设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//
设置要处理的事件类型
#if
ET
ev.events=EPOLLIN|EPOLLET;
#else
ev.events=EPOLLIN;
#endif
//
注册
epoll
事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
memset(&serveraddr,0,
sizeof
(serveraddr));
serveraddr.sin_family = AF_INET;
// char *local_addr="127.0.0.1";
// inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port=htons(SERV_PORT);
bind(listenfd,(
struct
sockaddr *)&serveraddr,
sizeof
(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
clilen =
sizeof
(
struct
sockaddr);
for
( ; ; ) {
//
等待
epoll
事件的发生
nfds=epoll_wait(epfd,events,20,500);
//
处理所发生的所有事件
for
(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)
{
printf(
"001\n"
);
connfd = accept(listenfd,(
struct
sockaddr *)&clientaddr, &clilen);
if
(connfd<0){
perror(
"connfd<0"
);
exit(1);
}
//setnonblocking(connfd);
char
*str = inet_ntoa(clientaddr.sin_addr);
printf(
"accapt a connection from %s\n"
,str);
//
设置用于读操作的文件描述符
ev.data.fd=connfd;
//
设置用于注册的读操作事件
#if
ET
ev.events=EPOLLIN|EPOLLET;
#else
ev.events=EPOLLIN;
#endif
//
注册
ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if (events[i].events&EPOLLIN)
{
printf(
"EPOLLIN\n"
);
if
( (sockfd = events[i].data.fd) < 0)
continue
;
if
( (n = read(sockfd, line, MAXLINE)) < 0) {
if
(errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
}
else
printf(
"error\n"
);
}
else
if
(n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
line[n] =
'\0'
;
printf(
"read %s\n"
,line);
//
设置用于写操作的文件描述符
ev.data.fd=sockfd;
//
设置用于注册的写操作事件
#if
ET
ev.events=EPOLLOUT|EPOLLET;
#else
ev.events=EPOLLOUT;
#endif
//
修改
sockfd
上要处理的事件为
EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);/*注意,如果将这一行的注释去掉,那么程序就会将要处理的事件修改为读,那么就会进入002进行处理*/
}
else
if
(events[i].events&EPOLLOUT)
{
printf(
"002\n"
);
sockfd = events[i].data.fd;
write(sockfd, line, n);
//
设置用于读操作的文件描述符
ev.data.fd=sockfd;
//
设置用于注测的读操作事件
#if
ET
ev.events=EPOLLIN|EPOLLET;
#else
ev.events=EPOLLIN;
#endif
//
修改
sockfd
上要处理的事件为
EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return
0;
}
|
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);/*注意,如果将这一行的注释去掉,那么程序就会将要处理的事件修改为读,那么就会进入002进行处理*/其结果如下所示
|
/*
** 这是客户端端程序文件名: epoll_io_client.perl
** perl参考
http://www.phpchina.com/resource/manual/perl/perl5-1.htm
**
执行方法: $chmod +x
epoll_io_client.perl
就可以执行了:$./
epoll_io_client.perl
。
注:你的程序的第一行必须为#!/usr/local/bin/perl(perl所在位置)
*/
#!/usr/bin/perl
use IO::Socket; my $host = "127.0.0.1"; my $port = 5000; my $socket = IO::Socket::INET->new("$host:$port") or die "create socket error $@"; my $msg_out = "1234567890"; print $socket $msg_out; print "now send over, go to sleep\n"; #sleep(5); print "5 second gone send another line\n"; #print $socket $msg_out; while (1) { sleep(1); } |
单进程的用法: int epfd = epoll_create(int size); 函数作用:用来创建Epoll事件的句柄。官方解释:它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。 我对于该函数的作用的理解:就是创建一个用于Epoll事件的句柄,类似文件句柄
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
函数作用:用来添加或者修改事件。
这里面重要的参数是struct epoll_event * event 和int fd。int fd 就是Epoll要处理的fd。
struct epoll_event * event则是设置了Epoll对fd的处理方式。
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
函数作用:
该函数用于轮询I/O事件的发生。对于epoll_ctl中设置的I/O fd进行处理。
参数说明:epfd:已经经过epoll_create创建和epoll_ctl设置。这里面存放着要处理的fd。
struct epoll_event * events:这个数组用来存放发生的fd和事件类型。其中的取值都是由epoll_ctl中注册的已经发生的fd和其对应的事件类型。
maxevents:每次能处理的事件数
注意:每次执行完epoll_wait函数都会将int epfd中注册的int fd清除,因此还需要重新设置int fd 和
struct epoll_event *event,并且使用epoll_ctl重新添加或者修改事件。
单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 所以你还是有必要建立线程池来发挥更大的效能。
线程池的用法:
本人对线程池不懂,但是可以从这篇文章中看到epoll的用法:一个简短的epoll服务器示例, 监听5000个端口, 使用线程池
从本例子中可以看到:
1)int epfd中不仅仅可以添加一个fd,可以添加多个fd,也就是epoll可以用来处理多个fd。
2)整个流程为 socket-----> bind -----> listen ----->创建epfd = epoll_create() ------------> 将socket产生的fd放入epfd中 epoll_add(epfd,EPOLL_CTL_ADD,fd,&ev)------------------------------->for(;;){ epoll_wait(epfd,events,20,time_value); .....}
|