1. 程式人生 > 其它 >網路程式設計之IO複用機制(多路IO轉接)之使用管道驗證epoll的LT和ET07

網路程式設計之IO複用機制(多路IO轉接)之使用管道驗證epoll的LT和ET07

技術標籤:Linux網路程式設計網路epollsocket

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模型是隻有還有資料,我就會去讀。

並且實際該例子也證明了epoll的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模型,說難不難,說簡單也不易,多看幾篇就熟。