IO複用的第三個系統調用就是epoll。
epoll并不是由一個函數來實作,而是一組函數。
一.epoll()函數
**I.**epoll函數的建立:
#include<sys/epoll.h>
int epoll_create(int size);
唯一參數size隻是給核心一個提示,告訴它需要多大的事件表;
**II.**epoll核心事件表的操作:
#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);
第一個參數fd為要操作的檔案描述符;
第二個參數op指定操作類型(往事件表中注冊,修改,删除fd上的注冊事件);
操作類型有:
EPOLL_CTL_ADD//往事件表中注冊fd上的事件
EPOLL_CTL_MOD//修改fd上的注冊事件
EPOLL_CTL_DEL//删除fd上的注冊事件
第三個參數event 指定事件,它是epoll_event結構指針類型。
struct epoll_event
{
_uint32_t events;//epoll事件
epoll_data_t data;//使用者資料
}
其中events成員描述事件類型,epoll支援的事件類型和poll基本相同。表示epoll事件類型的宏是在poll對用的宏前面加上“E”。
data成員用于存儲使用者資料,epoll_data_t是一個聯合體:
typedef_union epoll_data
{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
};
epoll_data_t是一個聯合體,其四個成員中使用最多的是fd,它指定事件所從屬的目标檔案描述符。ptr成員可以用來指定與fd相關的使用者資料。
**III.**epoll系列系統調用的主要接口:
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
//該函數成功時傳回就緒的檔案描述符的個數,失敗時傳回-1
第一個參數fd為要操作的檔案描述符;
此函數如果檢測到事件,就将所有就緒事件從核心事件表中(由epfd中的參數指定)複制到它的第二個參數events指定的數組中,這個數組隻輸出epoll_wait檢測出的就緒事件。
第三個參數maxevents指定最多監聽對少個事件。
第四個參數是epoll的逾時時間,機關為毫秒。
二.epoll的兩種模式
epoll對檔案描述符的操作有兩種模式:LT(level trigger電平觸發)和ET(edge trigger邊沿觸發)。LT模式是預設模式,LT模式與ET模式的差別如下:
LT模式:在資料到達後,無論程式是沒有接收,還是接收了,但沒有接收完,下一輪epoll_wait仍然會提醒應用程式該描述符上有資料,直到資料被接收完;
LT模式兩種方法把描述符放入就緒隊列
1.裝置驅動,調回調方法放入就緒隊列
2.把就緒的描述返還給就緒隊列
ET模式:在資料到達後,無論程式是沒有接收,還是接收了,但沒有接收完,都隻提醒一次,下一輪不在提醒應用程式該描述符上有資料。是以,要求程式在收到提醒時必須将資料接收完,否則将會出現丢掉資料的可能。
ET模式一種方法把描述符放入就緒隊列
1.裝置驅動,調回調方法放入就緒隊列
ET模在很大程度上減少了epoll事件被重複觸發的次數,是以效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個檔案句柄的阻塞讀/阻塞寫操作把處理多個檔案描述符的任務餓死。
三.代碼實作
用戶端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,);
assert(sockfd != -);
struct sockaddr_in saddr;
memset(&saddr,,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons();
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
assert(res != -);
while()
{
char buff[] = {};
printf("input:\n");
fgets(buff,,stdin);
if(strncmp(buff,"end",) == )
{
break;
}
send(sockfd,buff,strlen(buff),);
memset(buff,,);
recv(sockfd,buff,,);
printf("buff = %s\n",buff);
}
close(sockfd);
}
伺服器端:
#include<stdio.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<sys/time.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define MAXFD 10
int create_socket();
void epoll_add(int epfd,int fd);
void epoll_del(int epfd,int fd);
int main()
{
int sockfd = create_socket();
assert(sockfd != -);
int epfd = epoll_create(MAXFD);
assert(epfd != -);
epoll_add(epfd,sockfd);//向核心事件表添加檔案描述符
struct epoll_event events[MAXFD];//events數組存放就緒事件
while()
{
int n = epoll_wait(epfd,events,MAXFD,);//n就是有多少個檔案描述符就緒
if(n == -)
{
perror("epoll wait error\n");
}
else if(n == )
{
printf("timeout!\n");
continue;
}
else
{
int i = ;
for(;i < n;i++)
{
int fd = events[i].data.fd;
if(fd == -)
{
continue;
}
if(events[i].events & EPOLLIN)
{
if(fd == sockfd)//監聽套接字
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < )
{
continue;
}
printf("accept :%d\n",c);
epoll_add(epfd,c);
}
else
{
char buff[] = {};
if((recv(fd,buff,,))<=)
{
// close(fd);
epoll_del(epfd,fd);
close(fd);
printf("one client over\n");
continue;
}
printf("buff %d = %s\n",fd,buff);
send(fd,"OK",,);
}
}
}
}
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,);
if(sockfd == -)
{
return -;
}
struct sockaddr_in saddr;
memset(&saddr,,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons();
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(res == -)
{
return -;
}
listen(sockfd,);
return sockfd;
}
void epoll_add(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -)
{
perror("epoll_ctl_add error\n");
}
}
void epoll_del(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) == -)
{
perror("epoll_del_add error\n");
}
}
非阻塞的伺服器端:
#include<stdio.h>
#include<fcntl.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<sys/time.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define MAXFD 10
int create_socket();
void epoll_add(int epfd,int fd);
void epoll_del(int epfd,int fd);
void setnonblock(int fd);
int main()
{
int sockfd = create_socket();
assert(sockfd != -);
int epfd = epoll_create(MAXFD);
assert(epfd != -);
epoll_add(epfd,sockfd);//向核心事件表添加檔案描述符
struct epoll_event events[MAXFD];//events數組存放就緒事件
while()
{
int n = epoll_wait(epfd,events,MAXFD,);//n就是有多少個檔案描述符就緒
if(n == -)
{
perror("epoll wait error\n");
}
else if(n == )
{
printf("timeout!\n");
continue;
}
else
{
int i = ;
for(;i < n;i++)
{
int fd = events[i].data.fd;
if(fd == -)
{
continue;
}
if(events[i].events & EPOLLIN)
{
if(fd == sockfd)//監聽套接字
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < )
{
continue;
}
printf("accept :%d\n",c);
epoll_add(epfd,c);
}
else
{
while()
{
char buff[] = {};
int num = recv(fd,buff,,);
if(num == -)
{
send(fd,"ok",,);
break;
}
else if(num == )
{
epoll_del(epfd,fd);
close(fd);
printf("one client over\n");
}
else
{
printf("buff = %s\n",buff);
}
}
}
}
}
}
}
}
void setnonblock(int fd)
{
int oldfl = fcntl(fd,F_GETFL);
int newfl = oldfl | O_NONBLOCK;
if(fcntl(fd,F_SETFL,newfl) == -)
{
perror("fcntl error\n");
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,);
if(sockfd == -)
{
return -;
}
struct sockaddr_in saddr;
memset(&saddr,,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons();
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(res == -)
{
return -;
}
listen(sockfd,);
return sockfd;
}
void epoll_add(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = fd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -)
{
perror("epoll_ctl_add error\n");
}
setnonblock(fd);
}
void epoll_del(int epfd,int fd)
{
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = fd;
if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) == -)
{
perror("epoll_del_add error\n");
}
}
四.結果展示
這是epoll下LT模式的代碼運作結果:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXykleOdXRU1UNNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DO5UDOzEDN5EjMyQDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
ET模式其實隻需要在LT代碼中改一點:
這裡我們recv每次接收127個位元組,我們試試把它改為1個位元組:
結果我們可以發現是這樣的:
這就是我們ET模式下,當epoll_wait檢測到描述符事件發生并将此事件通知應用程式,應用程式必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程式并通知此事件,但是流式傳輸,我們沒有接受完上一個的資料,接下來還是會輸出的上個資料。
這裡有個問題:
就是一個用戶端結束并沒有像之前那樣告訴伺服器一個用戶端退出,而是發生異常終止的現象?
經過調試我們發現是在一個用戶端退出時我們的伺服器收到這樣一個信号,SIGPIPE
而這個信号在書上是這樣解釋的:
将一個socket 設定成阻塞模式和非阻塞模式,使用fcntl方法,即:
設定成非阻塞模式:
先用fcntl的F_GETFL擷取flags,用F_SETFL設定flags|O_NONBLOCK;
即:
flags = fcntl(sockfd, F_GETFL, ); //擷取檔案的flags值。
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //設定成非阻塞模式;
同時在接收和發送資料時,需要使用MSG_DONTWAIT标志
即:
在recv,recvfrom和send,sendto資料時,将flag設定為MSG_DONTWAIT。
設定成阻塞模式:
先用fcntl的F_GETFL擷取flags,用F_SETFL設定flags&~O_NONBLOCK;
即:
flags = fcntl(sockfd,F_GETFL,); //擷取檔案的flags值。
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //設定成阻塞模式;
大家可以參考這個部落格
采用非阻塞的方式循環讀取資料。
當沒有資料是傳回-1,隻要沒有傳回-1就說明有資料,我們就不斷循環的讀資料。
結果為: