EPOLL简介
EPOLL是linux下公认的最好用的I/O就绪通知方式。
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显着提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
EPOLL 相关的三个系统调用
int epoll_create(int size)
创建epoll句柄,占用一个fd值,使用完epoll 后必须调用close关闭
int epoll_ctl(int epfd, int op, int fd, struct epoll_events* event)
epoll的事件注册函数,告诉内核监听什么事件。
- 第一个参数epfd是上一个函数create的返回值。
-
第二个参数表示动作,用三个宏表示EPOLL_CTL_ADD注册新的fd到epfd中。
EPOLL_CTL_MOD修改已注册的fd监听事件。EPOLL_CTL_DELepfd中删除一个fd事件。
- 第三个参数是需要监听的fd
- 第四个参数是需要监听什么事件。
struct epoll_event {
_uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
events可以是以下几个宏的集合
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
timeout是超时时间(0会立即返回,-1将永久阻塞)。如果调用成功,则返回对应IO上已准备好文件描述符数目。
工作原理
epoll只告知那些已经就绪的文件描述符,通过调用epoll_wait()函数获得就绪的文件描述符时,返回的不是实际的文件描述符,而是就绪描述符数量的值,只需要去epoll指定一个数组去获得这些文件描述符,省去了这些描述符在系统调用时的开销。
*************************************************************************
> File Name: epoll.c
> Author: weierxiao
> Mail: @qq.com
> Created Time: Tue Jul :: PM CST
************************************************************************
#include<stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<string.h>
#include<sys/epoll.h>
#include<stdlib.h>
#define EPOLL_REVS_SIZE 64
static void Usage(const char* proc)
{
printf("Usage : %s, [local ip], [local port]\n", proc);
}
int startup(char *ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, );
if (sock < )
{
perror("sock");
exit();
}
int opt = ;
setsockopt(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(sock, (struct sockaddr*)&local, sizeof(local))<)
{
perror("bind");
exit();
}
if (listen(sock, ) < )
{
perror("listen");
exit();
}
return sock;
}
int main(int argc, char *argv[])
{
if (argc != )
{
Usage(argv[]);
return ;
}
int listen_sock = startup(argv[],atoi(argv[]));
int epfd = epoll_create();
if (epfd < )
{
perror("epoll create!\n");
return ;
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev );
int nums = -;
struct epoll_event revs[EPOLL_REVS_SIZE];
int timeout =-;
while ()
{
switch ( (nums = epoll_wait(epfd,revs, EPOLL_REVS_SIZE, timeout )))
{
case :
printf("time out ...\n");
break;
case -:
perror("epoll wait\n");
break;
default:
{
int i =;
for (;i< nums; i++)
{
int sock = revs[i].data.fd;
if (sock == listen_sock && (revs[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 < )
{
perror("accept !\n");
continue;
}
printf(" get client :[%s] [%d]\n", inet_ntoa(client.sin_addr),htons(client.sin_port));
ev.events = EPOLLIN;
ev.data.fd = new_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
}
else if(sock != listen_sock)
{
if (revs[i].events & EPOLLIN)
{
char *buf[];
ssize_t s = read(sock, buf, sizeof(buf)-);
if (s > )
{
printf("client : %s\n", buf);
ev.events = EPOLLOUT;
ev.data.fd = sock;
epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev);
}
else if (s == )
{
printf("client quit\n");
close(sock);
epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
}
else{
perror("read");
close(sock);
epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
}
}
else if (revs[i]. events & EPOLLOUT)
{
const char *msg = "HTTP/1.0 OK 200 \r\n\r\n<html><h1>WX EPOLL </h1></html>";
write(sock,msg, strlen(msg));
close(sock);
epoll_ctl(epfd, EPOLL_CTL_DEL,sock,NULL);
}
else
{}
}
}
}
break;
}
}
return ;
}
打开手机或电脑的浏览器,连接同一个局域网,输入绑定的服务器IP地址:绑定的端口号
