版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接:https://blog.csdn.net/men_wen/article/details/53456491
Linux網絡編程—I/O複用模型之epoll
1. epoll模型簡介
epoll是Linux多路服用IO接口select/poll的增強版,e對應的英文單詞就是enhancement,中文翻譯爲加強,增強,提升,充實的意思。因此epoll模型會顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率。編程
epoll把用戶關心的文件描述符上的時間放在內核的一個事件表中,無需像select和poll那樣每次調用都重複傳入文件描述符集。
epoll在獲取事件的時候,無需遍歷整個被監聽的文件描述符集合,而是遍歷那些被內核IO事件異步喚醒而加入ready隊列的描述符集合。
因此,epoll是Linux大規模高併發網絡程序的首選模型。vim
2.epoll模型的API
epoll使用一組函數來完成任務緩存
2.1 函數epoll_create
建立一個epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。服務器
#include <sys/epoll.h>網絡
int epoll_create(int size);
//返回值:若成功,返回一個非負的文件描述符,若出錯,返回-1。
1
2
3
4
該函數返回一個文件描述符,用來惟一標示內核中這個事件表,sizeof參數提示內核要監聽的文件描述符個數,這與內存大小有關。
返回的文件描述符將是其餘全部epoll系統調用的第一個參數,以指定要訪問的內核時間表,因此用該返回的文件描述符至關與其餘epoll調用的把手、把柄同樣。
查看進程可以打開的最大數目的文件描述符併發
➜ ~ cat /proc/sys/fs/file-max
1215126
//該值與內存大小有關
1
2
3
修改最大文件描述符限制app
➜ ~ sudo vim /etc/security/limits.conf
//重啓生效
1
2
2.2 函數epoll_ctl
該函數用來操做epoll的內核事件表異步
#include <sys/epoll.h>socket
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//返回值:若成功,返回0,若出錯返回-1。
1
2
3
4
epfd就是函數epoll_create建立的句柄。
op是指定操做類型,有一下三種
EPOLL_CTL_ADD,向epfd註冊fd的上的event
EPOLL_CTL_MOD,修改fd已註冊的event
EPOLL_CTL_DEL,從epfd上刪除fd的event
fd是操做的文件描述符
event指定內核要監聽事件,它是struct epoll_event結構類型的指針。epoll_event定義以下:
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
1
2
3
4
events成員描述事件類型,將如下宏定義經過位或方式組合函數
EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉)
POLLOUT:表示對應的文件描述符能夠寫
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)
EPOLLERR:表示對應的文件描述符發生錯誤
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
data用於存儲用戶數據,是epoll_data_t結構類型,該結構定義以下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
1
2
3
4
5
6
epoll_data_t是一個聯合體,fd指定事件所從屬的目標文件描述符。ptr能夠用來指定fd相關的用戶數據,但二者不能同時使用。
2.3 函數epoll_wait
函數epoll_wait用來等待所監聽文件描述符上有事件發生
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//返回值:若成功,返回就緒的文件描述符個數,若出錯,返回-1,時間超時返回0
1
2
3
4
epfd就是函數epoll_create建立的句柄
timeout是超時事件,-1爲阻塞,0爲當即返回,非阻塞,大於0是指定的微妙
events是一個 傳入傳出參數,它是epoll_event結構的指針,用來從內核獲得事件的集合
maxevents告知內核events的大小,但不能大於epoll_create()時建立的size
3. LT和ET模式
LT(Level Triggered,電平觸發):LT模式是epoll默認的工做模式,也是select和poll的工做模式,在LT模式下,epoll至關於一個效率較高的poll。
採用LT模式的文件描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程序後,應用程序能夠不當即處理此事件,當下一次調用epoll_wait是,epoll_wait還會將此事件通告應用程序。
ET(Edge Triggered,邊沿觸發):當調用epoll_ctl,向參數event註冊EPOLLET事件時,epoll將以ET模式來操做該文件描述符,ET模式是epoll的高效工做模式.
對於採用ET模式的文件描述符,當epoll_wait檢測到其上有事件發生並將此通知應用程序後,應用程序必須當即處理該事件,由於後續的epoll_wait調用將不在嚮應用程序通知這一事件。ET模式下降了贊成epoll事件被觸發的次數,效率比LT模式高。
4. LT和ET的服務端和客戶端代碼
4.1 服務器端
#include <sys/epoll.h>
#include <fcntl.h>
#include "wrap.h"
#define MAX_EVENT_NUM 1024
#define BUFFER_SIZE 10
#define true 1
#define false 0
int setnonblocking(int fd)
{
int old_opt = fcntl(fd, F_GETFD);
int new_opt = old_opt | O_NONBLOCK;
fcntl(fd, F_SETFD, new_opt);
return old_opt;
}//將文件描述符設置爲非阻塞的
void addfd(int epollfd, int fd, int enable_et)
{
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
if(enable_et){
event.events |= EPOLLET;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
// setnonblocking(fd);
}//將文件描述符fd的EPOLLIN註冊到epollfd指示的epoll內核事件表中,enable_et表示是否對fd啓用ET模式
void lt(struct epoll_event *events, int num, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
for(int i = 0; i < num; i++){
int sockfd = events[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in clientaddr;
socklen_t clilen = sizeof(clientaddr);
int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
addfd(epollfd, connfd, false);//對connfd使用默認的lt模式
}else if(events[i].events & EPOLLIN){//只要socket讀緩存中還有未讀的數據,這段代碼就會觸發
printf("event trigger once\n");
memset(buf, '\0', BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret <= 0){
Close(sockfd);
continue;
}
printf("get %d bytes of content:%s\n", ret, buf);
}else{
printf("something else happened\n");
}
}
}
void et(struct epoll_event *event, int num, int epollfd, int listenfd)
{
char buf[BUFFER_SIZE];
for(int i = 0; i < num; i++){
int sockfd = event[i].data.fd;
if(sockfd == listenfd){
struct sockaddr_in clientaddr;
int clilen = sizeof(clientaddr);
int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
addfd(epollfd, connfd, true);//多connfd開啓ET模式
}else if(event[i].events & EPOLLIN){
printf("event trigger once\n");
while(1){//這段代碼不會重複觸發,因此要循環讀取數據
memset(buf, '\0', BUFFER_SIZE);
int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
if(ret < 0){
if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
printf("read later\n");
break;
}
Close(sockfd);
break;
}else if(ret == 0){
Close(sockfd);
}else{
printf("get %d bytes of content:%s\n", ret, buf);
}
}
}else{
printf("something else happened \n");
}
}
}
int start_ser(char *ipaddr, char *port)
{
int sock = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(port));
inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);
Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
Listen(sock, 128);
return sock;
}
int main(int argc, char *argv[])
{
int listenfd = start_ser(argv[1], argv[2]);
struct epoll_event events[MAX_EVENT_NUM];
int epollfd = epoll_create(5);
if(epollfd < 0){
perr_exit("epoll_create err");
}
addfd(epollfd, listenfd, true);
while(1){
int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);
if(ret < 0){
printf("epoll failure\n");
break;
}
lt(events, ret, epollfd, listenfd);//lt模式
//et(events, ret, epollfd, listenfd);//et模式
}
Close(listenfd);
return 0;
}
//warp.h文件是將socket,bind,listen等函數封裝爲第一個字母大寫的頭文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
4.2 客戶端
#include "wrap.h"
int main(int argc, char *argv[])
{
int connfd;
struct sockaddr_in serveraddr;
char buf[1024];
connfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);
Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
while(fgets(buf, 1024, stdin) != NULL){
Write(connfd, buf, strlen(buf));
}
Close(connfd);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
4.3 兩種模式結果對比
當發送超過緩衝區大小的數據量,LT會屢次調用epoll_wait函數接受數據,則打印了屢次「event level once」,而ET則是循環讀取數據知道讀完,打印了一次「event trigger once」。 ———————————————— 版權聲明:本文爲CSDN博主「men_wen」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。 原文連接:https://blog.csdn.net/men_wen/article/details/53456491