1. 程式人生 > 其它 >事件驅動IO模式(圖解+秒懂+史上最全)

事件驅動IO模式(圖解+秒懂+史上最全)

文章很長,建議收藏起來,慢慢讀! Java 高併發 發燒友社群:瘋狂創客圈 奉上以下珍貴的學習資源:


推薦:入大廠 、做架構、大力提升Java 內功 的 精彩博文

入大廠 、做架構、大力提升Java 內功 必備的精彩博文 2021 秋招漲薪1W + 必備的精彩博文
1:Redis 分散式鎖 (圖解-秒懂-史上最全) 2:Zookeeper 分散式鎖 (圖解-秒懂-史上最全)
3: Redis與MySQL雙寫一致性如何保證? (面試必備) 4: 面試必備:秒殺超賣 解決方案 (史上最全)
5:面試必備之:Reactor模式 6: 10分鐘看懂, Java NIO 底層原理
7:
TCP/IP(圖解+秒懂+史上最全)
8:Feign原理 (圖解)
9:DNS圖解(秒懂 + 史上最全 + 高薪必備) 10:CDN圖解(秒懂 + 史上最全 + 高薪必備)

Java 面試題 30個專題 , 史上最全 , 面試必刷 阿里、京東、美團... 隨意挑、橫著走!!!
1: JVM面試題(史上最強、持續更新、吐血推薦) 2:Java基礎面試題(史上最全、持續更新、吐血推薦
3:架構設計面試題 (史上最全、持續更新、吐血推薦) 4:設計模式面試題 (史上最全、持續更新、吐血推薦)
17、分散式事務面試題 (史上最全、持續更新、吐血推薦) 一致性協議 (史上最全)
29、多執行緒面試題(史上最全) 30、HR面經,過五關斬六將後,小心陰溝翻船!
9.網路協議面試題(史上最全、持續更新、吐血推薦) 更多專題, 請參見【 瘋狂創客圈 高併發 總目錄

SpringCloud 精彩博文
nacos 實戰(史上最全) sentinel (史上最全+入門教程)
SpringCloud gateway (史上最全) 更多專題, 請參見【 瘋狂創客圈 高併發 總目錄

特別說明:本文所屬書籍已經更新啦,最新內容以書籍為準(書籍也免費送哦)

下面的內容,來自於《Java高併發核心程式設計卷1》一書,此書的最新電子版,已經免費贈送,大家找尼恩領取即可。

而且,《Java高併發核心程式設計卷1》的電子書,會不斷優化和迭代。最新一輪的迭代,增加了 訊息驅動IO模型的內容,這是之前沒有的,使得在 Java NIO 底層原理這塊,書的內容變得非常全面。
另外,如果出現內容需要更新,到處要更新的話,工作量會很大,所以後續的更新,都會統一到電子書哦。

訊號驅動IO的簡介

在訊號驅動IO模型中,使用者執行緒通過IO事件的回撥函式註冊,來避免IO時間查詢的阻塞。

具體的做法是,使用者程序預先在核心中設定一個回撥函式,當某個事件發生時,核心使用訊號(SIGIO)通知程序執行回撥函式。 然後使用者執行緒會繼續執行,在訊號回撥函式中呼叫IO讀寫操作來進行實際的IO請求操作。

訊號驅動IO的基本流程

訊號驅動IO的基本流程是:

使用者程序通過系統呼叫,向核心註冊SIGIO訊號的owner程序和以及程序內的回撥函式。核心IO事件發生後(比如核心緩衝區資料就位)後,通知使用者程式,使用者程序通過read系統呼叫,將資料複製到使用者空間,然後執行業務邏輯。

訊號驅動IO模型,每當套接字發生IO事件時,系統核心都會向用戶程序傳送SIGIO事件,所以,一般用於UDP傳輸,在TCP套接字的開發過程中很少使用,原因是SIGIO訊號產生得過於頻繁,並且核心傳送的SIGIO訊號,並沒有告訴使用者程序發生了什麼IO事件。

但是在UDP套接字上,通過SIGIO訊號進行下面兩個事件的型別判斷即可:

1 資料報到達套接字

2 套接字上發上一部錯誤

因此,在SIGIO出現的時候,使用者程序很容易進行判斷和做出對應的處理:如果不是發生錯誤,那麼就是有資料報到達了。

事件註冊的步驟

舉個例子。發起一個非同步IO的read讀操作的系統呼叫,流程如下:

(1)設定SIGIO訊號的訊號處理回撥函式。

(2)設定該套介面的屬主程序,使得套接字的IO事件發生時,系統能夠將SIGIO訊號傳遞給屬主程序,也就是當前程序。

(3)開啟該套介面的訊號驅動I/O機制,通常通過使用fcntl方法的F_SETFL操作命令,使能(enable)套接字的 O_NONBLOCK非阻塞標誌和O_ASYNC非同步標誌完成。

完成以上三步,使用者程序就完成了事件回撥處理函式的設定。當檔案描述符上有事件發生時,SIGIO 的訊號處理函式將被觸發,然後便可對目標檔案描述符執行 I/O 操作。

關於以上三步的詳細介紹,具體如下:

第一步:

設定SIGIO訊號的訊號處理回撥函式。Linux中通過 sigaction() 來完成。參考的程式碼如下:


   // 註冊SIGIO事件的回撥函式

   sigaction(SIGIO, &act, NULL); 

sigaction函式的功能是檢查或修改與指定訊號相關聯的處理動作(可同時兩種操作),函式的原型如下:


int sigaction(int signum, const struct sigaction *act,

                     struct sigaction *oldact);

對其中的引數說明如下:

1 signum引數指出要捕獲的訊號型別

2 act引數指定新的訊號處理方式

3 oldact引數輸出先前訊號的處理方式(如果不為NULL的話)。

該函式是Linux系統的一個基礎函式,不是為訊號驅動IO特供的。在訊號驅動IO的使用場景中,signum的值為常量 SIGIO。

第二步:

設定該套介面的屬主程序,使得套接字的IO事件發生時,系統能夠將SIGIO訊號傳遞給屬主程序,也就是當前程序。屬主程序是當檔案描述符上可執行 I/O 時,會接收到通知訊號的程序或程序組。

為檔案描述符的設定IO事件的屬主程序,通過 fcntl() 的 F_SETOWN 操作來完成,參考的程式碼如下:


fcntl(fd,F_SETOWN,pid)

當引數pid 為正整數時,代表了程序 ID 號。當引數pid 為負整數時,它的絕對值就代表了程序組 ID 號。

第三步:

開啟該套介面的訊號驅動IO機制,通常通過使用fcntl方法的F_SETFL操作命令,使能(enable)套接字的 O_NONBLOCK非阻塞標誌和O_ASYNC非同步標誌完成。參考的程式碼如下:

int flags = fcntl(socket_fd, F_GETFL, 0);

    flags |= O_NONBLOCK;  //設定非阻塞

    flags |= O_ASYNC;    //設定為非同步

    fcntl(socket_fd, F_SETFL, flags );

這一步通過 fcntl() 的 F_SETFL 操作來完成,O_NONBLOCK為非阻塞標誌,O_ASYNC為訊號驅動 I/O的標誌。

使用事件驅動IO進行UDP通訊應用的開發,參考的程式碼如下(C程式碼):


int socket_fd = 0;

 

//事件的處理函式

void do_sometime(int signal) {

    struct sockaddr_in cli_addr;

    int clilen = sizeof(cli_addr);

    int clifd = 0;

 

    char buffer[256] = {0};

    int len = recvfrom(socket_fd, buffer, 256, 0, (struct sockaddr *)&cli_addr,

                       (socklen_t)&clilen);

    printf("Mes:%s", buffer);

    

    //回寫

    sendto(socket_fd, buffer, len, 0, (struct sockaddr *)&cli_addr, clilen);

}

 

int main(int argc, char const *argv[]) {

    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sigaction act;
    act.sa_flags = 0;

    act.sa_handler = do_sometime;

    // 註冊SIGIO事件的回撥函式
    sigaction(SIGIO, &act, NULL); 

    struct sockaddr_in servaddr;

    memset(&servaddr, 0, sizeof(servaddr));

    servaddr.sin_family = AF_INET;

    servaddr.sin_port = htons(8888);

    servaddr.sin_addr.s_addr = INADDR_ANY;

 

    //第二步為檔案描述符的設定 屬主

    //設定將要在socket_fd上接收SIGIO的程序

    fcntl(socket_fd, F_SETOWN, getpid());

 

    //第三步:使能套接字的訊號驅動IO

    int flags = fcntl(socket_fd, F_GETFL, 0);

    flags |= O_NONBLOCK;  //設定非阻塞

    flags |= O_ASYNC;    //設定為非同步

    fcntl(socket_fd, F_SETFL, flags );

 

    bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    while (1) sleep(1); //死迴圈

    close(socket_fd);

    return 0;

}

當套件字的IO事件發生時,回撥函式被執行,在回撥函式中,使用者進行執行資料複製即可。

訊號驅動IO優勢:

使用者程序在等待資料時,不會被阻塞,能夠使用者程序的效率。具體來說:在訊號驅動式I/O模型中,應用程式使用套介面進行訊號驅動I/O,並安裝一個訊號處理函式,程序繼續執行並不阻塞。

訊號驅動IO缺點:

1 在大量IO事件發生時,可能會由於處理不過來,而導致訊號佇列溢位。

2 對於處理UDP套接字來講,對於訊號驅動I/O是有用的。可是,對於TCP而言,由於致使SIGIO訊號通知的條件為數眾多,進行IO訊號進一步區分的成本太高,訊號驅動的I/O方式近乎無用。

3 訊號驅動IO可以看成是一種非同步IO,可以簡單理解為系統進行使用者函式的回撥。但是,訊號驅動IO的非同步特性,又做的不徹底。訊號驅動IO僅僅在IO事件的通知階段,是非同步的,但是,在將資料從核心緩衝區複製到使用者緩衝區這個過程,使用者程序是阻塞的、同步的。