epoll是一个Linux内核 的系统调用,一个可扩展的I / O事件通知机制。它的功能是监控多个文件描述符,看看I / O有可能在其中任何一个。它是为了取代旧的POSIX select和系统调用,实现了更为苛刻的应用更好的性能,在观看了数文件描述符较大(不同于旧的系统调用,这在操作的时间O(N),工作在O(1)的时间 )。类似的FreeBSD的,因为它操作的配置的内核对象上,暴露于用户空间作为其自身的一个文件描述符。
一个简单的epoll代码如下。该程序和select的例子一样,故不在多介绍,读者可详细看看
<span style="font-size:18px;">#include<stdio.h>
#include<sys/socket.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/epoll.h>
static void guse(const char* prac)
{
printf("%s [ip] [port]..\n",prac);
}
static int my_select(const char* _ip, const int _port){ //获得socket套接字
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("create socket error...");
exit(1);
}
struct sockaddr_in server_socket;
bzero(&server_socket,sizeof(server_socket));
server_socket.sin_family = AF_INET;
server_socket.sin_addr.s_addr =inet_addr(_ip);
server_socket.sin_port = htons(_port);
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0)
{
perror("bind error ...");
exit(2);
}
if(listen(sock,5) < 0 ){
perror("listen errno ...");
close(sock);
exit(3);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
guse(argv[0]);
exit(0);
}
int listen_sock = my_select(argv[1], atoi(argv[2]));
int fdep = epoll_create(256); //创建epoll
if(fdep < 0){
perror("epoll_creact...\n");
exit(4);
}
struct epoll_event _ev,revent[20];
_ev.events = EPOLLIN;
_ev.data.fd = listen_sock;
if(epoll_ctl(fdep,EPOLL_CTL_ADD,listen_sock,&_ev)< 0){ //配置注册函数
perror("epoll _ctl is errno ..\n");
exit(5);
}
int times = -1;
int fdnum;
while(1){
int revent_size = sizeof(revent)/sizeof(revent);
switch(fdnum = epoll_wait(fdep,revent,revent_size,times)){ //收集等待事件
case -1:
printf("selsect errno..\n");
exit(6);
break;
case 0:
printf("Time out \n");
break;
default:
{
int index = 0;
int new_fd = 0;
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
char buf[1024];
for(;index < fdnum; index++){
int fd = revent[index].data.fd;
if(listen_sock == fd && revent[index].events & EPOLLIN){
new_fd =accept(listen_sock,(struct sockaddr*)&cli,&len);
if(new_fd >0 ){ //接受了一个新的链接
printf("%s:%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
_ev.data.fd = new_fd;
_ev.events = EPOLLIN;
if(epoll_ctl(fdep,EPOLL_CTL_ADD,new_fd,&_ev) <0 ){ //再把这个链接增加到epoll
perror("accept error...\n");
close(fd);
exit(7);
}
}
}else{
if(revent[index].events & EPOLLIN){ //如果是普通的,便输出
memset(buf,'\0',sizeof(buf));
ssize_t _s = recv(fd,buf,sizeof(buf)-1,0);
if(_s == 0){
printf("client close ...\n");
epoll_ctl(fdep,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}
else if(_s < 0)
{
printf("errno read..\n");
}
else{
printf("client:%s",buf);
}
}
}
}
}
}
break;
}
}
return 0;
}</span>
epoll同时提供了边沿触发和电平触发模式。在边沿触发模式,调用epoll_wait将返回只有当一个新的事件排队的epoll对象,而在电平触发模式,epoll_wait将只要条件成立返回。
例如,如果一个管到与登记的epoll,已接收到数据,则调用epoll_wait将返回,信令数据的存在被读取。假设读者仅消耗来自缓冲器的数据的一部分。在电平触发模式下,还呼吁epoll_wait将立即返回,只要管道缓冲区包含要读取的数据。在边沿触发模式,但是,epoll_wait将只一次新数据写入到管道返回。
下面便是给ET模式的代码,只是在上面代码的情况下做出了更改:
<span style="font-size:18px;">#include<stdio.h>
#include<sys/socket.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<sys/time.h>
#include<sys/epoll.h>
#include <fcntl.h>
static void guse(const char* prac)
{
printf("%s [ip] [port]..\n",prac);
}
static int read_data(int fd, char buf[]){ //对读的封装,考虑考ET的特性,故而,要一次将数据读完
int nread = 0;
int n = 0;
while(nread = (recv(fd,buf,128,0)) > 0){
n = nread;
}
return nread;
}
static int set_noblock(int sock){ //使其阻塞的等待
int fl = fcntl(sock,F_GETFL);
return fcntl(sock,F_SETFL,fl|O_NONBLOCK);
}
static int my_select(const char* _ip, const int _port){
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("create socket error...");
exit(1);
}
struct sockaddr_in server_socket;
bzero(&server_socket,sizeof(server_socket));
server_socket.sin_family = AF_INET;
server_socket.sin_addr.s_addr =inet_addr(_ip);
server_socket.sin_port = htons(_port);
int opt = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0)
{
perror("bind error ...");
exit(2);
}
if(listen(sock,5) < 0 ){
perror("listen errno ...");
close(sock);
exit(3);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
guse(argv[0]);
exit(0);
}
int listen_sock = my_select(argv[1], atoi(argv[2]));
int fdep = epoll_create(256);
if(fdep < 0){
perror("epoll_creact...\n");
exit(4);
}
struct epoll_event _ev,revent[128];
_ev.events = EPOLLIN;
_ev.data.fd = listen_sock;
if(epoll_ctl(fdep,EPOLL_CTL_ADD,listen_sock,&_ev)< 0){
perror("epoll _ctl is errno ..\n");
exit(5);
}
int revent_size = sizeof(revent)/sizeof(revent);
int times = -1;
int fdnum;
while(1){
switch(fdnum = epoll_wait(fdep,revent,revent_size,times)){
case -1:
printf("epoll errno..\n");
exit(6);
break;
case 0:
printf("Time out \n");
break;
default:
{
int index = 0;
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
char buf[1024];
for(;index < fdnum; index++){
int fd = revent[index].data.fd;
if(listen_sock == fd && revent[index].events & EPOLLIN){
int new_fd =accept(listen_sock,(struct sockaddr*)&cli,&len);
if(new_fd >0 ){
printf("%s:%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
_ev.data.fd = new_fd;
_ev.events = EPOLLIN | EPOLLET;
set_noblock(new_fd);
epoll_ctl(fdep,EPOLL_CTL_ADD,new_fd,&_ev);
}
}else{
if(revent[index].events & EPOLLIN){
memset(buf,'\0',sizeof(buf));
int ret = read_data(fd,buf);
if(ret < 0){
printf("###############\n");
printf("client close ...\n");
}
else if(ret == 0)
{
printf("client close ..\n");
}
else{
printf("recv ...\n");
}
}
_ev.data.fd = fd;
_ev.events = revent[index].events | EPOLLOUT;
if(epoll_ctl(fdep,EPOLL_CTL_MOD,fd,&_ev)<0){
perror("MOD ...\n");
}
if(revent[index].events & EPOLLOUT){
const char *msg = "HTTP/1.1 200 OK\r\n\r\n<h1>hello world 0.0</h1>\r\n";
send(fd,msg,strlen(msg),0);
epoll_ctl(fdep ,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}
}
}
break;
}
}
}
return 0;
}
}</span>
接下来,我们用浏览器看看: