client.c
1 /* client.c */ 2 #include 3 #include 4 #include 5 #include 6 #include "wrap.h" 7 8 #define MAXLINE 80 9 #define SERV_PORT 6666 10 11 int main(int argc, char *argv[]) 12 { 13 struct sockaddr_in servaddr; 14 char buf[MAXLINE]; 15 int sockfd, n; 16 17 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 18 19 bzero(&servaddr, sizeof(servaddr)); 20 servaddr.sin_family = AF_INET; 21 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 22 servaddr.sin_port = htons(SERV_PORT); 23 24 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 25 26 while (fgets(buf, MAXLINE, stdin) != NULL) { 27 Write(sockfd, buf, strlen(buf)); 28 n = Read(sockfd, buf, MAXLINE); 29 if (n == 0) 30 printf("the other side has been closed.\n"); 31 else 32 Write(STDOUT_FILENO, buf, n); 33 } 34 35 Close(sockfd); 36 return 0; 37 }
注意:epoll_wait 調用次數越多, 系統的開銷越大
四、epoll進階
1.?事件模型
EPOLL事件有兩種模型:
Edge Triggered (ET) 邊緣觸發只有數據到來才觸發,不管緩存區中是否還有數據。
Level Triggered (LT) 水平觸發只要有數據都會觸發。
思考如下步驟:
1)假定我們已經把一個用來從管道中讀取數據的文件描述符(RFD)添加到epoll描述符。
2)管道的另一端寫入了2KB的數據
3)調用epoll_wait,并且它會返回RFD,說明它已經準備好讀取操作
4)讀取1KB的數據
5)調用epoll_wait……
在這個過程中,有兩種工作模式:
(1)ET模式
ET模式即Edge Triggered工作模式。
如果我們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標志,那么在第5步調用epoll_wait之后將有可能會掛起,因為剩余的數據還存在于文件的輸入緩沖區內,而且數據發出端還在
等待一個針對已經發出數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候 ET 工作模式才會匯報事件。因此在第5步的時候,調用者可能會放棄等待仍在存在于文件輸入緩沖區內的剩
余數據。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。最好以下面的方式調用ET模式的epoll接口,在后面
會介紹避免可能的缺陷。
基于非阻塞文件句柄
只有當read或者write返回EAGAIN(非阻塞讀,暫時無數據)時才需要掛起、等待。但這并不是說每次read時都需要循環讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read返回的
讀到的數據長度小于請求的數據長度時,就可以確定此時緩沖中已沒有數據了,也就可以認為此事讀事件已處理完成。
(2)LT模式
LT模式即Level Triggered工作模式。
與ET模式不同的是,以LT方式調用epoll接口的時候,它就相當于一個速度比較快的poll,無論后面的數據是否被使用。
LT(level triggered):LT是缺省的工作方式,并且同時支持block和no-block socket。在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任
何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。
ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再
為那個文件描述符發送更多的就緒通知。請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)。
2.??實例一
基于管道epoll ET觸發模式
et_epoll.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 7 #define MAXLINE 10 8 9 int main(int argc, char *argv[]) 10 { 11 int efd, i; 12 int pfd[2]; 13 pid_t pid; 14 char buf[MAXLINE], ch = 'a'; 15 16 pipe(pfd); 17 pid = fork(); 18 if (pid == 0) { 19 close(pfd[0]); 20 while (1) { 21 for (i = 0; i < MAXLINE/2; i++) 22 buf[i] = ch; 23 buf[i-1] = '\n'; 24 ch++; 25 26 for (; i < MAXLINE; i++) 27 buf[i] = ch; 28 buf[i-1] = '\n'; 29 ch++; 30 31 write(pfd[1], buf, sizeof(buf)); 32 sleep(2); 33 } 34 close(pfd[1]); 35 } else if (pid > 0) { 36 struct epoll_event event; 37 struct epoll_event resevent[10]; 38 int res, len; 39 close(pfd[1]); 40 41 efd = epoll_create(10); 42 /* event.events = EPOLLIN; */ 43 event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ 44 event.data.fd = pfd[0]; 45 epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); 46 47 while (1) { 48 res = epoll_wait(efd, resevent, 10, -1); 49 printf("res %d\n", res); 50 if (resevent[0].data.fd == pfd[0]) { 51 len = read(pfd[0], buf, MAXLINE/2); 52 write(STDOUT_FILENO, buf, len); 53 } 54 } 55 close(pfd[0]); 56 close(efd); 57 } else { 58 perror("fork"); 59 exit(-1); 60 } 61 return 0; 62 }
3.?實例二
基于網絡C/S模型的epoll ET觸發模式
et_epoll.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 創建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服務器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 29 serv_addr.sin_port = htons(port); // 設置端口 30 // 綁定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 設置同時監聽的最大個數 34 listen(lfd, 36); 35 printf("Start accept ......\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 創建epoll樹根節點 41 int epfd = epoll_create(2000); 42 // 初始化epoll樹 43 struct epoll_event ev; 44 45 // 設置邊沿觸發 46 ev.events = EPOLLIN | EPOLLET; 47 ev.data.fd = lfd; 48 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 49 50 struct epoll_event all[2000]; 51 while(1) 52 { 53 // 使用epoll通知內核fd 文件IO檢測 54 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 55 printf("================== epoll_wait =============\n"); 56 57 // 遍歷all數組中的前ret個元素 58 for(int i=0; itcp_client.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 // 創建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 連接服務器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 寫數據 43 // 接收鍵盤輸入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 發送給服務器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服務器端的數據 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\n", buf, len); 52 } 53 return 0; 54 }
執行結果:
client端:
[root@centos epoll]# ./client 6666 000001111122222 read buf = 000001111122222 , len = 5 read buf = 11111, len = 5 read buf = 22222, len = 5
server端:

[root@centos epoll]# ./et_epoll 6666 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 54080 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222
執行結果分析:可以看出,當客戶端發送數據(000001111122222)到server端(接收數據緩沖區內),但是由于server端一次只接受5個字節(00000),因此在接受完5個字節之后,將接收的5個字節數據發回給客戶端,程序又會在epoll_wait處阻塞等待。當有新數據再次發送過來,則會將上一次緩沖區中剩余的數據(11111)讀取并發送給客戶端,如此最后將(22222)發送給客戶端。
【Linux C編程】第十八章 高并發服務器(四)
Linux 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。