您好,登錄后才能下訂單哦!
本篇內容主要講解“Java IO網絡模型如何實現”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java IO網絡模型如何實現”吧!
在開始本篇文章內容之前,有一個簡單的關于Socket的知識需要說明:在進行網絡通信的時候,需要一對Socket,一個運行于客戶端,一個運行于服務端,同時服務端還會有一個服務端Socket,用于監聽客戶端的連接。下圖進行一個簡單示意。
那么整個通信流程如下所示。
服務端運行后,會在服務端創建listen-socket,listen-socket會綁定服務端的ip和port,然后服務端進入監聽狀態;
客戶端請求服務端時,客戶端創建connect-socket,connect-socket描述了其要連接的服務端的listen-socket,然后connect-socket向listen-socket發起連接請求;
connect-socket與listen-socket成功連接后(TCP三次握手成功),服務端會為已連接的客戶端創建一個代表該客戶端的client-socket,用于后續和客戶端進行通信;
客戶端與服務端通過socket進行網絡IO操作,此時就實現了客戶端和服務端中的不同進程的通信。
需要知道的就是,在客戶端與服務端通信的過程中,出現了三種socket,分別是。
listen-socket。是服務端用于監聽客戶端建立連接的socket;
connect-socket。是客戶端用于連接服務端的socket;
client-socket。是服務端監聽到客戶端連接請求后,在服務端生成的與客戶端連接的socket。
(注:上述中的socket,可以被稱為套接字,也可以被稱為文件描述符。)
BIO,即同步阻塞IO模型。用戶進程調用read時發起IO操作,此時用戶進程由用戶態轉換到內核態,只有在內核態中將IO操作執行完后,才會從內核態切換回用戶態,這期間用戶進程會一直阻塞。
BIO示意圖如下。
簡單的BIO的Java編程實現如下。
服務端實現
public class BioServer { public static void main(String[] args) throws IOException { // 創建listen-socket ServerSocket listenSocket = new ServerSocket(8080); // 進入監聽狀態,是一個阻塞狀態 // 有客戶端連接時從監聽狀態返回 // 并創建代表這個客戶端的client-socket Socket clientSocket = listenSocket.accept(); // 獲取client-socket輸入流 BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(clientSocket.getInputStream())); // 讀取客戶端發送的數據 // 如果數據沒準備好,會進入阻塞狀態 String data = bufferedReader.readLine(); System.out.println(data); // 獲取client-socket輸出流 BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter(clientSocket.getOutputStream())); // 服務端向客戶端發送數據 bufferedWriter.write("來自服務端的返回數據\n"); // 刷新流 bufferedWriter.flush(); } }
客戶端實現
public class BioClient { public static final String SERVER_IP = "127.0.0.1"; public static final int SERVER_PORT = 8080; public static void main(String[] args) throws IOException { // 客戶端創建connect-socket Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT); // 獲取connect-socket輸出流 BufferedWriter bufferedWriter = new BufferedWriter( new OutputStreamWriter(connectSocket.getOutputStream())); // 客戶端向服務端發送數據 bufferedWriter.write("來自客戶端的請求數據\n"); // 刷新流 bufferedWriter.flush(); // 獲取connect-socket輸入流 BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(connectSocket.getInputStream())); // 讀取服務端發送的數據 String returnData = bufferedReader.readLine(); System.out.println(returnData); } }
BIO的問題就在于服務端在accept時是阻塞的,并且在主線程中,一次只能accept一個Socket,accept到Socket后,讀取客戶端數據時又是阻塞的。
Non Blocking IO,即同步非阻塞IO。是用戶進程調用read時,用戶進程由用戶態轉換到內核態后,此時如果沒有系統資源數據能夠被讀取到內核緩沖區中,返回read失敗,并從內核態切換回用戶態。也就是用戶進程發起IO操作后會立即得到一個操作結果。
Non Blocking IO示意圖如下所示。
簡單的Non Blocking IO的Java編程實現如下。
public class NonbioServer { public static final List<SocketChannel> clientSocketChannels = new ArrayList<>(); public static void main(String[] args) throws Exception { // 客戶端創建listen-socket管道 // 管道支持非阻塞模式和同時讀寫 ServerSocketChannel listenSocketChannel = ServerSocketChannel.open(); // 設置為非阻塞模式 listenSocketChannel.configureBlocking(false); // 綁定監聽的端口號 listenSocketChannel.socket().bind(new InetSocketAddress(8080)); // 在子線程中遍歷clientSocketChannels并讀取客戶端數據 handleSocketChannels(); while (true) { // 非阻塞方式監聽客戶端連接 // 如果無客戶端連接則返回空 // 有客戶端連接則創建代表這個客戶端的client-socket管道 SocketChannel clientSocketChannel = listenSocketChannel.accept(); if (clientSocketChannel != null) { // 設置為非阻塞模式 clientSocketChannel.configureBlocking(false); // 添加到clientSocketChannels中 // 用于子線程遍歷并讀取客戶端數據 clientSocketChannels.add(clientSocketChannel); } else { LockSupport.parkNanos(1000 * 1000 * 1000); } } } public static void handleSocketChannels() { new Thread(() -> { while (true) { // 遍歷每一個client-socket管道 Iterator<SocketChannel> iterator = clientSocketChannels.iterator(); while (iterator.hasNext()) { SocketChannel clientSocketChannel = iterator.next(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); int read = 0; try { // 將客戶端發送的數據讀取到ByteBuffer中 // 這一步的操作也是非阻塞的 read = clientSocketChannel.read(byteBuffer); } catch (IOException e) { // 移除發生異常的client-socket管道 iterator.remove(); e.printStackTrace(); } if (read == 0) { System.out.println("客戶端數據未就緒"); } else { System.out.println("客戶端數據為:" + new String(byteBuffer.array())); } } LockSupport.parkNanos(1000 * 1000 * 1000); } }).start(); } }
上述是Non Blocking IO的一個簡單服務端的實現,相較于BIO,服務端在accept時是非阻塞的,在讀取客戶端數據時也是非阻塞的,但是還是存在如下問題。
一次只能accept一個Socket;
需要在用戶進程中遍歷所有的SocketChannel并調用read() 方法獲取客戶端數據,此時如果客戶端數據未準備就緒,那么這一次的read() 操作的開銷就是浪費的。
在上述的BIO和Non Blocking IO中,一次系統調用,只會獲取一個IO的狀態,而如果采取IO多路復用機制,則可以一次系統調用獲取多個IO的狀態。
也就是獲取多個IO的狀態可以復用一次系統調用。
最簡單的IO多路復用方式是基于select模型實現,步驟如下。
在用戶進程中將需要監控的IO文件描述符(Socket)注冊到IO多路復用器中;
執行select操作,此時用戶進程由用戶態轉換到內核態(一次系統調用),然后在內核態中會輪詢注冊到IO多路復用器中的IO是否準備就緒,并得到所有準備就緒的IO的文件描述符列表,最后返回這些文件描述符列表;
用戶進程在select操作返回前會一直阻塞,直至select操作返回,此時用戶進程就獲得了所有就緒的IO的文件描述符列表;
用戶進程獲得了就緒的IO的文件描述符列表后,就可以對這些IO進行相應的操作了。
換言之,IO多路復用中,只需要一次系統調用,IO多路復用器就可以告訴用戶進程,哪些IO已經準備就緒可以進行操作了,而如果不采用IO多路復用,則需要用戶進程自己遍歷每個IO并調用accept() 或者read() 方法去判斷,且一次accept() 或者read() 方法調用只能判斷一個IO。
NIO,即New IO。關于NIO,有如下三大組件。
channel(管道)。介于buffer(字節緩沖區)和Socket(套接字)之間,用于數據的讀寫操作;
buffer(字節緩沖區)。是用戶程序和channel(管道)之間進行讀寫數據的中間區域;
selector(IO多路復用器)。服務端的listen-socket和client-socket,客戶端的connect-socket,都可以注冊在selector上,注冊的時候還需要指定監聽的事件,比如為listen-socket指定監聽的事件為ACCEPT事件,該事件發生則表示客戶端建立了連接,還比如為client-socket指定監聽的事件為READ事件,該事件發生則表示客戶端發送的數據已經可讀。
NIO的代碼實現如下所示。
服務端實現
public class NioServer { private static Selector selector; public static void main(String[] args) { try { // 開啟并得到多路復用器 selector = Selector.open(); // 服務端創建listen-socket管道 ServerSocketChannel listenSocketChannel = ServerSocketChannel.open(); // 設置為非阻塞模式 listenSocketChannel.configureBlocking(false); // 為管道綁定端口 listenSocketChannel.socket().bind(new InetSocketAddress(8080)); // 將listen-socket管道注冊到多路復用器上,并指定監聽ACCEPT事件 listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { // 獲取發生的事件,這個操作是阻塞的 selector.select(); // 拿到有事件發生的SelectionKey集合 // SelectionKey表示管道與多路復用器的綁定關系 Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍歷每個發生的事件,然后判斷事件類型 // 根據事件類型,進行不同的處理 Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); if (selectionKey.isAcceptable()) { // 處理客戶端連接事件 handlerAccept(selectionKey); } else if (selectionKey.isReadable()) { // 處理客戶端數據可讀事件 handlerRead(selectionKey); } } LockSupport.parkNanos(1000 * 1000 * 1000); } } catch (IOException e) { e.printStackTrace(); } } private static void handlerAccept(SelectionKey selectionKey) { // 從事件中獲取到listen-socket管道 ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel(); try { // 為連接的客戶端創建client-socket管道 SocketChannel clientSocketChannel = listenSocketChannel.accept(); // 設置為非阻塞模式 clientSocketChannel.configureBlocking(false); // 將client-socket管道注冊到多路復用器上,并指定監聽READ事件 clientSocketChannel.register(selector, SelectionKey.OP_READ); // 給客戶端發送數據 clientSocketChannel.write(ByteBuffer.wrap("連接已建立\n".getBytes())); } catch (IOException e) { e.printStackTrace(); } } private static void handlerRead(SelectionKey selectionKey) { // 從事件中獲取到client-socket管道 SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); try { // 讀取客戶端數據 int read = clientSocketChannel.read(byteBuffer); if (read <= 0) { // 關閉管道 clientSocketChannel.close(); // 從多路復用器移除綁定關系 selectionKey.cancel(); } else { System.out.println(new String(byteBuffer.array())); } } catch (IOException e1) { try { // 關閉管道 clientSocketChannel.close(); } catch (IOException e2) { e2.printStackTrace(); } // 從多路復用器移除綁定關系 selectionKey.cancel(); e1.printStackTrace(); } } }
客戶端實現
public class NioClient { private static Selector selector; public static final String SERVER_IP = "127.0.0.1"; public static final int SERVER_PORT = 8080; public static void main(String[] args) { try { // 開啟并得到多路復用器 selector = Selector.open(); // 創建connect-socket管道 SocketChannel connectSocketChannel = SocketChannel.open(); // 設置為非阻塞模式 connectSocketChannel.configureBlocking(false); // 設置服務端IP和端口 connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT)); // 將connect-socket管道注冊到多路復用器上,并指定監聽CONNECT事件 connectSocketChannel.register(selector, SelectionKey.OP_CONNECT); while (true) { // 獲取發生的事件,這個操作是阻塞的 selector.select(); // 拿到有事件發生的SelectionKey集合 // SelectionKey表示管道與多路復用器的綁定關系 Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 遍歷每個發生的事件,然后判斷事件類型 // 根據事件類型,進行不同的處理 Iterator<SelectionKey> iterator = selectionKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); if (selectionKey.isConnectable()) { // 處理連接建立事件 handlerConnect(selectionKey); } else if (selectionKey.isReadable()) { // 處理服務端數據可讀事件 handlerRead(selectionKey); } } LockSupport.parkNanos(1000 * 1000 * 1000); } } catch (IOException e) { e.printStackTrace(); } } private static void handlerConnect(SelectionKey selectionKey) throws IOException { // 拿到connect-socket管道 SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel(); if (connectSocketChannel.isConnectionPending()) { connectSocketChannel.finishConnect(); } // 設置為非阻塞模式 connectSocketChannel.configureBlocking(false); // 將connect-socket管道注冊到多路復用器上,并指定監聽READ事件 connectSocketChannel.register(selector, SelectionKey.OP_READ); // 向服務端發送數據 connectSocketChannel.write(ByteBuffer.wrap("客戶端發送的數據\n".getBytes())); } private static void handlerRead(SelectionKey selectionKey) { // 拿到connect-socket管道 SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); try { // 讀取服務端數據 int read = connectSocketChannel.read(byteBuffer); if (read <= 0) { // 關閉管道 connectSocketChannel.close(); // 從多路復用器移除綁定關系 selectionKey.cancel(); } else { System.out.println(new String(byteBuffer.array())); } } catch (IOException e1) { try { // 關閉管道 connectSocketChannel.close(); } catch (IOException e2) { e2.printStackTrace(); } // 從多路復用器移除綁定關系 selectionKey.cancel(); e1.printStackTrace(); } } }
到此,相信大家對“Java IO網絡模型如何實現”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。