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>
接下來,我們用瀏覽器看看: