epoll模型可以說是select模型和poll模型的升級版,但epoll要求在linux內核版本2.6以上。
相對於,select和poll來說,epoll更加靈活,沒有描述符限制。相比於select和poll,epoll不會因爲監聽的描述符數目變多而導致輪詢過多(耗時太多),不會因爲fd的數目增大而降低響應效率。
另外,對於select的最大描述符,linux會有限制,在linux/posix_types.h頭文件有這樣的聲明:
#define __FD_SETSIZE 1024
表示select最多同時監聽1024個fd,當然,可以通過修改頭文件再重編譯內核來擴大這個數目,但這似乎並不治本。
epoll使用一個描述符去管理多個套接字(多個描述符),其實這個管理其他套接字的描述符就相當於一個handle,將用戶關係的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核結構之間的數據拷貝只需一次。
使用epoll模型需要包含頭文件
#include <sys/epoll.h>
epoll提供了3個函數
(1) int epoll_create(int size);
epoll_create創建了一個epoll句柄(handle,或稱爲實例),這個實例本質上也是一個文件描述符。參數size是通知內核要監聽的描述符數量,但自從linux 內核2.6.8後,這個參數是忽略的,只有設置爲大於0就可以。
參數size的監聽數量與select的第一個參數有所不同,select的第一個參數要求是監聽的描述符中最大值+1,當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡(其實就是必須銷燬句柄的意思)。
當epoll_create返回-1時,表示epoll_create失敗,同時會去設置errno,返回非負值,也就是文件描述符,表示epoll_create成功。
(2) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件註冊函數epoll_ctl的主要功能是添加描述符及爲相關描述符的註冊事件。
它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
第一個參數 epfd就是epoll_create創建出的epoll句柄
第二個參數op屬於操作宏,有三個類型:
a. EPOLL_CTL_ADD
註冊新的fd(第三個參數)到epfd中;
b. EPOLL_CTL_MOD
改變fd的已經註冊過的事件。(給fd設置一個新的事件)
c. EPOLL_CTL_DEL
從epoll句柄管理的描述符集中刪除fd
第三個參數fd就是操作的文件描述符。
第四個參數event是struct epoll_event *結構體指針。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
struct epoll_event 結構體成員events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level
Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
epoll_ctl返回值:
成功返回值爲0,失敗返回值爲-1.
(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll_wait靈活等待事件的到來,類似於select()調用。參數events用來從內核得到事件的集合(傳入還是傳出的),maxevents告知內核這個events有多大,這個 maxevents的值不能大於創建epoll_create()時的size,參數timeout是超時時間(單位毫秒,0會立即返回,-1永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
然後詳細解釋ET, LT:
LT(level triggered)是缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.
ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再爲就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認(這句話不理解)
使用epoll
服務器端代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/epoll.h>
extern int errno;
#define MaxConnectNum (5)
#define EPOLL_SIZE (1000)
#define EPOLL_EVENT_SIZE (100)
int main()
{
int domain = AF_INET;
int type = SOCK_STREAM;
int protocol = 0;
int ret = -1;
int nListenFd = -1;
int nNewClientFd = -1;
short int port = 2000;
struct sockaddr_in addr_in;
int backlog = 128; // 默認是128
int len = 0;
char chBuffer[1024] = {0};
int flags = 0;
int nMaxFd = -1;
int i = 0;
static int s_nCountClient = 0;
int epoll_handle_fd = -1;
struct epoll_event stuEventTmp;
struct epoll_event stuEventArray[EPOLL_EVENT_SIZE];
nListenFd = socket( domain, type, protocol);
if(nListenFd < 0)
{
printf("\n socket failed ! errno[%d] err[%s]\n", errno, strerror(errno));
return -1;
}
memset(&addr_in, 0, sizeof(struct sockaddr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(port);//htons的返回值是16位的網絡字節序整型數 htons尾的字母s代表short
addr_in.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(nListenFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("\n bind failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免資源泄漏
return -1;
}
ret = listen(nListenFd, backlog);
if(ret < 0)
{
printf("\n listen failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免資源泄漏
return -1;
}
epoll_handle_fd = epoll_create(EPOLL_SIZE);
if(-1 == epoll_handle_fd)
{
printf("\n epoll_create failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nListenFd); //避免資源泄漏
return -1;
}
//添加listenfd
// int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
memset(&stuEventTmp, 0, sizeof(struct epoll_event));
stuEventTmp.data.fd = nListenFd;
stuEventTmp.events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nListenFd,&stuEventTmp );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
close(nListenFd); //避免資源泄漏
return -1;
}
while(1)
{
int num = 0;
int timeout = 5000;
num = epoll_wait(epoll_handle_fd, stuEventArray, EPOLL_EVENT_SIZE,timeout);//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
if(num < 0)
{
break;
}
else if(num == 0)
{
printf("\n epoll time out\n");
continue;
}
else
{
for(i=0; i < num; i++)
{
if(stuEventArray[i].data.fd == nListenFd)
{
nNewClientFd = accept(nListenFd, ( struct sockaddr *)NULL, NULL); //阻塞模式
if(nNewClientFd < 0)
{
printf("\n accept failed ! errno[%d] err[%s]\n", errno, strerror(errno));
//close(nListenFd); //避免資源泄漏
break;
}
//添加
memset(&stuEventTmp, 0, sizeof(struct epoll_event));
stuEventTmp.data.fd = nNewClientFd;
stuEventTmp.events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_ADD, nNewClientFd,&stuEventTmp );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
}
else if( stuEventArray[i].data.fd != -1)
{
if((stuEventArray[i].events & EPOLLIN))
{
//READ DATA
len = recv(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags爲0,阻塞模式
if(len <= 0)
{
printf("\n recv failed ! errno[%d] err[%s] len[%d]\n", errno, strerror(errno),len);
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
close(stuEventArray[i].data.fd);
continue;
}
printf("\n recv [%s] len[%d]\n", chBuffer,len);
stuEventArray[i].events = EPOLLOUT ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
#if 1
if(stuEventArray[i].events & EPOLLOUT )
{
//SEND DATA
len = send(stuEventArray[i].data.fd , chBuffer, sizeof(chBuffer) , flags);//flags爲0,阻塞模式
if(len <= 0)
{
printf("\n send failed ! errno[%d] err[%s] len[%d]\n", errno, strerror(errno),len);
close(stuEventArray[i].data.fd);
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_DEL, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
printf("\n send [%s] len[%d]\n", chBuffer,len);
stuEventArray[i].events = EPOLLIN ;
ret = epoll_ctl(epoll_handle_fd, EPOLL_CTL_MOD, stuEventArray[i].data.fd,&stuEventArray[i] );
if(-1 == ret)
{
printf("\n epoll_ctl failed ! errno[%d] err[%s] __LINE__[%d]\n", errno, strerror(errno),__LINE__);
break;
}
continue;
}
#endif
}
}
if(i != num )
{
printf("\n if(i != num ) \n");
break;
}
}
}
return 0;
}
客戶端代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
extern int errno;
int main()
{
int domain = AF_INET;//AF_INET
int type = SOCK_STREAM;
int protocol = 0;
int ret = -1;
int nClientFd = -1;
short int port = 2000;
struct sockaddr_in addr_in;
int len = 0;
char chBuffer[1024] = {0};
int flags = 0;
char * pchServerIP = "192.168.1.211";
nClientFd = socket( domain, type, protocol);
if(nClientFd < 0)
{
printf("\n socket failed ! errno[%d] err[%s]\n", errno, strerror(errno));
return -1;
}
memset(&addr_in, 0, sizeof(struct sockaddr_in));
addr_in.sin_family = AF_INET;
addr_in.sin_port = htons(port);//htons的返回值是16位的網絡字節序整型數 htons尾的字母s代表short
//addr_in.sin_addr.s_addr = htonl(inet_addr(pchServerIP)); //錯誤的做法
addr_in.sin_addr.s_addr = inet_addr(pchServerIP);
ret = connect(nClientFd, ( struct sockaddr * )(&addr_in), sizeof(struct sockaddr_in));
if(ret < 0)
{
printf("\n connect failed ! errno[%d] err[%s]\n", errno, strerror(errno));
close(nClientFd); //避免資源泄漏
return -1;
}
printf("\n connect success ! \n");
for(;;)
{
len = send(nClientFd, "2", sizeof("2"), flags);
sleep(2);
}
close(nClientFd);
return 0;
}