网络编程中BIO和NIO的区别
網絡編程中BIO和NIO的區別
先上結論
BIO中,每個請求因為要阻塞直到結果返回,所以比較好的解決是每個請求都需要一個線程來處理,但是線程又是他的制約條件。
NIO中,每個請求進來都會綁定到一個channel上,然后channel注冊給一個 selector(多路選擇器),多路選擇器可以在channel有消息的時候進行處理。
保證了只有selector在輪詢查找。最開始的 selector 使用的是 select方法,jdk使用了優化后的epoll,避免了select有數量限制(1024/2048),在感興趣的channel上看是否有該類型數據出現,如果有,則調用處理,沒有則繼續睡眠,等待喚醒。
selector 使用reactor模式,將事件監聽分離(讀就緒/寫就緒/鏈接就緒),對每個事件都可以進行獨立的操作。
reactor模式其實就是:注冊所有感興趣的事件處理器,單線程輪詢選擇就緒事件,執行事件處理器。注冊監聽(每個請求來都做) + 輪詢選擇(單線程處理) + 事件處理。
BIO
缺點:每來一個請求就需要一個線程來處理,線程太多容易造成系統不可用.最開始的Tomcat使用的就是BIO
優化:通過線程池來管理線程,但是造成新的缺點:請求太多時不能被處理的請求就回阻塞,等待。不能被處理。正因為限制了線程數量,如果發生大量并發請求,超過最大數量的線程就只能等待,直到線程池中的有空閑的線程可以被復用。而對Socket的輸入流就行讀取時,會一直阻塞。
服務端代碼
package order.core.common;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.util.concurrent.ThreadPoolExecutor;import javax.sound.sampled.Line;public class BIOServer {private static final int PORT = 8000;public static void main(String[] args) throws IOException {ServerSocket serverSocket = null;Socket socket = null;ThreadPoolExecutor threadPoolExecutor = null;try {serverSocket = new ServerSocket(PORT);System.out.println("serverSocket 啟動了...");while (true) {socket = serverSocket.accept();System.out.println("接受到socket...");new Thread(new MyThread(socket)).start();}} catch (Exception e) {// TODO: handle exception} finally {socket.close();serverSocket.close();}} }class MyThread implements Runnable {private Socket socket;public MyThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);String expression;String result;// 通過BufferedReader讀取一行// 如果已經讀到輸入流尾部,返回null,退出循環// 如果得到非空值,就嘗試計算結果并返回if ((expression = in.readLine()) != null) {System.out.println("服務器收到消息:" + expression);out.print(5678978);}System.out.println("=========");} catch (Exception e) {e.printStackTrace();} finally {// 一些必要的清理工作if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (out != null) {out.close();out = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}}}客戶端代碼
package order.core.common;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket;public class BIOClient {private static final String IP = "127.0.0.1";// 默認的端口號private static int DEFAULT_SERVER_PORT = 8000;private static String DEFAULT_SERVER_IP = "127.0.0.1";public static void send(String expression) {send(DEFAULT_SERVER_PORT, expression);}public static void main(String[] args) {send("12345");}public static void send(int port, String expression) {System.out.println("發送消息為:" + expression);Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket(DEFAULT_SERVER_IP, port);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println(expression);System.out.println("___結果為:" + in.readLine());} catch (Exception e) {e.printStackTrace();} finally {// 一下必要的清理工作if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (out != null) {out.close();out = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}} }NIO
優點:只需要打開Selector和 ServerSocketChannel 并開啟非阻塞模式。然后只需要將ServerSocketChannel注冊給selector即可。Tomcat后續默認全部使用NIO
監聽的selector有四種:
public static final int OP_READ = 1 << 0;public static final int OP_WRITE = 1 << 2;public static final int OP_CONNECT = 1 << 3;public static final int OP_ACCEPT = 1 << 4;服務端監聽的是OP_ACCEPT。
客戶端監聽的是OP_CONNECT。
發送消息類的監聽OP_READ。
2.1、簡介
NIO我們一般認為是New I/O(也是官方的叫法),因為它是相對于老的I/O類庫新增的(其實在JDK 1.4中就已經被引入了,但這個名詞還會繼續用很久,即使它們在現在看來已經是“舊”的了,所以也提示我們在命名時,需要好好考慮),做了很大的改變。但民間跟多人稱之為Non-block I/O,即非阻塞I/O,因為這樣叫,更能體現它的特點。而下文中的NIO,不是指整個新的I/O庫,而是非阻塞I/O。
NIO提供了與傳統BIO模型中的Socket和ServerSocket相對應的SocketChannel和ServerSocketChannel兩種不同的套接字通道實現。
新增的著兩種通道都支持阻塞和非阻塞兩種模式。
阻塞模式使用就像傳統中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。
對于低負載、低并發的應用程序,可以使用同步阻塞I/O來提升開發速率和更好的維護性;對于高負載、高并發的(網絡)應用,應使用NIO的非阻塞模式來開發。
下面會先對基礎知識進行介紹。
2.2、緩沖區 Buffer
Buffer是一個對象,包含一些要寫入或者讀出的數據。
在NIO庫中,所有數據都是用緩沖區處理的。在讀取數據時,它是直接讀到緩沖區中的;在寫入數據時,也是寫入到緩沖區中。任何時候訪問NIO中的數據,都是通過緩沖區進行操作。
緩沖區實際上是一個數組,并提供了對數據結構化訪問以及維護讀寫位置等信息。
具體的緩存區有這些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他們實現了相同的接口:Buffer。
2.3、通道 Channel
我們對數據的讀取和寫入要通過Channel,它就像水管一樣,是一個通道。通道不同于流的地方就是通道是雙向的,可以用于讀、寫和同時讀寫操作。
底層的操作系統的通道一般都是全雙工的,所以全雙工的Channel比流能更好的映射底層操作系統的API。
Channel主要分兩大類:
SelectableChannel:用戶網絡讀寫 FileChannel:用于文件操作
后面代碼會涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子類。
2.4、多路復用器 Selector
Selector是Java NIO 編程的基礎。
Selector提供選擇已經就緒的任務的能力:Selector會不斷輪詢注冊在其上的Channel,如果某個Channel上面發生讀或者寫事件,這個Channel就處于就緒狀態,會被Selector輪詢出來,然后通過SelectionKey可以獲取就緒Channel的集合,進行后續的I/O操作。
一個Selector可以同時輪詢多個Channel,因為JDK使用了epoll()代替傳統的select實現,所以沒有最大連接句柄1024/2048的限制。所以,只需要一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端。
服務端代碼
package order.core.common;import java.io.IOException; import java.net.InetSocketAddress; 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 Server {private static int DEFAULT_PORT = 8001;private static ServerHandle serverHandle;public static void start() {start(DEFAULT_PORT);}public static synchronized void start(int port) {if (serverHandle != null)serverHandle.stop();serverHandle = new ServerHandle(port);new Thread(serverHandle, "Server").start();}public static void main(String[] args) {start();} }class ServerHandle implements Runnable {private Selector selector;private ServerSocketChannel serverChannel;private volatile boolean started;/*** 構造方法* * @param port* 指定要監聽的端口號*/public ServerHandle(int port) {try {// 創建選擇器selector = Selector.open();// 打開監聽通道serverChannel = ServerSocketChannel.open();// 如果為 true,則此通道將被置于阻塞模式;如果為 false,則此通道將被置于非阻塞模式serverChannel.configureBlocking(false);// 開啟非阻塞模式// 綁定端口 backlog設為1024serverChannel.bind(new InetSocketAddress("localhost",port));// 監聽客戶端連接請求serverChannel.register(selector, SelectionKey.OP_ACCEPT);// 標記服務器已開啟started = true;System.out.println("服務器已啟動,端口號:" + port);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void stop() {started = false;}@Overridepublic void run() {// 循環遍歷selectorwhile (started) {try {// 無論是否有讀寫事件發生,selector每隔1s被喚醒一次selector.select(1000);// 阻塞,只有當至少一個注冊的事件發生的時候才會繼續.// selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null) {key.channel().close();}}}}} catch (Throwable t) {t.printStackTrace();}}// selector關閉后會自動釋放里面管理的資源if (selector != null)try {selector.close();} catch (Exception e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {// 處理新接入的請求消息if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();// 通過ServerSocketChannel的accept創建SocketChannel實例// 完成該操作意味著完成TCP三次握手,TCP物理鏈路正式建立SocketChannel sc = ssc.accept();// 設置為非阻塞的sc.configureBlocking(false);// 注冊為讀sc.register(selector, SelectionKey.OP_READ);}// 讀消息if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();// 創建ByteBuffer,并開辟一個1M的緩沖區ByteBuffer buffer = ByteBuffer.allocate(1024);// 讀取請求碼流,返回讀取到的字節數int readBytes = sc.read(buffer);// 讀取到字節,對字節進行編解碼if (readBytes > 0) {// 將緩沖區當前的limit設置為position=0,用于后續對緩沖區的讀取操作buffer.flip();// 根據緩沖區可讀字節數創建字節數組byte[] bytes = new byte[buffer.remaining()];// 將緩沖區可讀字節數組復制到新建的數組中buffer.get(bytes);String expression = new String(bytes, "UTF-8");System.out.println("服務器收到消息:" + expression);// 處理數據String result = null;try {result = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";} catch (Exception e) {result = "計算錯誤:" + e.getMessage();}// 發送應答消息doWrite(sc, result);}// 沒有讀取到字節 忽略// else if(readBytes==0);// 鏈路已經關閉,釋放資源else if (readBytes < 0) {key.cancel();sc.close();}}}}// 異步發送應答消息private void doWrite(SocketChannel channel, String response) throws IOException {// 將消息編碼為字節數組byte[] bytes = response.getBytes();// 根據數組容量創建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);// 將字節數組復制到緩沖區writeBuffer.put(bytes);// flip操作writeBuffer.flip();// 發送緩沖區的字節數組channel.write(writeBuffer);// ****此處不含處理“寫半包”的代碼} }客戶端代碼
package order.core.common; import java.io.IOException; 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; import java.util.Set;public class Client {private static String DEFAULT_HOST = "127.0.0.1";private static int DEFAULT_PORT = 8001;private static ClientHandler clientHandle;public static void start(){start(DEFAULT_HOST,DEFAULT_PORT);}public static synchronized void start(String ip,int port){if(clientHandle!=null)clientHandle.stop();clientHandle = new ClientHandler(ip,port);new Thread(clientHandle,"Server").start();}//向服務器發送消息public static boolean sendMsg(String msg) throws Exception{clientHandle.sendMsg(msg);return true;}public static void main(String[] args) throws Exception{start();sendMsg("aaaaaaaaa");} }class ClientHandler implements Runnable{private String host;private int port;private Selector selector;private static SocketChannel socketChannel;private static volatile boolean started;public ClientHandler(String ip,int port) {this.host = ip;this.port = port;try{//創建選擇器selector = Selector.open();//打開監聽通道socketChannel = SocketChannel.open();//如果為 true,則此通道將被置于阻塞模式;如果為 false,則此通道將被置于非阻塞模式socketChannel.configureBlocking(false);//開啟非阻塞模式socketChannel.connect(new InetSocketAddress("127.0.0.1", port));System.out.println(socketChannel.finishConnect());socketChannel.register(selector, SelectionKey.OP_CONNECT);started = true;}catch(IOException e){e.printStackTrace();System.exit(1);}}public void stop(){started = false;}@Overridepublic void run() {//循環遍歷selectorwhile(started){try{//無論是否有讀寫事件發生,selector每隔1s被喚醒一次selector.select(1000);//阻塞,只有當至少一個注冊的事件發生的時候才會繼續. // selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> it = keys.iterator();SelectionKey key = null;while(it.hasNext()){key = it.next();it.remove();try{handleInput(key);}catch(Exception e){if(key != null){key.cancel();if(key.channel() != null){key.channel().close();}}}}}catch(Exception e){e.printStackTrace();System.exit(1);}}}private void handleInput(SelectionKey key) throws IOException{if(key.isValid()){SocketChannel sc = (SocketChannel) key.channel();if(key.isConnectable()){if(sc.finishConnect());else System.exit(1);}//讀消息if(key.isReadable()){//創建ByteBuffer,并開辟一個1M的緩沖區ByteBuffer buffer = ByteBuffer.allocate(1024);//讀取請求碼流,返回讀取到的字節數int readBytes = sc.read(buffer);//讀取到字節,對字節進行編解碼if(readBytes>0){//將緩沖區當前的limit設置為position=0,用于后續對緩沖區的讀取操作buffer.flip();//根據緩沖區可讀字節數創建字節數組byte[] bytes = new byte[buffer.remaining()];//將緩沖區可讀字節數組復制到新建的數組中buffer.get(bytes);String result = new String(bytes,"UTF-8");System.out.println("客戶端收到消息:" + result);}//沒有讀取到字節 忽略 // else if(readBytes==0);//鏈路已經關閉,釋放資源else if(readBytes<0){key.cancel();sc.close();}}}}//異步發送消息private void doWrite(SocketChannel channel,String request) throws IOException{//將消息編碼為字節數組byte[] bytes = request.getBytes();//根據數組容量創建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);//將字節數組復制到緩沖區writeBuffer.put(bytes);//flip操作writeBuffer.flip();//發送緩沖區的字節數組channel.write(writeBuffer);//****此處不含處理“寫半包”的代碼}public void doConnect() throws IOException{System.out.println("==");try {System.out.println(socketChannel.isOpen());System.out.println(socketChannel.isConnected());} catch (Exception e) {// TODO: handle exception}}public void sendMsg(String msg) throws Exception{socketChannel.register(selector, SelectionKey.OP_READ);doWrite(socketChannel, msg);} }總結
以上是生活随笔為你收集整理的网络编程中BIO和NIO的区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用redis实现消息队列(实时消费+ac
- 下一篇: 高并发系统处理之——限流