對於應用服務器來說,性能是非常重要的,基本可以說決定着這款應用服務器的未來。通常從軟件角度來說,應用服務器性能包括如下幾個方面:
1、請求處理的並發程度,當前主流服務器均采用異步的方式處理客戶端的請求;
2、減少網絡傳輸的數據量,提高網絡利用率;
3、降低新建網絡鏈接的開銷,以實現鏈接在多個請求之間的復用;
4、選擇合適的I/O方式,例如NIO等。
一、阻塞與非阻塞、同步與異步
------同步:發出一個調用時,沒有得到結果之前,該調用不返回,由調用者主動等待調用結果。 | 關注的是消息通信機制---------------------| | ------異步:調用發出之后,調用直接返回,此時不會拿到返回結果。被調用者通過狀態通知調用者或回調函數處理這個調用。 ------阻塞:調用結果返回之前,當前線程會被掛起。 | 關注的是程序在等待調用結果時的狀態---------| | ------非阻塞:調用返回結果之前,當前線程不會被掛起。
二、BIO
概念:bio基於流,是同步阻塞IO模式,數據的讀取寫入必須阻塞在一個線程內等待其完成。這里使用那個經典的燒開水例子,這里假設一個燒開水的場景,有一排水壺在燒開水,BIO的工作模式就是, 叫一個線程停留在一個水壺那,直到這個水壺燒開,才去處理下一 個水壺。但是實際上線程在等待水壺燒開的時間段什么都沒有做。不知道io操作中什么時候有數據可讀,所以一直是阻塞的模式。
缺點:當並發數達到一定量時,並且服務端需要一定的時間去處理請求時,例如1-2s,這時需要開啟非常多的線程數,並且這些線程啥事不干,都是等着請求返回,大大浪費了系統資源,而且在線程切換上下文的過程中,也會浪費很多的資源
BIO是阻塞式I/O,通過socket在客戶端與服務端建立雙向鏈接以實現通信,主要步驟如下:
- a、服務端監聽某個端口是否有鏈接請求;
- b、客戶端向服務端發出鏈接請求;
- c、服務端向客戶端返回accept()消息,此時鏈接成功;
- d、客戶端和服務端通過send()、write()等方法與對方通信;
- e、關閉鏈接
eg:簡單的網絡通信如下:
服務端:


客戶端:


這種簡單的示例只支持一個客戶端鏈接到一個服務端,現實情況是N個客戶端鏈接到服務端。Tomcat是這么實現的:

三、NIO
概念:bio的性能是相對較差的,在NIO中,基於塊的概念,可以在不編寫本地代碼的情況下利用底層優化。
NIO結構圖:

來個復雜點的:

selectionKey則是用來描述相關事件。
1、通道(channel)

2、緩沖區(buffer)

3、選擇器(selector)

簡單的NIO示例:
服務端:NIOServer
package com.ty.server; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; /** * 服務端 */ public class NIOServer { /** * 定義服務端的selector, * 主要作用: * 1、將各種事件注冊到selector中,selector監控各種事件的發生,例如accept、read等 * 2、將不同事件分配到不同的channel */ private Selector selector; //服務端初始化 public void init() throws IOException { //創建一個selector對象 this.selector = Selector.open(); //創建serverSocketChanel對象 ServerSocketChannel serverSocketChanel = ServerSocketChannel.open(); //設置為非阻塞 serverSocketChanel.configureBlocking(false); //通過serverSocketChannel對象獲取serverSocket ServerSocket serverSocket = serverSocketChanel.socket(); //綁定端口 InetSocketAddress address = new InetSocketAddress(8080); serverSocket.bind(address); //注冊accept事件到selector中,accept用於獲取客戶端請求 serverSocketChanel.register(selector, SelectionKey.OP_ACCEPT); } //服務端啟動服務 public void start() throws IOException { //這地方只做一個最簡單的示例,不考慮服務端stop while(true) { /** * selector監控客戶端是否有對應事件發生,例如accept、read等等。 * 注:此方法是阻塞的,當客戶端一直沒有事件觸發,線程一直掛起,直到至少有一事件觸發,走后續流程 */ selector.select(); //獲取該selector監控到的所有觸發的事件 Set<SelectionKey> selectionKeys = selector.selectedKeys(); //拿到迭代器,循環所有監控到的事件 Iterator<SelectionKey> iterator = selectionKeys.iterator(); while(iterator.hasNext()) { //事件用SelectionKey描述,主要包括connect、accept、read、write事件 SelectionKey selectionKey = iterator.next(); //每種事件只處理一次,避免重復處理 iterator.remove(); if(selectionKey.isAcceptable()) { accept(selectionKey); } if(selectionKey.isReadable()) { read(selectionKey); } } } } private void accept(SelectionKey selectionKey) throws IOException { /** * 從selectionkey中獲取serverSocketChannel。 * ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道, 就像標准IO中的ServerSocket一樣 */ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); //serverSocketChannel監聽到新連接后,會創建socketChannel。獲取socketChannel SocketChannel socketChannel = serverSocketChannel.accept(); //設置為非阻塞 socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } private void read(SelectionKey selectionKey) throws IOException { /** * SocketChannel是一個連接到TCP網絡套接字的通道,就像標准IO中的socket * 創建方式:可以通過以下2種方式創建SocketChannel * 1、打開一個SocketChannel並連接到互聯網上的某台服務器。 * 2、一個新連接到達ServerSocketChannel時,會創建一個SocketChannel。 */ SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); //創建讀取緩沖區 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //通過socketChannel.read()獲取客戶端的請求數據 socketChannel.read(byteBuffer); String request = new String(byteBuffer.array()).trim(); System.out.println("客戶端發送的請求為:" + request); //將一個數組包裝成ByteBuffer ByteBuffer outBuffer = ByteBuffer.wrap("請求收到啦!".getBytes()); //數據發送到客戶端 socketChannel.write(outBuffer); } public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.init(); server.start(); } }
客戶端:NIOClient
package com.ty.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOClient { private Selector selector; private BufferedReader clientInput = new BufferedReader(new InputStreamReader(System.in)); public void init() throws IOException { //創建selector this.selector = Selector.open(); //創建SocketChannel SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080)); //注冊connect事件 socketChannel.register(selector, SelectionKey.OP_CONNECT); } public void start() throws IOException { while(true) { selector.select(); Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()) { SelectionKey selectionKey = iterator.next(); iterator.remove(); if(selectionKey.isConnectable()) { connect(selectionKey); } if(selectionKey.isReadable()) { read(selectionKey); } } } } public void connect(SelectionKey selectionKey) throws IOException { SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); //如果客戶端正在鏈接 if(socketChannel.isConnectionPending()) { //如果客戶端已經鏈接成功 if(socketChannel.finishConnect()) { socketChannel.configureBlocking(false); //鏈接成功后自然要獲取服務端的返回,因此注冊read事件 socketChannel.register(selector, SelectionKey.OP_READ); String request = clientInput.readLine(); //數據發送到服務端 socketChannel.write(ByteBuffer.wrap(request.getBytes())); }else { //事件未注冊成功,取消掉 selectionKey.cancel(); } } } public void read(SelectionKey selectionKey) throws IOException { //socketChannel與服務端的對應,雙方友好建立一個通道 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); ByteBuffer byteBuffer = ByteBuffer.allocate(1024); socketChannel.read(byteBuffer); System.out.println("服務端響應為:" + new String(byteBuffer.array()).trim()); String request = clientInput.readLine(); socketChannel.write(ByteBuffer.wrap(request.getBytes())); } public static void main(String[] args) throws IOException { NIOClient client = new NIOClient(); client.init(); client.start(); } }
測試結果:
服務端:

客戶端:

這樣通過NIO,客戶端與服務端即可正常通信。
Tomcat中的NIO實現:



