事件驅動IO模式(圖解+秒懂+史上最全)
文章很長,建議收藏起來,慢慢讀! Java 高併發 發燒友社群:瘋狂創客圈 奉上以下珍貴的學習資源:
-
免費贈送 經典圖書:《Java高併發核心程式設計(卷1)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Java高併發核心程式設計(卷2)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Netty Zookeeper Redis 高併發實戰》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《SpringCloud Nginx高併發核心程式設計》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取
推薦:入大廠 、做架構、大力提升Java 內功 的 精彩博文
入大廠 、做架構、大力提升Java 內功 必備的精彩博文 | 2021 秋招漲薪1W + 必備的精彩博文 |
---|---|
1:Redis 分散式鎖 (圖解-秒懂-史上最全) | 2:Zookeeper 分散式鎖 (圖解-秒懂-史上最全) |
3: Redis與MySQL雙寫一致性如何保證? (面試必備) | 4: 面試必備:秒殺超賣 解決方案 (史上最全) |
5:面試必備之:Reactor模式 | 6: 10分鐘看懂, Java NIO 底層原理 |
7: |
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事件的通知階段,是非同步的,但是,在將資料從核心緩衝區複製到使用者緩衝區這個過程,使用者程序是阻塞的、同步的。