網路程式設計之IO複用機制(多路IO轉接)之使用管道驗證epoll的LT和ET07
1 阻塞與非阻塞,LT與ET概念
- 1)我們知道,檔案描述符(例如管道,套接字等等)具有阻塞與非阻塞,例如套接字,當我們使用read去讀取內容時,由於套接字預設是阻塞的,所以當沒有內容時,read就會阻塞,這就是read讀取阻塞的根本原因。所以阻塞就是卡住,非阻塞就是不卡住,這是最簡單的理解。
- 2)而Level Triggered (LT)和Edge Triggered (ET) 兩者,前者是水平出發,只要有資料就會讀取。後者是邊沿觸發,只有電平改變才會觸發,電平改變就是0變成1(上升沿)或者1變成0(下降沿)。例如伺服器與客戶端連線,只有客戶端發訊息到套接字的緩衝區了,伺服器才會認為觸發到滿足事件,然後去讀取套接字的緩衝區。否則即使你套接字還有內容,但是客戶端沒有發訊息,伺服器也不會去讀取,這就是邊沿觸發。
- 3)注意,ET和LT是針對於epoll的,因為select,poll是不支援這兩個模型的。
2 epoll的LT和ET詳解
經過上面分析後,我們可以這樣理解,阻塞非阻塞是檔案描述符的屬性,而描述符作為read(write不需要考慮,因為我們只需要分析讀即可)的參1,該屬性會影響到read,也就是說,描述符的阻塞非阻塞就是read的阻塞非阻塞。藉此,我們進而可以深入分析epoll的LT和ET模型。首先先記住使用epoll的總結。
2.1 epoll的LT,ET模型是否阻塞和非阻塞總結
- 1)epoll的LT模型支援阻塞和非阻塞。
- 2)epoll的ET模型只支援非阻塞,不支援阻塞。
2.2 針對epoll的LT模型程式碼
為了方便,我們使用管道來測試。注意,select,poll,epoll都是可以在檔案描述符中使用,檔案描述符包括管道(即管道兩個讀寫fd),mmap對映,網路套接字這些。這些IO複用函式雖然常用在網路套接字上,但不僅僅只能在網路套接字使用select,poll,epoll,也能用在管道等檔案描述符。
案例:我們建立父子程序,然後父程序讀,子程序寫。子程序每次往管道寫10個位元組後睡眠5s,等待父程序讀。父程序讀,但是每次讀5個,也就是說,讀完5個後(睡眠1s讓現象明顯),管道中還有資料,若仍會觸發epoll返回,去讀取管道剩餘的資料,就證明epoll預設是LT,LT模型是隻有還有資料,我就會去讀。
程式碼:
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[])
{
int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd);
pid = fork();
if (pid == 0) {//子程序寫
close(pfd[0]);
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) {//父程序讀
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait就緒返回event
int res, len;
close(pfd[1]);
efd = epoll_create(10);
//event.events = EPOLLIN | EPOLLET; // ET 邊沿觸發
event.events = EPOLLIN; // LT 水平觸發 (預設)
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
//printf("res %d\n", res);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
sleep(1);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork");
exit(-1);
}
return 0;
}
結果:
1)首先父程序會先讀取5個。然後因為是LT模型,所以會繼續觸發epoll返回,讀取剩餘的5個位元組。
2)然後子程序睡5s後,繼續寫10個位元組。父程序不斷重複第一步讀取。
所以,程式正常按照猜想執行,說明epoll的預設模型是LT,並且LT模型是支援阻塞的。關於LT支援非阻塞的案例這裡不舉例了,可以通過fcntl這個函式設定非阻塞測試,但是個人覺得沒啥必要。我們只需要知道epoll的LT和ET即可。並且這個案例只是為了讓大家理解epoll的這兩種
模型,實際專案很少用到。
2.3 測試epoll的ET模型程式碼(專案中絕不允許使用,這裡只是測試,看第四小點解釋)
將上面程式碼的註釋行改成ET邊沿觸發即可。
//event.events = EPOLLIN | EPOLLET; // ET 邊沿觸發
1)首先,子程序寫了10個位元組,然後父程序讀取5個位元組後,由於是ET模型,所以剩餘的資料並不會被讀取,只能等待下一次子程序寫時才會觸發。
2)接著,5s後子程序又傳送10個位元組,父程序將管道剩餘的5位元組讀取即bbbb,但是本次的10個位元組並未讀取。
3)也就是說,子程序每隔5s寫一次,父程序就會讀一次,只有子程序寫了,父程序才會去讀。即使當管道還有剩餘的資料,epoll也不會返回,讓父程序去讀。
4)看到這裡,有人會問,這不是ET的阻塞模型嗎?這程式不是可以執行嗎,我們開始解釋:我們使用的是read,因為本程式子程序不斷寫保證管道有資料,所以read不會阻塞,只會在epoll_wait函式阻塞。但是很多時候read並不會直接使用,很多公司會將read封裝成readn,readn的作用是隻有讀取到一定位元組數才會返回,封裝阻塞。那麼當readn阻塞了,客戶端再發送資訊,而由於程式阻塞在readn導致epoll_wait無法返回,那麼程式就卡死了,無法進行任何處理。所以這裡證明epoll的ET模式不支援阻塞(注意管道的檔案描述符預設是阻塞)。
3 總結epoll的LT和ET
- 1)epoll的LT模型支援阻塞和非阻塞。
- 2)epoll的ET模型只支援非阻塞,不支援阻塞(看2.3的第4點)。
- 3)select,poll,epoll這些IO複用函式可以用在管道,mmap對映,套接字等檔案描述符的場合。
好了,本篇就是我們想要講述的epoll的LT和ET模型,說難不難,說簡單也不易,多看幾篇就熟。