事件驅動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事件的通知階段,是異步的,但是,在將數據從內核緩衝區複製到用戶緩衝區這個過程,用戶進程是阻塞的、同步的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章