第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為什么需要非阻塞 Java Socket 編程

1. 前言

前面小節(jié)介紹的都是阻塞式 Socket 編程。比如,我們最早編寫的 TCP Client/Server 示例程序,客戶端定時(shí)發(fā)送消息,服務(wù)器只是做一個(gè)響應(yīng)。由于只是服務(wù)一個(gè)客戶端,所以通過阻塞式 Socket 編程勉強(qiáng)能滿足需求。

在 Java 服務(wù)器多線程一節(jié),我們介紹了每線程模型線程池模型。通過這兩種多線程模型,服務(wù)器可以同時(shí)和多個(gè)客戶端完成通信。對于每線程模型來說,其核心是為每一個(gè)新連接創(chuàng)建一個(gè)獨(dú)立的子線程,由這個(gè)獨(dú)立的子線程負(fù)責(zé)和客戶端完成數(shù)據(jù)收發(fā)。每線程模型的特點(diǎn)是服務(wù)器上的線程和客戶端是一對一的。這種解決方案是有很大弊端的,因?yàn)橄到y(tǒng)能夠創(chuàng)建的線程數(shù)量是有限的,是無法支撐高并發(fā)場景的。對于線程池模型來說,盡管限制了創(chuàng)建的線程的總數(shù),但是由于是阻塞式 Socket,一旦某個(gè)線程被分配和客戶端通信,就只能和此客戶端通信,所以在容量上有限制。

對于高并發(fā)的應(yīng)用場景來說,還是得通過非阻塞式 Socket 編程來解決。

首先我們了解一下阻塞式非阻塞式的區(qū)別。

2. 阻塞式與非阻塞式模型

我們以 Linux 系統(tǒng)為例,介紹阻塞式非阻塞式的概念。Linux 程序的執(zhí)行模式分為用戶態(tài)內(nèi)核態(tài),應(yīng)用程序邏輯運(yùn)行在用戶態(tài),訪問系統(tǒng)資源的邏輯運(yùn)行在內(nèi)核態(tài)。其實(shí)現(xiàn)代操作系統(tǒng)都是這種模式。

當(dāng)程序的執(zhí)行邏輯從用戶態(tài)切換到內(nèi)核態(tài)時(shí),會引發(fā)上下文的切換,會涉及到數(shù)據(jù)從用戶態(tài)內(nèi)核態(tài),或者是從內(nèi)核態(tài)用戶態(tài)拷貝的問題。這時(shí),系統(tǒng) API 會提供阻塞式非阻塞式兩種調(diào)用方式。比如,我們調(diào)用 recv 函數(shù)接收 Socket 數(shù)據(jù),recv 函數(shù)可以選擇阻塞式或者是非阻塞式調(diào)用模式,不同的模式,編程風(fēng)格是完全不同。假如 Socket 的接收緩沖區(qū)沒有準(zhǔn)備好要接收的數(shù)據(jù),如果選擇阻塞式調(diào)用,那么應(yīng)用線程會被阻塞在 recv 調(diào)用上,不能繼續(xù)執(zhí)行,線程會處于等待狀態(tài),直到系統(tǒng)準(zhǔn)備好數(shù)據(jù);如果選擇非阻塞式調(diào)用,那么應(yīng)用線程不會被阻塞,recv 函數(shù)會立即返回。當(dāng)系統(tǒng)準(zhǔn)備好數(shù)據(jù)以后,會觸發(fā)一個(gè)讀事件,這就要求我們必須通過某種機(jī)制監(jiān)聽讀事件,一般都是通過 I/O 多路復(fù)用機(jī)制來解決。

我們通過兩張圖來感受一下阻塞式非阻塞式的差異。

阻塞式:

圖片描述

非阻塞式:
圖片描述

從以上兩張圖可以看出,如果 read 函數(shù)采用阻塞式調(diào)用 ,當(dāng)內(nèi)核沒有準(zhǔn)備好的數(shù)據(jù)時(shí),應(yīng)用線程會被阻塞到 read 調(diào)用上,進(jìn)入等待狀態(tài),直到有數(shù)據(jù)可以讀取才返回。如果 read 函數(shù)采用非阻塞式調(diào)用,當(dāng)內(nèi)核沒有準(zhǔn)備好數(shù)據(jù)時(shí),read 函數(shù)會返回 EAGAIN,線程不會被阻塞。當(dāng)系統(tǒng)準(zhǔn)備好數(shù)據(jù)以后,會觸發(fā)一個(gè)讀事件。

對于邏輯比較簡單的場景,比如邏輯簡單的客戶端程序,可以采用阻塞式編程模型,這樣實(shí)現(xiàn)簡單,容易理解。對于邏輯比較復(fù)雜的場景,比如高性能服務(wù)器,必須采用非阻塞式編程模型,而且要配合 I/O 多路復(fù)用機(jī)制。

下來我們就介紹一下如何進(jìn)行非阻塞式 Socket 編程。

3. Java 非阻塞式 Socket 編程

介紹 Java 非阻塞式 Socket 編程,就得介紹 Java NIO。Java NIO 是 Java New IO API,有時(shí)也解釋為 Java Non-blocking IO。通過 Java NIO 可以實(shí)現(xiàn) Java 非阻塞 Socket 編程。

Java NIO 是 Java 1.4 支持的,它將 Socket 數(shù)據(jù)流抽象為一個(gè) Channel(管道),Socket 數(shù)據(jù)讀寫是通過 Channel
實(shí)現(xiàn)的,并且提供了 Buffer 機(jī)制,提高數(shù)據(jù)讀寫的性能。Java NIO 通常用來編寫高性能 Java 服務(wù)器程序。在 Java 1.7 以后,Java NIO 對磁盤文件處理得到了增強(qiáng),可以將 Socket I/O 和 文件 I/O 融合在 Java NIO 中。

Java NIO 提供的新的類結(jié)構(gòu)如下:

類名稱 功能說明
ServerSocketChannel 表示服務(wù)端 TCP Socket 的監(jiān)聽 Channel。ServerSocketChannel 提供的工廠方法 open,用于創(chuàng)建它的實(shí)例;同時(shí)它提供了 accept 方法用于在服務(wù)器中接收新的客戶端連接請求,返回值是 SocketChannel 類的實(shí)例。
SocketChannel SocketChannel 表示一個(gè) TCP 通信 Channel,可以通過它的 open 方法創(chuàng)建,也可以通過 ServerSocketChannel 的 accept 方法創(chuàng)建。
Selector Java I/O 事件多路復(fù)用機(jī)制,用于同時(shí)監(jiān)聽多個(gè) Channel 的讀、寫、監(jiān)聽事件
SelectionKey 用于表示具體的事件對象
ByteBuffer 通過 SocketChannel 進(jìn)行數(shù)據(jù)讀寫,依賴 ByteBuffer

ServerSocketChannel 和 SocketChannel 同時(shí)支持阻塞式非阻塞式,默認(rèn)是阻塞式??梢酝ㄟ^如下的方法,打開非阻塞式。

// 配置監(jiān)聽 ServerSocketChannel 為非阻塞模式
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);

// 配置服務(wù)器新建立的 SocketChannel 為非阻塞模式
SocketChannel newSock = serverChannel.accept();
newSock.configureBlocking(false);
SocketAddress serverAddr = new InetSocketAddress("127.0.0.1", PORT);
SocketChannel sock = SocketChannel.open(serverAddr);
// 配置客戶端 SocketChannel 為非阻塞
sock.configureBlocking(false);

4. 小結(jié)

阻塞式 Socket 編程,程序結(jié)構(gòu)簡單,容易編寫,容易理解。但是由于阻塞式 Socket 編程,在調(diào)用 recv、send 讀寫數(shù)據(jù)的時(shí)候,會阻塞線程,所以只能適應(yīng)簡單的應(yīng)用場景。對于編寫高性能服務(wù)器來說,必須采用非阻塞式 Socket 編程。但是非阻塞式 Socket 編程,程序結(jié)構(gòu)要復(fù)雜很多,并且不容易理解,要想編寫健壯、穩(wěn)定的程序不是一件容易的事情。

編寫 Java 非阻塞 Socket 程序,需要采用 Java NIO API,這些 API 的具體功能、具體用法,在后面小節(jié)介紹。