1、前言
之前我有寫過 利用多路轉接的select的TCP_server,但當時我們提到了很多關於select的缺點:
1、select可監聽的文件描述符有上限制;
2、因爲select參數是輸入輸出型的,所以每次重新設置select時,都需遍歷式設置,對性能有一定的影響
3、用戶增多時,多次重複遍歷和頻繁內核與進程數據拷貝(多次的返回)
4、需要自己維護一個數組/鏈表,對文件描述符的管理,實現也比較複雜
5、每次多需要重新設置select—將fd設置從用戶拷貝到內核
基於這麼多的缺和實現的複雜,所以我們基本不會使用select來實現,而今天的主題epoll對這些問題都進行解決
2、epoll
epoll man手冊上說linux2.6後性能最好的!!!
爲什麼性能好呢?
我們來看張圖:
再來學習epoll的函數:
epoll有三個函數:
1、int epoll_create(int size); //創建epoll(創建紅黑樹)
- 參數size :對內核的提醒(建議)空間大小,man手冊解釋size可以被忽略
- 返回值: 返回一個epoll句柄,用於對紅黑樹的操作,在使用完epoll之後,因使用close()關閉;
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //設置紅黑樹
- 參數:epfd , 紅黑樹的句柄
- 參數:op,操作方式,有以下三種:
EPOLL_CTL_ADD :添加事件(在紅黑樹上添加節點)
EPOLL_CTL_MOD:更改事件(改變紅黑樹指定節點事件發生條件)
EPOLL_CTL_DEL:刪除事件(刪除節點)- 返回值: 成功返回 0、失敗返回-1;
3、int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);//等待事件就緒函數
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* 事件,用專門的宏來設置*/
epoll_data_t data; /* User data variable */
};
- 參數 epfd epoll句柄!!!
- 參數 events,是用戶創建的結構體數據組,是一個輸出型參數,當內核的就緒時間隊列中有事件時,將事件帶回。
- maxevents events最多可以帶回多少事件,0 < maxevents < size(前面創建epoll是的大小)
- 參數:timeout 超時時間,-1: 阻塞,0: 不阻塞
- 返回值,就緒事件的個數
- 函數功能:epoll_wait如果監測到事件就將所有就緒的事件傳送到第二個參數struct epoll_event 結構體數組中,每次調用epoll_wait返回鏈表是從內核返回給用戶空間的,每次從內核傳送到用戶空間的描述符並不是很多,epoll的時間複雜度是O(1).
講完所有的接口是不是對EPOLL爲什麼效率高的原因,有了一定的瞭解;
3、總結epoll的高效性:
1、內核創建紅黑樹
2、不需要每次都對每個事件重新設置(比較於select)
3、 操作系統在檢測文件描述符采用回調函數
4、用戶查找就緒文件符的複雜度O(1),—利用隊列遍歷
5、用戶與內核採用內存映射,看到同意內存,不需要拷貝
4、利用epoll編寫TCP_server
下面的代碼可以實現 server—client的交互式通信
while(1)
{
int n = epoll_wait(epfd, rev, 64, -1);//等待就緒事件
switch(n){ //
case 0:{
printf("time out\n");
continue;
}
case -1:{
perror("epoll_wait");
return 5;
}
default:{//有事件就緒
int i = 0;
for(i=0; i<n; ++i)//遍歷數組,時間複雜度O(n)=O(1)
{
if(rev[i].data.fd == listen_sock)
{
struct sockaddr_in client;
size_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new sock:%s, %d\n",\
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
ev.events = EPOLLIN;//添加新的套接字讀事件
ev.data.fd = new_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
}
else if(rev[i].events & EPOLLIN){//讀事件就緒
char buf[1024];
ssize_t s = read(rev[i].data.fd, buf, sizeof(buf)-1);
if(s < 0){//當讀取錯誤或者對端關閉連接時,刪除該套接字
perror("read");
close(rev[i].data.fd);
ev.events = EPOLLIN;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
continue;
}
else if(s == 0){
close(rev[i].data.fd);
ev.events = EPOLLIN;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
printf("client quit\n");
continue;
}
buf[s] = '\0';
printf("clinet say#%s\n",buf);
//讀取數據完成後,更改套接字讀事件爲寫事件
ev.events = EPOLLOUT;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}
else{//寫事件就緒
char *str = "wlcome to sock ";
write(rev[i].data.fd, str, strlen(str));
ev.events = EPOLLIN;//數據回饋完成後,更改套接字事件爲讀
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}
}
}
}