日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

BIO与NIO比较

發布時間:2025/5/22 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 BIO与NIO比较 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • BIO 同步阻塞
    • BIO介紹
    • BIO的編程流程
    • BIO實現通信
      • 實現思路:
      • 服務器:
      • 客戶端:
  • NIO 同步非阻塞
    • NIO中重要組件
      • channel:通道
      • Buffer緩沖區
        • 基本用法
        • Buffer實現原理
        • Buffer常見方法
        • Buffer的分配
      • selector:選擇器
        • Selector概述
        • selector的使用
    • NIO非阻塞式網絡通信原理分析
    • NIO實現
      • 服務端實現
      • 客戶端
  • BIO與AIO區別

BIO 同步阻塞

服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動
一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷

BIO介紹

Java BIO 就是傳統的 java io 編程,其相關的類和接口在 java.ioBIO(blocking I/O) : 同步阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,可以通過線程池機制改善(實現多個客戶連接服務器).

BIO的編程流程

  • 服務器端啟動一個 ServerSocket,注冊端口,調用accpet方
    法監聽客戶端的Socket連接。
  • 客戶端啟動 Socket對服務器進行通信,默認情況下服務器端
    需要對每個客戶 建立一個線程與之通訊
  • BIO實現通信

    通過BIO+線程池完成少量用戶的通信架構

    實現思路:

    通過線程池控制解決為每個請求創建一個獨立線程造成線程資源耗盡的問題。
    存在的問題:
    但由于底層依然是采用的同步阻塞模型,因此無法從根本上解決問題。如果單個消息處理的緩慢,或者服務器線程池中的全部線程都被阻塞,那么后續socket的i/o消息都將在隊列中排隊。新的Socket請求將被拒絕,客戶端會發生大量連接超時。

    服務器:

    public class Server {public static void main(String[] args) {try {//注冊端口ServerSocket serverSocket = new ServerSocket(9999);//初始化一個線程對象HandlerSocketServerPool pool = new HandlerSocketServerPool(5, 20);//循環接受客戶端的請求while (true){Socket socket = serverSocket.accept();//將socket封裝成一個Runnable線程交給線程池ServerRunnableTarget runnable = new ServerRunnableTarget(socket);pool.execute(runnable);}} catch (Exception e) {e.printStackTrace();}} } //線程池類 class HandlerSocketServerPool{private ExecutorService executorService;//創建類的對象的時候初始化線程池對象public HandlerSocketServerPool(int maxThreadNum,int queuSize){executorService = new ThreadPoolExecutor(3, maxThreadNum, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queuSize));}/*** 提交一個方法來提交任務給線程池的任務隊列來暫時存儲,等著線程池的處理** */public void execute(Runnable target){executorService.execute(target);} } class ServerRunnableTarget implements Runnable{private Socket socket;public ServerRunnableTarget(Socket socket){this.socket=socket;}@Overridepublic void run() {//從Sacket得到一個字節輸入流try {InputStream inputStream = socket.getInputStream();//使用緩沖字符輸入流BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s;while ((s=bufferedReader.readLine())!=null){System.out.println(s);}} catch (Exception e) {e.printStackTrace();}} }

    客戶端:

    public class Client {public static void main(String[] args) throws IOException {System.out.println("客戶端啟動");Socket socket = new Socket("127.0.0.1",9999);OutputStream outputStream = socket.getOutputStream();//打印流PrintStream printStream = new PrintStream(outputStream);Scanner scanner = new Scanner(System.in);while (true){System.out.println("請說:");String s = scanner.nextLine();printStream.println(s);printStream.flush();}} }

    NIO 同步非阻塞

    NIO中重要組件

    channel:通道

    channel和用戶操作IO相連,但通道的使用是不能直接訪問數據的需要和緩沖區Buffer相連
    讀數據:將數據從channel中讀取到Buffer,從Buffer在獲取到數據
    寫數據:將數據線寫入Buffer,Buffer中的數據寫入到通道

    channel與流stream的區別:

    channel不僅能讀,也能寫,stream通常是要么讀要么寫 channel可以同步也可以異步寫 channel總是讀取或寫入一個Buffer中

    主要的實現類:

    FileChannel:用于讀取、寫入、映射和操作文件的通道 DatagramChannel:通過UDP讀寫網絡中的數據通道 SocketChannel:通過TCP讀寫網絡中的數據,一般是客戶端的實現 ServerSocketChannel:監聽新進來的TCP連接,對每一個連接創建一個SocketChannel,一般是服務端的實現

    基于SocketChannel和ServerSocketChannel實現C/S大致流程:
    服務端
    1.通過ServerSocketChannel 綁定ip地址和端口號
    2.通過ServerSocketChannelImpl的accept()方法創建一個SocketChannel對象用戶從客戶端讀/寫數據
    3.創建讀數據/寫數據緩沖區對象來讀取客戶端數據或向客戶端發送數據
    4. 關閉SocketChannel和ServerSocketChannel
    Scatter / Gather( 散射/采集 )

    Scatter/Gather應該使用直接的ByteBuffers以從本地I/O獲取最大性能優勢。 Scatter/Gather功能是通道(Channel)提供的 并不是Buffer。 Scatter:從一個Channel讀取的信息分散到N個緩沖區中(Buufer). Gather:將N個Buffer里面內容按照順序發送到一個Channel

    Buffer緩沖區

    Java NIO 的 Buffer 用于和 NIO Channel(通道)交互。數據是從通道讀入緩沖區,從緩沖區寫入到通道中。緩沖區本質上是塊可以寫入數據,再從中讀數據的內存。該內存被包裝成 NIO 的 Buffer 對象,并提供了一系列方法,方便開發者訪問該塊內存

    基本用法

    使用Buffer讀寫數據一般四步走:

    1、寫數據到 Buffer 2、調用buffer.flip切換為讀模式 3、從Buffer中讀取數據 4、調用clear()或者compact()清除數據

    當向 buffer 寫數據時,buffer 會記錄寫了多少數據。一旦要讀取數據,需通過 flip() 將 Buffer 從寫模式切到讀模式。在讀模式下,可讀之前寫到 buffer 的所有數據。一旦讀完數據,就需要清空緩沖區,讓它可以再次被寫入。有兩種方式能清空緩沖區:調用 clear() 或 compact() 方法。
    clear() 會清空整個緩沖區
    compact() 只會清除已經讀過的數據。任何未讀數據都被移到緩沖區的起始處,新寫入的數據將放到緩沖區未讀數據的后面。

    Buffer實現原理

    Buffer就像一個數組,可以保存多個相同類型的數據。根據
    數據類型不同 ,有以下 Buffer 常用子類:

    ByteBuffer
    CharBuffer
    ShortBuffer
    IntBuffer
    LongBuffer
    FloatBuffer
    DoubleBuffer
    Buffer的實現底層是通過特定類型(byte、long…)數組來存儲數據
    數組中數據的操作需要借助4個指針來操作:

    private int mark = -1; //標記 private int position = 0; //位置 private int limit; //限制 private int capacity; 容量 // Invariants: mark <= position <= limit <= capacity

    容量 (capacity) :作為一個內存塊,Buffer具有一定的固定大小,
    也稱為"容量",緩沖區容量不能為負,并且創建后不能更改。
    限制 (limit):表示緩沖區中可以操作數據的大小
    (limit 后數據不能進行讀寫)。緩沖區的限制不能
    為負,并且不能大于其容量。 寫入模式,限制等于
    buffer的容量。讀取模式下,limit等于寫入的數據量。

    位置 (position):下一個要讀取或寫入的數據的索引。
    緩沖區的位置不能為 負,并且不能大于其限制

    標記 (mark)與重置 (reset):標記是一個索引,
    通過 Buffer 中的 mark() 方法 指定 Buffer 中一個
    特定的 position,之后可以通過調用 reset() 方法恢
    復到這 個 position.
    標記、位置、限制、容量遵守以下不變式:
    0 <= mark <= position <= limit <= capacity

    Buffer常見方法

    Buffer clear() 清空緩沖區并返回對緩沖區的引用 Buffer flip() 為 將緩沖區的界限設置為當前位置,并將當前位置充值為 0 int capacity() 返回 Buffer 的 capacity 大小 boolean hasRemaining() 判斷緩沖區中是否還有元素 int limit() 返回 Buffer 的界限(limit) 的位置 Buffer limit(int n) 將設置緩沖區界限為 n,并返回一個具有新 limit 的緩沖區對象 Buffer mark() 對緩沖區設置標記 int position() 返回緩沖區的當前位置 position Buffer position(int n) 將設置緩沖區的當前位置為 n,并返回修改后的 Buffer 對象 int remaining() 返回 position 和 limit 之間的元素個數 Buffer reset() 將位置 position 轉到以前設置的mark 所在的位置 Buffer rewind() 將位置設為為 0, 取消設置的 mark

    Buffer的分配

    要想獲得一個Buffer對象首先要進行分配。每個Buffer類都有一個allocate方法。
    直接與非直接緩沖區
    ByteBufferbyte byffer可以是兩種類型,一種是基于直接內存(也就是
    非堆內存);另一種是非直接內存(也就是堆內存)。對于直
    接內存來說,JVM將會在IO操作上具有更高的性能,因為它
    直接作用于本地系統的IO操作。而非直接內存,也就是堆內
    存中的數據,如果要作IO操作,會先從本進程內存復制到直接
    內存,再利用本地IO處理。

    從數據流的角度,非直接內存是下面這樣的作用鏈:
    本地IO–>直接內存–>非直接內存–>直接內存–>本地IO

    而直接內存是:
    本地IO–>直接內存–>本地IO

    很明顯,在做IO處理時,比如網絡發送大量數據時,直接內
    存會具有更高的效率。直接內存使用allocateDirect創建,但
    是它比申請普通的堆內存需要耗費更高的性能。不過,這
    部分的數據是在JVM之外的,因此它不會占用應用的內
    存。所以呢,當你有很大的數據要緩存,并且它的生命
    周期又很長,那么就比較適合使用直接內存。只是一般
    來說,如果不是能帶來很明顯的性能提升,還是推薦直接
    使用堆內存。字節緩沖區是直接緩沖區還是非直接緩沖
    區可通過調用其 isDirect() 方法來確定。
    Buffer的創建:
    ByteBuffer為例:
    ByteBuffer allocate(int capacity):在堆上創建指定大小的緩沖
    ByteBuffer allocateDirect(int capacity):在堆外空間創建指定大小的緩沖
    ByteBuffer wrap(byte[] array):通過byte數組實例創建一個緩沖區
    ByteBuffer wrap(byte[] array, int offset, int length) 指定byte數據中的內容寫入到一個新的緩沖區
    向Buffer寫數據
    寫數據到Buffer有兩種方式:
    1、從Channel寫到Buffer

    inChannel.read(buf);

    2、通過Buffer的put()方法寫到Buffer里

    buf.put(127);

    flip()方法:
    flip方法將Buffer從寫模式切換到讀模式。調用flip()方法會將position設回0,并將limit設置成之前position的值。換句話說,position現在用于標記讀的位置,limit表示之前寫進了多少個byte、char等 —— 現在能讀取多少個byte、char等。
    從Buffer讀數據
    兩種方式:
    1、從Buffer讀取數據到Channel。

    int bytesWritten = inChannel.write(buf);

    2、使用get()方法從Buffer中讀取數據。

    byte aByte = buf.get();

    get方法有很多版本,允許你以不同的方式從Buffer中讀取數據。例如,從指定position讀取,或者從Buffer中讀取數據到字節數組。
    mark()與reset()方法
    通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。之后可以通過調用Buffer.reset()方法恢復到這個position。例如:

    buffer.mark();// call buffer.get() a couple of times, e.g. during parsing.buffer.reset();

    selector:選擇器

    Selector概述

    選擇器(Selector) 是 SelectableChannle 對象的多路復用器,Selector 可以同時監控多個 SelectableChannel 的 IO 狀況,也就是說,利用 Selector可使一個單獨的線程管理多個 Channel。Selector 是非阻塞 IO 的核心

    Java 的 NIO,用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到 Selector(選擇器)Selector 能夠檢測多個注冊的通道上是否有事件發生(注意:多個 Channel 以事件的方式可以注冊到同一個Selector),如果有事件發生,便獲取事件然后針對每個事件進行相應的處理。這樣就可以只用一個單線程去管理多個通道,也就是管理多個連接和請求。只有在 連接/通道 真正有讀寫事件發生時,才會進行讀寫,就大大地減少了系統開銷,且不必為每個連接都創建一個線程,不用去維護多個線程避免了多線程之間的上下文切換導致的開銷
    selector優勢:

    使用更少的線程管理更多的通道了,相比多線程,減少了上下文切換

    selector的使用

    創建 Selector :
    通過調用 Selector.open() 方法創建一個 Selector。
    向選擇器注冊通道:
    SelectableChannel.register(Selector sel, int ops)

    舉例:

    //1. 獲取通道 ServerSocketChannel ssChannel = ServerSocketChannel.open(); //2. 切換非阻塞模式 ssChannel.configureBlocking(false); //3. 綁定連接 ssChannel.bind(new InetSocketAddress(9898)); //4. 獲取選擇器 Selector selector = Selector.open(); //5. 將通道注冊到選擇器上, 并且指定“監聽接收事件” ssChannel.register(selector, SelectionKey.OP_ACCEPT);

    當調用 register(Selector sel, int ops) 將通道注冊選擇
    器時,選擇器對通道的監聽事件,需要通過第二個參
    數 ops 指定。可以監聽的事件類型(用 可使
    用 SelectionKey 的四個常量 表示):

    讀 : SelectionKey.OP_READ (1)
    寫 : SelectionKey.OP_WRITE (4)
    連接 : SelectionKey.OP_CONNECT (8)
    接收 : SelectionKey.OP_ACCEPT (16)
    若注冊時不止監聽一個事件,則可以使用“位或”操作符連接。
    如:

    int interestSet = SelectionKey.OP_READ|SelectionKey.OP_WRITE

    NIO非阻塞式網絡通信原理分析

    Selector可以實現: 一個 I/O 線程可以并發處理 N 個客戶端連接和讀寫操作,這從根本上解決了傳統同步阻塞 I/O 一連接一線程模型,架構的性能、彈性伸縮能力和可靠性都得到了極大的提升。

    NIO實現

    編寫一個 NIO 群聊系統,實現客戶端與客戶端的通信需求(非阻塞)
    服務器端:可以監測用戶上線,離線,并實現消息轉發功能
    客戶端:通過 channel 可以無阻塞發送消息給其它所有客戶端用戶,同時可以接受其它客戶端用戶通過服務端轉發來的消息

    服務端實現

    //服務端群聊系統實現 public class Server {//定義一些成員屬性:選擇器 服務器通道 端口private Selector selector;private ServerSocketChannel serverSocketChannel;private static final int PORT = 9999;//定義初始化代碼邏輯public Server(){//接受選擇器try {//初始化選擇器selector = Selector.open();//初始化通道serverSocketChannel=ServerSocketChannel.open();//綁定端口serverSocketChannel.bind(new InetSocketAddress(PORT));//通道切換為非阻塞模式serverSocketChannel.configureBlocking(false);//將通道注冊到選擇器上,并且開始指定監聽接收事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}}/*** 監聽客戶端各種消息事件:連接、群聊、離線* */private void listen(){try {//循環判斷是否存在就緒事件while (selector.select()>0){//獲取選擇器中的所有注冊的通道中已經就緒好的事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();//開始遍歷這些準備好的事件while (iterator.hasNext()){//提取當前事件SelectionKey sk = iterator.next();//判斷是否為可接收事件if (sk.isAcceptable()){//獲取當前接入的客戶端通道SocketChannel socketChannel = serverSocketChannel.accept();//通道切換為非阻塞模式socketChannel.configureBlocking(false);//將本客戶端的通道注冊到選擇器上System.out.println(socketChannel.getRemoteAddress() + " 上線 ");socketChannel.register(selector,SelectionKey.OP_READ);}//判斷是否為可讀事件if(sk.isReadable()){//讀操作和轉發給其他客戶端readClientData(sk);}iterator.remove();//處理完畢移除當前事件}}}catch (Exception e){e.printStackTrace();}}/*** 接收當前客戶端發送的消息,并轉發給全部客戶端通道* */private void readClientData(SelectionKey sk){SocketChannel socketChannel = null;try {//取到該讀操作的通道socketChannel = (SocketChannel) sk.channel();//創建緩沖區對象開始接收客戶端發送的消息ByteBuffer buffer = ByteBuffer.allocate(1024);int count = socketChannel.read(buffer);if(count>0){//設置為讀模式buffer.flip();//提取讀取到的信息String msg = new String(buffer.array(),0,count);System.out.println("接收到了客戶端信息:"+msg);//返回給其他在線客戶端消息sendMsgToAllClient(msg,socketChannel);}}catch (Exception e){//該客戶斷開連接會拋出異常,異常發出下線通知try {System.out.println(socketChannel.getRemoteAddress()+"下線了");sk.channel();//關閉通道socketChannel.close();} catch (IOException ioException) {ioException.printStackTrace();}}}//發送消息給所有在線人private void sendMsgToAllClient(String msg,SocketChannel socketChannel) throws Exception{System.out.println("服務端開始轉發消息,當前處理的線程" + Thread.currentThread().getName());//循環給所有在線通道發送消息for (SelectionKey key:selector.keys()){Channel channel = key.channel();//不要把數據發送服務器和自己if(channel instanceof SocketChannel && socketChannel!=channel){//將消息存儲到buffer緩存ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());//將緩存寫入到通道( (SocketChannel)channel).write(buffer);}}}public static void main(String[] args) {//創建服務端對象Server server = new Server();//開始監聽客戶端各種消息事件:連接、群聊、離線server.listen();} }

    客戶端

    //客戶端群聊系統實現 public class Client {private Selector selector;private static final int PDRT = 9999;private SocketChannel socketChannel;public Client(){try {//初始化選擇器selector = Selector.open();//初始化通道,并綁定通信地址與端口socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",PDRT));//通道設置為非阻塞模式socketChannel.configureBlocking(false);//將通道加載到選擇器上,并開始指定監聽讀事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("當前客戶端準備完成");}catch (Exception e){e.printStackTrace();}}public static void main(String[] args) {Client client = new Client();//定義一個線程負責監聽服務端發來的線程消息new Thread(new Runnable() {@Overridepublic void run() {try {while (true){//接收讀事件client.readInfo();}} catch (IOException e) {e.printStackTrace();}}}).start();//主線程進行發送消息Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()){String s= scanner.nextLine();//數據發送client.sendTOServer(s);}}private void sendTOServer(String s) {try {//數據經過緩存加載到通道上socketChannel.write(ByteBuffer.wrap(s.getBytes()));} catch (IOException e) {e.printStackTrace();}}/*** ** */private void readInfo() throws IOException {//判斷選擇器是是否有就緒事件if(selector.select()>0){//循環處理這些準備就緒的事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()){//取得當前就緒事件SelectionKey key = iterator.next();//判斷當前是否為讀事件if(key.isReadable()){//取得當前通道SocketChannel selectableChannel = (SocketChannel) key.channel();//創建buffer緩存接收事件ByteBuffer buffer = ByteBuffer.allocate(1024);//讀取通道上的數據socketChannel.read(buffer);//輸出緩存中數據System.out.println(new String(buffer.array()).trim());}//處理完關閉該通道iterator.remove();}}} }




    BIO與AIO區別

    BIO與NIO一個比較重要的不同,是我們使用BIO的時候往往會引入多線程,每個連接一個單獨的線程;
    而NIO則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。NIO的最重要的地方是當一個連接創建后,不需要對應一個線程,這個連接會被注冊到多路復用器上面,所以所有的連接只需要一個線程就可以搞定,當這個線程中的多路復用器進行輪詢的時候,發現連接上有請求的話,才開啟一個線程進行處理,也就是一個請求一個線程模式。
    NIO比BIO最大的好處是,一個線程可以處理多個socket(channel),這樣NIO+多線程會提高網絡服務器的性能,最主要是大大降低線程的數量
    服務器線程數量過多對系統有什么影響?
    1.java里面創建進程和線程,最終映射到本地操作系統上創建進程和線程,拿Linux來說,fork(進程創建函數)和pthread_create(線程創建函數)都是重量級的函數,調用它們開銷很大
    2.多線程隨著CPU的調度,會有上下文切換,如果線程過多,線程上下文切換的時間花費慢慢趨近或者大于線程本身執行指令的時間,那么CPU就完全被浪費掉了,大大降低了系統的性能
    3.線程的開辟伴隨著線程私有內存的分配,如果線程數量過多,為線程運行準備的內存占去很多,真正能用來分配做業務處理的內存大大減少,系統運行不可靠
    4.數量過多的線程,阻塞等待網絡事件發生,如果一瞬間客戶請求量比較大,系統會瞬間喚醒很多數量的線程,造成系統瞬間的內存使用率和CPU使用率居高不下,服務器系統不應該總是出現鋸齒狀的系統負載,內存使用率和CPU使用率應該持續的保證平順運行

    總結

    以上是生活随笔為你收集整理的BIO与NIO比较的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。