慢慢聊异步IO之Linux Epoll
一、What:异步IO Epoll是什么?
1. 一句话总结
允许进程发起很多I/O操作,而不用阻塞或等待任何操作完成。Epoll是Linux下的网络异步IO库函数。
2. 详细说说
一般来说,服务器端的I/O主要有两种情况:一是来自网络的I/O;二是对文件(设备)的I/O。Windows的异步I/O模型能很好的适用于这两种情况。而Linux针对前者提供了epoll模型,针对后者提供了AIO模型(关于是否把两者统一起来争论了很久)。今天我们聊聊前者。
NAME epoll_create, epoll_create1 - open an epoll file descriptor SYNOPSIS #include <sys/epoll.h> 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 <sys/epoll.h> 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 <sys/epoll.h> 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没有被清空,只是事件类型被清空。 |
二、Why:为什么Epoll,它能干啥?
Epoll与select和poll模式相比有很多优势。
三、How:异步IO Epoll怎么玩
先上代码
示例代码
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
/*
setnonblocking - 设置句柄为非阻塞方式
*/
int setnonblocking(int sockfd)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
{
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)
{
return -1;
}
return 0;
}
/*
handle_message - 处理每个 socket 上的消息收发
*/
int handle_message(int new_fd)
{
char buf[MAXBUF + 1];
int len;
/* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
{
printf("%d接收消息成功:'%s',共%d个字节的数据\n",
new_fd, buf, len);
}
else
{
if (len < 0)
printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
close(new_fd);
return -1;
}
/* 处理每个新连接上的数据收发结束 */
return len;
}
int main(int argc, char **argv)
{
int listener, new_fd, kdpfd, nfds, n, ret, curfds;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
struct rlimit rt;
myport = 5000;
lisnum = 2;
/* 设置每个进程允许打开的最大文件数 */
rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
printf("The MaxEpollSize is %d", rt.rlim_max);
if (setrlimit(RLIMIT_NOFILE, &rt) == -1)
{
perror("setrlimit");
exit(1);
}
else
{
printf("设置系统资源参数成功!\n");
/*
if (execl("/bin/echo","echo 'setrlimit success' >>/tmp/testlog",NULL)<0){
perror("exec");
exit(1);
}*/
}
/* 开启 socket 监听 */
if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
else
{
printf("socket 创建成功!\n");
}
setnonblocking(listener);
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = PF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)
{
perror("bind");
exit(1);
}
else
{
printf("IP 地址和端口绑定成功\n");
}
if (listen(listener, lisnum) == -1)
{
perror("listen");
exit(1);
}
else
{
printf("开启服务成功!\n");
}
//exit(1);
/* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
kdpfd = epoll_create(MAXEPOLLSIZE);
len = sizeof(struct sockaddr_in);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listener;//listen socket
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)
{
fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
return -1;
}
else
{
printf("监听 socket 加入 epoll 成功!\n");
}
curfds = 1;
while (1)
{
/* 等待有事件发生 */
nfds = epoll_wait(kdpfd, events, curfds, -1);//nfds is the num of happened events
if (nfds == -1)
{
perror("epoll_wait");
break;
}
/* 处理所有事件 */
for (n = 0; n < nfds; ++n)
{
if (events[n].data.fd == listener)
{
new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);//new_fd: client fd with detailed info
if (new_fd < 0)
{
perror("accept");
continue;
}
else
{
printf("有连接来自于: %d:%d, 分配的 socket 为:%d\n",
inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
}
setnonblocking(new_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = new_fd;
if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)
{
fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s\n",
new_fd, strerror(errno));
return -1;
}
curfds++;
}
else
{
ret = handle_message(events[n].data.fd);
if (ret < 1 && errno != 11)
{
epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
curfds--;
}
}
}
}
close(listener);
return 0;
}
示例程序结果:
Server端:
test@spark1:~/xuezh/linux/libfunc$ ./epoll2
The MaxEpollSize is 10000设置系统资源参数成功!
socket 创建成功!
IP 地址和端口绑定成功
开启服务成功!
监听 socket 加入 epoll 成功!
有连接来自于: -1929214248:52688, 分配的 socket 为:5
有连接来自于: -1929214248:52691, 分配的 socket 为:5
有连接来自于: -1929214248:52693, 分配的 socket 为:5
5接收消息成功:'hello, spark1, I'm from spark2
',共31个字节的数据
简单测一测:Client端:
test@spark2:~$ nc -v -z 172.18.8.239 4999-5001
nc: connect to 172.18.8.239 port 4999 (tcp) failed: Connection refused
Connection to 172.18.8.239 5000 port [tcp/*] succeeded!
nc: connect to 172.18.8.239 port 5001 (tcp) failed: Connection refused
test@spark2:~$ echo "hello, spark1, I'm from spark2" |nc 172.18.8.239 5000
稍微正规点测试:Client端
客户端代码:
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>
int main(int argc, char* argv[] )
{
int cfd; /* 文件描述符 */
int recbytes;
int sendbytes;
int sin_size;
char buffer[1024]={0}; /* 接受缓冲区 */
struct sockaddr_in s_add,c_add; /* 存储服务端和本端的ip、端口等信息结构体 */
//unsigned short portnum=0x8888; /* 服务端使用的通信端口,可以更改,需和服务端相同 */
unsigned short portnum=atoi( argv[2] ); /* 服务端使用的通信端口,可以更改,需和服务端相同 */
printf("Hello,welcome to client !\r\n");
/* 建立socket 使用因特网,TCP流传输 */
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == cfd)
{
printf("socket fail ! \r\n");
return -1;
}
printf("socket ok !\r\n");
printf("Address is %s\n", argv[1]);
/* 构造服务器端的ip和端口信息,具体结构体可以查资料 */
bzero(&s_add,sizeof(struct sockaddr_in));
s_add.sin_family=AF_INET;
s_add.sin_addr.s_addr= inet_addr(argv[1]); /* ip转换为4字节整形,使用时需要根据服务端ip进行更改 */
s_add.sin_port=htons(portnum); /* 这里htons是将short型数据字节序由主机型转换为网络型,其实就是
将2字节数据的前后两个字节倒换,和对应的ntohs效果、实质相同,只不过名字不同。htonl和ntohl是
操作的4字节整形。将0x12345678变为0x78563412,名字不同,内容两两相同,一般情况下网络为大端,
PPC的cpu为大端,x86的cpu为小端,arm的可以配置大小端,需要保证接收时字节序正确。
*/
printf("s_addr = %#x ,port : %#x\r\n",s_add.sin_addr.s_addr,s_add.sin_port); /* 这里打印出的是小端和我们平时看到的是相反的。 */
/* 客户端连接服务器,参数依次为socket文件描述符,地址信息,地址结构大小 */
if(-1 == connect(cfd,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
{
perror("connect fail");
return -1;
}
printf("connect ok !\r\n");
/*连接成功,从服务端接收字符*/
char *buff = "hi, i'm from client,haaaaaaaaaaaaaaaaaaaaaaaa\n";
if ( sendbytes = send(cfd, buff, 8*sizeof(buff), MSG_DONTWAIT) == -1) {
perror("send");
return -1;
}
printf("send ok\r\nREC:\r\n");
buffer[sendbytes]='\0';
printf("%s\r\n",buffer);
close(cfd); /* 关闭连接,本次通信完成 */
return 0;
}
调用客户端用脚本压:
#!/bin/bash
for((i=1;i<100;i++));
do
./epollclient 172.18.8.239 5000
done;
四、参考
http://blog.csdn.net/penzo/article/details/5986574 (非常不错,例子简单易懂。原帖无法访问了
http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html)
事件处理机制之epoll: http://blog.csdn.net/yankai0219/article/details/8453345
linux epoll模型:http://yjtjh.blog.51cto.com/1060831/294119
Epoll 程序示例: http://blog.csdn.net/vonzhoufz/article/details/20864011
Epoll精髓:http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html
更多推荐
所有评论(0)