epoll是Linux特有的I/O復(fù)用函數(shù),它在實(shí)現(xiàn)和使用上與select和poll有很大差異。epoll使用一組函數(shù)來完成任務(wù),而不是單個(gè)函數(shù)。epoll把用戶關(guān)心的文件描述符上的事件放在內(nèi)核的一個(gè)事件表中,無需像select和poll那樣每次調(diào)用都要重復(fù)傳入文件描述符集或事件集,但epoll需要一個(gè)額外的文件描述符,來唯一標(biāo)示內(nèi)核中的這個(gè)事件表,這個(gè)文件描述符使用epoll_create函數(shù)來創(chuàng)建。

epoll是一種高效的管理socket的模型,相對(duì)于select和poll來說具有更高的效率和易用性。epoll的性能不會(huì)隨socket數(shù)量的增加而下降。
下面我們來說說epoll的使用:
epoll所使用的數(shù)據(jù)結(jié)構(gòu)如下:

結(jié)構(gòu)體epoll_event被用于注冊(cè)感興趣的事件和回傳所發(fā)生待處理的事件,epoll_data聯(lián)合體用來保存觸發(fā)事件的某個(gè)文件描述符相關(guān)的數(shù)據(jù)。例如一個(gè)client連接到服務(wù)器,服務(wù)器通過調(diào)用accept函數(shù)可以得到這個(gè)client對(duì)應(yīng)的socket文件描述符,可以把這個(gè)文件描述符賦給epoll_data的fd字段,以便以后的讀寫操作在這個(gè)文件描述符上進(jìn)行。epoll_data_t是一個(gè)聯(lián)合體,其中4個(gè)成員最多用的就是fd,它指定事件所從屬的目標(biāo)文件描述符,ptr成員可用來指定與fd相關(guān)的用戶數(shù)據(jù)。但由于epoll_data_t是一個(gè)聯(lián)合體,我們不能同時(shí)使用其ptr成員和fd成員,因此我們要將文件描述符和用戶數(shù)據(jù)關(guān)聯(lián)起來,以實(shí)現(xiàn)快熟的數(shù)據(jù)訪問,只能使用其他手段,我們?cè)谙旅娴某绦蛑凶远x了個(gè)結(jié)構(gòu)體,里面有我們所關(guān)心的fd和保存用戶數(shù)據(jù)的buf。
events字段是表示感興趣的事件,被觸發(fā)的事件的可能取值為:
EPOLLIN:表示對(duì)應(yīng)的文件描述符可以讀;
EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫;
EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀;
EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;
EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷;
EPOLLET:表示將EPOLL設(shè)置為邊緣觸發(fā)模式;
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里。
所用到的函數(shù)有如下三個(gè):
1.epoll_create函數(shù):
原型:int epoll_create(int size)
該函數(shù)生成一個(gè)epoll專用的文件描述符,size參數(shù)指定生成描述符的大范圍。size參數(shù)現(xiàn)在并不起作用,使用紅黑樹來管理所有的文件描述符,該函數(shù)返回的文件描述符將用作其他所有的epoll系統(tǒng)調(diào)用的第一個(gè)參數(shù),
2.epoll_ctl函數(shù):
原型:int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
該函數(shù)用于控制某個(gè)文件描述符上的事件,可以注冊(cè)事件,修改事件,刪除事件。
參數(shù):epfd:由epoll_create生成的epoll專用的文件描述符
op:要進(jìn)行的操作,可能的取值有:
EPOLL_CTL_ADD 注冊(cè)
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 刪除
fd:關(guān)聯(lián)的文件描述符
event:指向epoll_event的指針
調(diào)用成功返回0,失敗返回-1;
3.epoll_wait函數(shù):
原型:int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
該函數(shù)用于輪詢I/O事件的發(fā)生,調(diào)用成功時(shí)返回就緒的文件描述符的個(gè)數(shù),失敗時(shí)返回-1,并設(shè)置errno。該函數(shù)如果檢測(cè)到事件,就將所有就緒的事件從內(nèi)核表中(由epfd參數(shù)指定)復(fù)制到它的第二個(gè)參數(shù)events指向的數(shù)組中,這個(gè)數(shù)組只用于輸出epoll_wait檢測(cè)到的就緒事件。
參數(shù):
epfd:由epoll_create生成的epoll專用的文件描述符
events:用于回傳待處理的數(shù)組
maxevent:每次能處理的事件數(shù)
timeout:與poll接口的timeout參數(shù)相同,是超時(shí)時(shí)間,0會(huì)立即返回,-1是永久阻塞。
如果該函數(shù)調(diào)用成功,返回對(duì)應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,如果返回0表示已超時(shí)。
接下來我們來說說epoll的工作原理:
epoll同樣只告訴那些已就緒的文件描述符,而且當(dāng)我們調(diào)用epoll_wait獲得就緒文件描述符時(shí),返回的不是實(shí)際的描述符,而是一個(gè)代表就緒描述符數(shù)量的值,我們只需去epoll指定的數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里使用內(nèi)存映射技術(shù),節(jié)省了系統(tǒng)調(diào)用時(shí)的開銷。兩一個(gè)本質(zhì)的改進(jìn)在與epoll采用基于事件的就緒通知方式,在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過epoll_ctl來注冊(cè)一個(gè)文件描述符,一旦某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用回調(diào)機(jī)制,循序激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait時(shí)便得到通知。
LT模式和ET模式:
epoll對(duì)于文件描述符的操作有兩種模式,LT(水平觸發(fā))和ET(邊緣觸發(fā))。LT模式是默認(rèn)的工作模式,這種模式下epoll相當(dāng)于一個(gè)效率較高的poll。當(dāng)往epoll內(nèi)核事件表中注冊(cè)一個(gè)文件描述符上的EPOLLET事件時(shí),epoll將以ET模式來操作該文件描述符。ET模式是epoll的高效工作模式。
對(duì)于采用LT工作模式的文件描述符,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理該事件。這樣,當(dāng)應(yīng)用程序下一次調(diào)用epoll_wait時(shí),epoll_wait還會(huì)再次向應(yīng)用程序告知該事件,直到該事件被處理。而采用ET模式的文件描述符,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,因?yàn)楹罄m(xù)的epoll_wait調(diào)用將不再向應(yīng)用程序通知這一事件,。這樣,ET模式在很大程度上降低了同一個(gè)epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率比LT模式要高。
我們?cè)谙旅娴木幊讨羞€用到了一個(gè)fcntl函數(shù),該函數(shù)原型如下:
int fcntl(int fd,int cmd,...)
該函數(shù)可以執(zhí)行各種描述符控制操作,它提供的與網(wǎng)絡(luò)編程相關(guān)的特性如下:
1.非阻塞式I/O。通過使用F_SETFL命令設(shè)置O_NONBLOCK文件狀態(tài)標(biāo)志,我們可以把一個(gè)套接字設(shè)置為非阻塞。
2.信號(hào)驅(qū)動(dòng)式I/O。通過使用F_SETFL命令設(shè)置O_ASYNC文件狀態(tài)標(biāo)志,我們可以把一個(gè)套接字設(shè)置成一旦其狀態(tài)發(fā)生變化,內(nèi)核就產(chǎn)生一個(gè)SIGIO信號(hào)。
3.F_SETOWN命令允許我們指定用于接收SIGIO和SIGURG信號(hào)的套接字屬主。其中SIGIO信號(hào)時(shí)套接字被設(shè)置為信號(hào)驅(qū)動(dòng)式I/O型后產(chǎn)生的,SIGURG信號(hào)時(shí)在新的帶外數(shù)據(jù)到達(dá)套接字時(shí)產(chǎn)生的。F_GETOWN返回套接字的當(dāng)前屬主。
接下來我們看看基于epoll的socket編程代碼:(基于LT模式下的阻塞模式)
客戶端給服務(wù)端發(fā)送消息。服務(wù)端回顯給客戶端:
server端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<errno.h>
#include<fcntl.h>
#define _BACKLOG_ 5
#define _BUF_SIZE_ 10240
#define _MAX_ 64
typedef struct _data_buf
{
int fd;
char buf[_BUF_SIZE_];
}data_buf_t,*data_buf_p;
static void usage(const char* proc)
{
printf("usage:%s[ip][port]\n",proc);
}
static int start(int port,char *ip)
{
assert(ip);
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
struct sockaddr_in local;
local.sin_port=htons(port);
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(ip);
int opt=1; //設(shè)置為接口復(fù)用
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(2);
}
if(listen(sock,_BACKLOG_)<0)
{
perror("listen");
exit(3);
}
return sock;
}
static int epoll_server(int listen_sock)
{
int epoll_fd=epoll_create(256);//生成一個(gè)專用的epoll文件描述符
if(epoll_fd<0)
{
perror("epoll_create");
exit(1);
}
struct epoll_event ev;//用于注冊(cè)事件
struct epoll_event ret_ev[_MAX_];//數(shù)組用于回傳要處理的事件
int ret_num=_MAX_;
int read_num=-1;
ev.events=EPOLLIN;
ev.data.fd=listen_sock;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)//用于控制某個(gè)文件描述符上的事件(注冊(cè),修改,刪除)
{
perror("epoll_ctl");
return -2;
}
int done=0;
int i=0;
int timeout=5000;
struct sockaddr_in client;
socklen_t len=sizeof(client);
while(!done)
{
switch(read_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout))//用于輪尋I/O事件的發(fā)生
{
case0:
printf("time out\n");
break;
case -1:
perror("epoll");
exit(2);
default:
{
for(i=0;i<read_num;++i)
{
if(ret_ev[i].data.fd==listen_sock&&(ret_ev[i].events&EPOLLIN))
{
int fd=ret_ev[i].data.fd;
int new_sock=accept(fd,(struct sockaddr*)&client,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
ev.events=EPOLLIN;
ev.data.fd=new_sock;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
printf("get a new client...\n");
}
else //normal sock
{
if(ret_ev[i].events&EPOLLIN)
{
int fd=ret_ev[i].data.fd;
data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t));
if(!mem)
perror("malloc");
continue;
}
mem->fd=fd;
memset(mem->buf,'\0',sizeof(mem->buf));
ssize_t _s=read(mem->fd,mem->buf,sizeof(mem -> buf)-1);
if(_s>0)
{
mem->buf[_s-1]='\0';
printf("client: %s\n",mem->buf);
ev.events=EPOLLOUT;
ev.data.ptr=mem;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
}
else if(_s==0)
{
printf("client close...\n");
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
free(mem);
}
else
{
continue;
}
}
else if(ret_ev[i].events&EPOLLOUT) //寫事件準(zhǔn)備就緒
{
data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr;
int fd=mem->fd;
char *buf=mem->buf;
write(fd,buf,strlen(buf));
ev.events=EPOLLIN; //寫完,下次關(guān)心讀事件
ev.data.fd=fd;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
}
else{
//....
}
}
}
}
break;
}
}
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}
int port=atoi(argv[2]);
char *ip=argv[1];
int listen_sock=start(port,ip);
epoll_server(listen_sock);
close(listen_sock);
return 0;
}client端:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
static void usage(const char* arg)
{
printf("usage:%s [ip][port]",arg);
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
usage(argv[0]);
exit(1);
}
int port=atoi(argv[2]);
char *ip=argv[1];
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in remote;
remote.sin_family=AF_INET;
remote.sin_port=htons(port);
remote.sin_addr.s_addr=inet_addr(ip);
int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));
char buf[1024];
while(1)
{
printf("please enter: ");
fflush(stdout);
ssize_t _s=read(0,buf,sizeof(buf)-1);
buf[_s]='\0';
write(sock,buf,sizeof(buf)-1);
memset(buf,'\0',sizeof(buf));
read(sock,buf,sizeof(buf)-1);
printf("echo:%s\n",buf);
}
return 0;
}運(yùn)行結(jié)果:
我們可以看到,客戶端發(fā)給服務(wù)端的數(shù)據(jù),被服務(wù)端收到后,回顯給客戶端。
接下來我們把程序改為ET模式非阻塞模式:
主要改的地方有:
1.因?yàn)镋T模式只通知一次,所以我們?cè)谧x取數(shù)據(jù)的時(shí)候必須一次讀完,我們寫的read_data函數(shù)就是實(shí)現(xiàn)這個(gè)功能的;
2.把所有的描述符都改為非阻塞模式,調(diào)用我們的set_no_block函數(shù);
3.注冊(cè)事件的時(shí)候,要與上EPOLLET;
具體如下:
server端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<errno.h>
#include<fcntl.h>
#define _BACKLOG_ 5
#define _BUF_SIZE_ 10240
#define _MAX_ 64
typedef struct _data_buf
{
int fd;
char buf[_BUF_SIZE_];
}data_buf_t,*data_buf_p;
static void usage(const char* proc)
{
printf("usage:%s[ip][port]\n",proc);
}
static int set_no_block(int fd) //用來設(shè)置非阻塞
{
int old_fl=fcntl(fd,F_GETFL);
if(old_fl<0)
{
perror("perror");
return -1;
}
if(fcntl(fd,F_SETFL,old_fl|O_NONBLOCK))
{
perror("fcntl");
return -1;
}
return 0;
}
int read_data(int fd,char* buf,int size)//ET模式下讀取數(shù)據(jù),必須一次讀完
{
assert(buf);
int index=0;
ssize_t _s=-1;
while((_s=read(fd,buf+index,size-index))<size)
{
if(errno==EAGAIN)
{
break;
}
index += _s;
}
return index;
}
static int start(int port,char *ip)
{
assert(ip);
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
struct sockaddr_in local;
local.sin_port=htons(port);
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(ip);
int opt=1; //設(shè)置為接口復(fù)用
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(2);
}
if(listen(sock,_BACKLOG_)<0)
{
perror("listen");
exit(3);
}
return sock;
}
static int epoll_server(int listen_sock)
{
int epoll_fd=epoll_create(256);//生成一個(gè)專用的epoll文件描述符
if(epoll_fd<0)
{
perror("epoll_create");
exit(1);
}
set_no_block(listen_sock);//設(shè)置監(jiān)聽套接字為非阻塞
struct epoll_event ev;//用于注冊(cè)事件
struct epoll_event ret_ev[_MAX_];//數(shù)組用于回傳要處理的事件
int ret_num=_MAX_;
int read_num=-1;
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=listen_sock;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)//用于控制某個(gè)文件描述符上的事件(注冊(cè),修改,刪除)
{
perror("epoll_ctl");
return -2;
}
int done=0;
int i=0;
int timeout=5000;
struct sockaddr_in client;
socklen_t len=sizeof(client);
while(!done)
{
switch(read_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout))//用于輪尋I/O事件的發(fā)生
{
case0:
printf("time out\n");
break;
case -1:
perror("epoll");
exit(2);
default:
{
for(i=0;i<read_num;++i)
{
if(ret_ev[i].data.fd==listen_sock&&(ret_ev[i].events&EPOLLIN))
{
int fd=ret_ev[i].data.fd;
int new_sock=accept(fd,(struct sockaddr*)&client,&len);
if(new_sock<0)
{
perror("accept");
continue;
}
set_no_block(new_sock);//設(shè)置套接字為非阻塞
ev.events=EPOLLIN|EPOLLET;
ev.data.fd=new_sock;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
printf("get a new client...\n");
}
else //normal sock
{
if(ret_ev[i].events&EPOLLIN)
{
int fd=ret_ev[i].data.fd;
data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t));
if(!mem)
{
perror("malloc");
continue;
}
mem->fd=fd;
memset(mem->buf,'\0',sizeof(mem->buf));
ssize_t _s=read_data(mem->fd,mem->buf,sizeof(mem -> buf)-1);//一次讀完
if(_s>0)
{
mem->buf[_s-1]='\0';
printf("client: %s\n",mem->buf);
ev.events=EPOLLOUT|EPOLLET;
ev.data.ptr=mem;
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
}
else if(_s==0)
{
printf("client close...\n");
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
free(mem);
}
else
{
continue;
}
}
else if(ret_ev[i].events&EPOLLOUT) //寫事件準(zhǔn)備就緒
{
data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr;
char* msg="http/1.0 200 ok\r\n\r\nhello bit\r\n";
int fd=mem->fd;
write(fd,msg,strlen(msg));
close(fd);
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,&ev); //寫完服務(wù)端直接退出
free(mem);
}
else{
//....
}
}
}
}
break;
}
}
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}
int port=atoi(argv[2]);
char *ip=argv[1];
int listen_sock=start(port,ip);
epoll_server(listen_sock);
close(listen_sock);
return 0;
}我們?cè)跒g覽器上訪問我們的服務(wù)器程序,當(dāng)服務(wù)器發(fā)送給瀏覽器數(shù)據(jù)后,服務(wù)端關(guān)閉連接關(guān)閉連接結(jié)果如下:

至此,完。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開啟,新人活動(dòng)云服務(wù)器買多久送多久。
網(wǎng)頁(yè)題目:epoll實(shí)現(xiàn)socket通信-創(chuàng)新互聯(lián)
轉(zhuǎn)載來源:http://www.chinadenli.net/article34/dgecse.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁(yè)設(shè)計(jì)公司、網(wǎng)站排名、企業(yè)建站、品牌網(wǎng)站制作、外貿(mào)建站、標(biāo)簽優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容