文章很長,建議收藏起來,慢慢讀! 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: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事件的通知階段,是異步的,但是,在將數據從內核緩衝區複製到用戶緩衝區這個過程,用戶進程是阻塞的、同步的。