网络编程中BIO和NIO的区别
網(wǎng)絡(luò)編程中BIO和NIO的區(qū)別
先上結(jié)論
BIO中,每個(gè)請(qǐng)求因?yàn)橐枞钡浇Y(jié)果返回,所以比較好的解決是每個(gè)請(qǐng)求都需要一個(gè)線程來(lái)處理,但是線程又是他的制約條件。
NIO中,每個(gè)請(qǐng)求進(jìn)來(lái)都會(huì)綁定到一個(gè)channel上,然后channel注冊(cè)給一個(gè) selector(多路選擇器),多路選擇器可以在channel有消息的時(shí)候進(jìn)行處理。
保證了只有selector在輪詢查找。最開始的 selector 使用的是 select方法,jdk使用了優(yōu)化后的epoll,避免了select有數(shù)量限制(1024/2048),在感興趣的channel上看是否有該類型數(shù)據(jù)出現(xiàn),如果有,則調(diào)用處理,沒有則繼續(xù)睡眠,等待喚醒。
selector 使用reactor模式,將事件監(jiān)聽分離(讀就緒/寫就緒/鏈接就緒),對(duì)每個(gè)事件都可以進(jìn)行獨(dú)立的操作。
reactor模式其實(shí)就是:注冊(cè)所有感興趣的事件處理器,單線程輪詢選擇就緒事件,執(zhí)行事件處理器。注冊(cè)監(jiān)聽(每個(gè)請(qǐng)求來(lái)都做) + 輪詢選擇(單線程處理) + 事件處理。
BIO
缺點(diǎn):每來(lái)一個(gè)請(qǐng)求就需要一個(gè)線程來(lái)處理,線程太多容易造成系統(tǒng)不可用.最開始的Tomcat使用的就是BIO
優(yōu)化:通過(guò)線程池來(lái)管理線程,但是造成新的缺點(diǎn):請(qǐng)求太多時(shí)不能被處理的請(qǐng)求就回阻塞,等待。不能被處理。正因?yàn)橄拗屏司€程數(shù)量,如果發(fā)生大量并發(fā)請(qǐng)求,超過(guò)最大數(shù)量的線程就只能等待,直到線程池中的有空閑的線程可以被復(fù)用。而對(duì)Socket的輸入流就行讀取時(shí),會(huì)一直阻塞。
服務(wù)端代碼
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 啟動(dòng)了...");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;// 通過(guò)BufferedReader讀取一行// 如果已經(jīng)讀到輸入流尾部,返回null,退出循環(huán)// 如果得到非空值,就嘗試計(jì)算結(jié)果并返回if ((expression = in.readLine()) != null) {System.out.println("服務(wù)器收到消息:" + 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";// 默認(rèn)的端口號(hào)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("發(fā)送消息為:" + 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("___結(jié)果為:" + 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
優(yōu)點(diǎn):只需要打開Selector和 ServerSocketChannel 并開啟非阻塞模式。然后只需要將ServerSocketChannel注冊(cè)給selector即可。Tomcat后續(xù)默認(rèn)全部使用NIO
監(jiān)聽的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;服務(wù)端監(jiān)聽的是OP_ACCEPT。
客戶端監(jiān)聽的是OP_CONNECT。
發(fā)送消息類的監(jiān)聽OP_READ。
2.1、簡(jiǎn)介
NIO我們一般認(rèn)為是New I/O(也是官方的叫法),因?yàn)樗窍鄬?duì)于老的I/O類庫(kù)新增的(其實(shí)在JDK 1.4中就已經(jīng)被引入了,但這個(gè)名詞還會(huì)繼續(xù)用很久,即使它們?cè)诂F(xiàn)在看來(lái)已經(jīng)是“舊”的了,所以也提示我們?cè)诿麜r(shí),需要好好考慮),做了很大的改變。但民間跟多人稱之為Non-block I/O,即非阻塞I/O,因?yàn)檫@樣叫,更能體現(xiàn)它的特點(diǎn)。而下文中的NIO,不是指整個(gè)新的I/O庫(kù),而是非阻塞I/O。
NIO提供了與傳統(tǒng)BIO模型中的Socket和ServerSocket相對(duì)應(yīng)的SocketChannel和ServerSocketChannel兩種不同的套接字通道實(shí)現(xiàn)。
新增的著兩種通道都支持阻塞和非阻塞兩種模式。
阻塞模式使用就像傳統(tǒng)中的支持一樣,比較簡(jiǎn)單,但是性能和可靠性都不好;非阻塞模式正好與之相反。
對(duì)于低負(fù)載、低并發(fā)的應(yīng)用程序,可以使用同步阻塞I/O來(lái)提升開發(fā)速率和更好的維護(hù)性;對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用NIO的非阻塞模式來(lái)開發(fā)。
下面會(huì)先對(duì)基礎(chǔ)知識(shí)進(jìn)行介紹。
2.2、緩沖區(qū) Buffer
Buffer是一個(gè)對(duì)象,包含一些要寫入或者讀出的數(shù)據(jù)。
在NIO庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的;在寫入數(shù)據(jù)時(shí),也是寫入到緩沖區(qū)中。任何時(shí)候訪問(wèn)NIO中的數(shù)據(jù),都是通過(guò)緩沖區(qū)進(jìn)行操作。
緩沖區(qū)實(shí)際上是一個(gè)數(shù)組,并提供了對(duì)數(shù)據(jù)結(jié)構(gòu)化訪問(wèn)以及維護(hù)讀寫位置等信息。
具體的緩存區(qū)有這些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他們實(shí)現(xiàn)了相同的接口:Buffer。
2.3、通道 Channel
我們對(duì)數(shù)據(jù)的讀取和寫入要通過(guò)Channel,它就像水管一樣,是一個(gè)通道。通道不同于流的地方就是通道是雙向的,可以用于讀、寫和同時(shí)讀寫操作。
底層的操作系統(tǒng)的通道一般都是全雙工的,所以全雙工的Channel比流能更好的映射底層操作系統(tǒng)的API。
Channel主要分兩大類:
SelectableChannel:用戶網(wǎng)絡(luò)讀寫 FileChannel:用于文件操作
后面代碼會(huì)涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子類。
2.4、多路復(fù)用器 Selector
Selector是Java NIO 編程的基礎(chǔ)。
Selector提供選擇已經(jīng)就緒的任務(wù)的能力:Selector會(huì)不斷輪詢注冊(cè)在其上的Channel,如果某個(gè)Channel上面發(fā)生讀或者寫事件,這個(gè)Channel就處于就緒狀態(tài),會(huì)被Selector輪詢出來(lái),然后通過(guò)SelectionKey可以獲取就緒Channel的集合,進(jìn)行后續(xù)的I/O操作。
一個(gè)Selector可以同時(shí)輪詢多個(gè)Channel,因?yàn)镴DK使用了epoll()代替?zhèn)鹘y(tǒng)的select實(shí)現(xiàn),所以沒有最大連接句柄1024/2048的限制。所以,只需要一個(gè)線程負(fù)責(zé)Selector的輪詢,就可以接入成千上萬(wàn)的客戶端。
服務(wù)端代碼
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;/*** 構(gòu)造方法* * @param port* 指定要監(jiān)聽的端口號(hào)*/public ServerHandle(int port) {try {// 創(chuàng)建選擇器selector = Selector.open();// 打開監(jiān)聽通道serverChannel = ServerSocketChannel.open();// 如果為 true,則此通道將被置于阻塞模式;如果為 false,則此通道將被置于非阻塞模式serverChannel.configureBlocking(false);// 開啟非阻塞模式// 綁定端口 backlog設(shè)為1024serverChannel.bind(new InetSocketAddress("localhost",port));// 監(jiān)聽客戶端連接請(qǐng)求serverChannel.register(selector, SelectionKey.OP_ACCEPT);// 標(biāo)記服務(wù)器已開啟started = true;System.out.println("服務(wù)器已啟動(dòng),端口號(hào):" + port);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void stop() {started = false;}@Overridepublic void run() {// 循環(huán)遍歷selectorwhile (started) {try {// 無(wú)論是否有讀寫事件發(fā)生,selector每隔1s被喚醒一次selector.select(1000);// 阻塞,只有當(dāng)至少一個(gè)注冊(cè)的事件發(fā)生的時(shí)候才會(huì)繼續(xù).// 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關(guān)閉后會(huì)自動(dòng)釋放里面管理的資源if (selector != null)try {selector.close();} catch (Exception e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {// 處理新接入的請(qǐng)求消息if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();// 通過(guò)ServerSocketChannel的accept創(chuàng)建SocketChannel實(shí)例// 完成該操作意味著完成TCP三次握手,TCP物理鏈路正式建立SocketChannel sc = ssc.accept();// 設(shè)置為非阻塞的sc.configureBlocking(false);// 注冊(cè)為讀sc.register(selector, SelectionKey.OP_READ);}// 讀消息if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();// 創(chuàng)建ByteBuffer,并開辟一個(gè)1M的緩沖區(qū)ByteBuffer buffer = ByteBuffer.allocate(1024);// 讀取請(qǐng)求碼流,返回讀取到的字節(jié)數(shù)int readBytes = sc.read(buffer);// 讀取到字節(jié),對(duì)字節(jié)進(jìn)行編解碼if (readBytes > 0) {// 將緩沖區(qū)當(dāng)前的limit設(shè)置為position=0,用于后續(xù)對(duì)緩沖區(qū)的讀取操作buffer.flip();// 根據(jù)緩沖區(qū)可讀字節(jié)數(shù)創(chuàng)建字節(jié)數(shù)組byte[] bytes = new byte[buffer.remaining()];// 將緩沖區(qū)可讀字節(jié)數(shù)組復(fù)制到新建的數(shù)組中buffer.get(bytes);String expression = new String(bytes, "UTF-8");System.out.println("服務(wù)器收到消息:" + expression);// 處理數(shù)據(jù)String result = null;try {result = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";} catch (Exception e) {result = "計(jì)算錯(cuò)誤:" + e.getMessage();}// 發(fā)送應(yīng)答消息doWrite(sc, result);}// 沒有讀取到字節(jié) 忽略// else if(readBytes==0);// 鏈路已經(jīng)關(guān)閉,釋放資源else if (readBytes < 0) {key.cancel();sc.close();}}}}// 異步發(fā)送應(yīng)答消息private void doWrite(SocketChannel channel, String response) throws IOException {// 將消息編碼為字節(jié)數(shù)組byte[] bytes = response.getBytes();// 根據(jù)數(shù)組容量創(chuàng)建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);// 將字節(jié)數(shù)組復(fù)制到緩沖區(qū)writeBuffer.put(bytes);// flip操作writeBuffer.flip();// 發(fā)送緩沖區(qū)的字節(jié)數(shù)組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();}//向服務(wù)器發(fā)送消息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{//創(chuàng)建選擇器selector = Selector.open();//打開監(jiān)聽通道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() {//循環(huán)遍歷selectorwhile(started){try{//無(wú)論是否有讀寫事件發(fā)生,selector每隔1s被喚醒一次selector.select(1000);//阻塞,只有當(dāng)至少一個(gè)注冊(cè)的事件發(fā)生的時(shí)候才會(huì)繼續(xù). // 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()){//創(chuàng)建ByteBuffer,并開辟一個(gè)1M的緩沖區(qū)ByteBuffer buffer = ByteBuffer.allocate(1024);//讀取請(qǐng)求碼流,返回讀取到的字節(jié)數(shù)int readBytes = sc.read(buffer);//讀取到字節(jié),對(duì)字節(jié)進(jìn)行編解碼if(readBytes>0){//將緩沖區(qū)當(dāng)前的limit設(shè)置為position=0,用于后續(xù)對(duì)緩沖區(qū)的讀取操作buffer.flip();//根據(jù)緩沖區(qū)可讀字節(jié)數(shù)創(chuàng)建字節(jié)數(shù)組byte[] bytes = new byte[buffer.remaining()];//將緩沖區(qū)可讀字節(jié)數(shù)組復(fù)制到新建的數(shù)組中buffer.get(bytes);String result = new String(bytes,"UTF-8");System.out.println("客戶端收到消息:" + result);}//沒有讀取到字節(jié) 忽略 // else if(readBytes==0);//鏈路已經(jīng)關(guān)閉,釋放資源else if(readBytes<0){key.cancel();sc.close();}}}}//異步發(fā)送消息private void doWrite(SocketChannel channel,String request) throws IOException{//將消息編碼為字節(jié)數(shù)組byte[] bytes = request.getBytes();//根據(jù)數(shù)組容量創(chuàng)建ByteBufferByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);//將字節(jié)數(shù)組復(fù)制到緩沖區(qū)writeBuffer.put(bytes);//flip操作writeBuffer.flip();//發(fā)送緩沖區(qū)的字節(jié)數(shù)組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);} }總結(jié)
以上是生活随笔為你收集整理的网络编程中BIO和NIO的区别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 用redis实现消息队列(实时消费+ac
- 下一篇: 高并发系统处理之——限流