IO多路复用之epoll
一、epoll简介
epoll是select和poll的增强版,
epoll使用一个文件描述符管理多个文件描述符
二、有关函数
1.
描述:用来创建一个epoll的句柄,该句柄创建成功后会占用一个fd,所以在使用完之后需要关闭
返回值:成功返回该fd的值,失败返回-1
参数:自从linux2.6.8之后,size参数是被忽略的
2.
描述:它是epoll的事件注册函数,对描述符fd所进行操作,它和select的不同在于select是在监听的时候告诉内核需要监听什么事件,epoll则事先注册监听事件的类型
返回值:成功返回0,失败返回-1
参数: epfd:epoll_create()的返回值
op:要对fd进行的操作,可用3个宏表示
EPOLL_CTL_ADD //注册新的fd到epfd中
EPOLL_CTL_MOD //修改已经注册到eofd中fd的监听事件
EPOLL_CTL_DEL //从epfd中删除fd
fd:要监听的文件描述符
evevt:用来描述fd所关心的事件并告知内核,如果op=EPOLL_CTL_DEL,忽略该参数设置为NULL
epoll_event的结构如下:
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
3.
描述:等待被监听事件的发生,并分别保存在events结构体中,类似于select的调用
返回值:大于0 成功,返回值为绪文件描述符的个数
等于0 超时,超过设定的时间timeout
小于0 失败,返回-1
参数: epfd:epoll_create()的返回值
events:保存从内核得到发生事件的集合
maxevents:告知内核,events的大小
timeout:timeout的值有三种情况,单位毫秒
timeout>0,超时后返回0;
timeout=0,立即返回,即使没有一个描述符上有事件发生也不等待;
timeout<0,阻塞等待,会一直等下去,直到有一个文件描述符上的事件发生
三、epoll工作原理
epoll先创建一个epoll_fd文件描述符
epoll_wait()返回的是就绪的文件描述符的个数,而不是描述符的总数,且内核会按顺序一次把描述符放在所以我们只需要从epoll制定的一个结构体数组中一次取得相应数量的文件描述符即可
四、epoll的2种工作模式
epoll对文件描述符的操作有两种工作模式
水平触发(LT):当有事件发生时,内核会通知应用程序,应用程序可以不立即处理,下一次epoll_wait的时候内核会再次通知应用程序此事件发生
边缘触发(ET):当有事件发生时,内核会通知应用程序,应用程序必须立即处理,否则下一次epoll_wait的时候内核不会再次通知应用程序此事件发生
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口:
i 基于非阻塞文件句柄
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成
ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件
因为epoll工作在et模式是必须使用非阻塞套接字接口,因此,我们可以使用fcntl()函数来把置描述符设置成非阻塞的
返回值:失败返回-1
F_GETFL 对应于当前文件状态标志作为函数值返回
F_SETFL 将文件状态标志设置为第三个参数的值 (取为整型值),可以更改的几个标志是: O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC
五、代码展示
server_epoll.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <assert.h>
#define _MAX_FD_NUM_ 64
#define _BLOCKLOG_ 6
typedef struct buf_ptr
{
int fd;
char ptr[1024];
}buf_t,*buf_p;
static int set_non_block(int fd)
{
int OLD_FL=fcntl(fd,F_GETFL);
if(OLD_FL<0){
perror("fcntl");
return -1;
}
if(fcntl(fd,F_SETFL,OLD_FL|O_NONBLOCK)<0){
perror("fcntl");
return -2;
}
return 0;
}
static void usage(const char *porc)
{
assert(porc);
printf("Usage:%s [ip] [port]\n",porc);
}
static int read_data(int fd,char *buf,ssize_t size)
{
assert(buf);
int index=0;
ssize_t ret=0;
while((ret=read(fd,buf+index,size-index))>0){
index+=ret;
}
if(ret<0){
if(errno==EAGAIN){
return 0;
}else{
perror("read");
return -1;
}
}else if(ret==0){
printf("client closed...\n");
return -2;
}
}
static int bind_sock(const char *ip,int port)
{
assert(ip);
int listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0){
perror("socket");
return -3;
}
//使服务器不会进入TIMEWAIT状态
int opt=1;
setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip);
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0){
perror("bind");
return -4;
}
return listen_sock;
}
static int server_handler(int listen_sock)
{
//创建epoll描述符
int epoll_fd=epoll_create(256);
if(epoll_fd<0){
perror("epoll_create");
return -6;
}
struct epoll_event ev;
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=listen_sock;
//添加监听描述符事件
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0){
perror("epoll_ctl");
return -7;
}
struct epoll_event ev_set[_MAX_FD_NUM_];
int timeout=5000;
// int timeout=-1;
char buf[1024];
int ready_num=-1;
int i=0;
int done=0;
while(!done){
//等待事件的发生
if((ready_num=epoll_wait(epoll_fd,ev_set,_MAX_FD_NUM_,timeout))<0){
perror("epoll_wait");
}else if(ready_num==0){
printf("epoll timeout...\n");
}else{
for(i=0;i<ready_num;++i){
//printf("EPOLLIN=%d,ready_num=%d\n",EPOLLIN,ready_num);
if(ev_set[i].data.fd==listen_sock && (ev_set[i].events&EPOLLIN)){
struct sockaddr_in client;
socklen_t len=sizeof(client);
//连接请求
int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock<0){
perror("accept");
continue;
}
//设置套接字为非阻塞
if(set_non_block(new_sock)<0){
printf("set noblock failed...\n");
return -8;
}
ev.data.fd=new_sock;
ev.events=EPOLLIN|EPOLLET;
//添加套接字到epoll_fd
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev)<0){
perror("epoll_ctl");
return -9;
}
printf("get a connection:%s,%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
}else if(ev_set[i].events&(EPOLLIN|EPOLLET)){
int fd=ev_set[i].data.fd;
buf_p mem=(buf_p)malloc(sizeof(buf_t));
if(!mem){
perror("malloc");
continue;
}
mem->fd=fd;
int ret=read_data(fd,mem->ptr,sizeof(mem->ptr)-1);
if(ret==0){
printf("client# %s",mem->ptr);
ev.events=EPOLLOUT|EPOLLET;
ev.data.ptr=mem;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,mem->fd,&ev);
//ev_set[i].data.ptr=(void *)mem;//can't operator to ev_set[]***************
}else if(ret==-2){
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NULL);
close(mem->fd);
free(mem);
}else{
continue;
}
}else if((ev_set[i].events&(EPOLLOUT)|EPOLLET)){
buf_p mem=(buf_p)ev_set[i].data.ptr;
int fd=mem->fd;
char *buf=mem->ptr;
if(write(fd,buf,strlen(buf))<0){
perror("write");
return -10;
}
//char *msg="HTTP/1.0 200 ok\r\n\r\nhello world:)\r\n";
//write(fd,msg,strlen(msg));
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
free(mem);
}
}
}
}
}
int main(int argc,char *argv[])
{
//检查参数
if(argc!=3){
usage(argv[0]);
return 1;
}
char *ip=argv[1];
int port=atoi(argv[2]);
//创建和绑定监听套接字
int listen_fd=bind_sock(ip,port);
if(listen_fd<0){
printf("create or bind failed!\n");
return 2;
}
//服务器进行处理
if(server_handler(listen_fd)<0){
printf("server_handler failed!\n");
}
close(listen_fd);
return 0;
}
我们使用telnet 来进行客户端登陆,运行结果如下:
上面为服务器端结果,下面为客户端结果
当服务器端运行程序时,还没有客户端连接,因此服务器端超过设定的timeout时间后会输出超时信息,而当有连接到达时,服务器端收到客户端发送的消息,回显给客户端后关闭了连接
把程序中
else if((ev_set[i].events&(EPOLLOUT)|EPOLLET))
改为
else if((ev_set[i].events&(EPOLLOUT)|EPOLLET)){
buf_p mem=(buf_p)ev_set[i].data.ptr;
int fd=mem->fd;
char *msg="HTTP/1.0 200 ok\r\n\r\nhello world:)\r\n";
write(fd,msg,strlen(msg));
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
free(mem);
}
也可以用自己写的客户端进行测试:
client_epoll.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void usage(char *proc)
{
printf("%s [ip] [port]\n",proc);
}
int creat_socket()
{
int fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0){
perror("socket");
exit(1);
}
return fd;
}
int main(int argc,char* argv[])
{
if(argc!=3){
usage(argv[0]);
exit(1);
}
int fd=creat_socket();
int _port=atoi(argv[2]);
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(_port);
inet_aton(argv[1],&addr.sin_addr);
socklen_t addrlen=sizeof(addr);
if(connect(fd,(struct sockaddr*)&addr,addrlen)<0){
perror("connect");
exit(2);
}
char buf[1024];
while(1){
memset(buf,'\0',sizeof(buf));
printf("Please Enter:");
printf("%s",fgets(buf,sizeof(buf)-1,stdin));
//if(send(fd,buf,strlen(buf),0)<0){
if(send(fd,buf,sizeof(buf)-1,0)<0){
perror("send");
continue;
}
ssize_t _size=recv(fd,buf,sizeof(buf)-1,0);
if(_size>0){
buf[_size]='\0';
printf("echo->%s",buf);
}
}
return 0;
}
我们使用浏览器来连接服务器端,结果如下:
服务器端会受到浏览器发的消息,
第一部分(第一行)为请求行,格式为:Method Request-URI HTTP-Version CRLF
第二部分为请求消息报头
接下来有一个空行
第三部分为请求正文
服务器给浏览器发送HTTP响应,也是由三个部分组成分别是:
第一部分为状态行
第二部分为消息报头
第三部分为响应正文
转载于:https://blog.51cto.com/lingdandan/1785221