1. 程式人生 > 實用技巧 >java --- IO/NIO介紹,網路程式設計模型

java --- IO/NIO介紹,網路程式設計模型

1、阻塞I/O模型

  阻塞I/O模型是常見的I/O模型,在讀寫資料時客戶端會發生阻塞。阻塞I/O模式的工作流程為:當用戶發出I/O請求之後,核心會檢查資料是否就緒,此時使用者執行緒會一直阻塞等待記憶體資料就緒,在記憶體資料就緒後,核心將資料複製到使用者執行緒中,並返回I/O執行結果到使用者執行緒,此時使用者執行緒將解除阻塞狀態並開始處理資料。

      典型的阻塞I/O模型的如下:

      data=socket.read()

2、非阻塞I/O模型

  非阻塞I/O模型是指使用者在發起一個I/O操作後,無需阻塞便馬上可以得到核心返回的結果。如果核心返回的結果為false,則表示核心資料還沒準備好,需要稍後再發起I/O操作。一旦核心中資料準備好了,並且再次收到使用者的請求,核心就會立刻將資料複製到使用者執行緒中並將結果通知使用者執行緒。

      典型的非阻塞I/O模型如下:

1     while (true){
2             data = socket.read();
3             if(data == true){
4                 //獲取並處理核心資料,通知使用者執行緒
5                 break;
6             }else {
7                 //資料沒有準備好,此時處理使用者執行緒其他實際
8             }
9         }

3、多路複用I/O模式

  多路複用模型是多執行緒併發程式設計用得比較多的模型,java NIO就是基於多路複用I/O模型實現的。在多路複用I/O模型中會有一個被稱為selector的執行緒不斷輪詢多個socket的狀態,只有在socket有讀寫事件時,才會通知使用者執行緒進行I/O讀寫操作。

  因為在多路複用I/O模型中只需要一個selector執行緒就可以管理多個socket,並且只有在真正有socket讀寫操作的時候才會使用作業系統的I/O資源,大大節約了系統資源。

  java NIO在使用者的每個執行緒中都通過selector.select()查詢當前通道是否有事件到達,如果沒有,使用者執行緒會一直阻塞。而多路複用I/O模型通過一個執行緒管理多個socket通道,在socket有讀寫事件觸發的時候才會通知使用者執行緒進行I/O讀寫操作。

  非阻塞I/O模型在每個使用者執行緒中都進行socket狀態檢查,而多路複用I/O模型中是在系統核心中進行socket狀態檢查,這也是多路複用I/O模型比非阻塞I/O模型效率高的原因。

  多路複用I/O模型通過在一個selector執行緒上以輪詢的方式檢測在多個socket上是否有事件到達,並啄個進行事件處理和響應。因為對於多路複用I/O模型來說,在事件響應體很大時,selector執行緒就會成為效能瓶頸,導致後續的事件遲遲得不到處理,影響下一輪的事件輪詢。在事件應用中,在多路複用方法體內一般不建議做複雜邏輯運算,只做資料的接收和轉發。

4、訊號驅動I/O模型

  在訊號驅動I/O模型中,在使用者發起一個I/O請求操作時,系統會為該請求對應的socket註冊一個訊號函式,然後使用者執行緒可以繼續執行其他業務邏輯,在核心資料就緒時,系統會發送一個訊號到使用者執行緒,使用者執行緒收到訊號時,會在訊號函式中呼叫對應的I/O讀寫操作完成實際的I/O請求操作。

5、非同步I/O模型

  在非同步I/O模型中,使用者執行緒會發起一個asynchronous read操作到核心,核心在接收到請求後會立馬返回一個狀態,來說明請求是否成功發起,在此過程中使用者執行緒不會發生任何阻塞。接著,核心會等待資料準備完成並將資料複製到使用者執行緒中,在複製完成後會發個訊號給使用者執行緒,通知使用者執行緒asynchronous讀操作已完成。在非同步I/O模型中,使用者執行緒不需要關心整個I/O操作是如何進行的,只需要發一個請求,在接收到核心返回的成功或者失敗時,說明I/O請求已經完成,直接使用資料即可。

6、java I/O

  具體使用參考JDK api

7、Java NIO

  Java NIO 的主要實現涉及三大核心內容:Selector、Channel、Buffer。Selector用於監聽多個Channeld的事件,比如連結開啟或資料到達,因此,一個執行緒可以實現對多個Channel的管理。傳統的I/O是基於流的讀寫操作,而java NIO基於Channel和Buffer進行I/O讀寫操作,並且資料總是被從Channel讀取到Buffer中,或者從Buffer讀取到Channel中。

  java NIO跟傳統I/O最大的區別如下:

    1、I/O是面向流的,NIO是面向緩衝區的,在面向流的操作中,資料只能在一個流中連續進行讀寫,資料沒有緩衝,因此位元組流無法前後移動。而在NIO中每次都是將資料從一個Channel讀取到一個Buffer中,再從Buffer讀取到Channel中,因此可以方便的在緩衝區進行資料的前後移動等操作。該功能在應用層主要用於資料的粘包,拆包等操作,在網路不可靠的環境下尤為重要。

    2、傳統I/O的流操作是阻塞的,NIO流的操作是非阻塞的。在傳統的I/O下,使用者執行緒在呼叫read()或write()時,使用者執行緒將一直被阻塞,直到資料被完全讀取或寫入。NIO通過selector監聽Channel上的事件變化,在Channel上有資料變化時,通知該執行緒進行資料的讀寫操作。對於讀取操作而言,在Channel上有資料時,執行緒將進行讀Buffer操作,在沒有資料時,執行緒可以執行其他業務邏輯。對於寫操作而言,在使用執行緒執行寫操作將一些資料寫入某channel時,只需將channel上的資料非同步寫入Buffer即可,Buffer上的資料會被非同步寫入目標Channel上,使用者不需要等待整個資料被完全寫入目標Channel就可以繼續執行其他業務邏輯。

    

  1、Channel介紹:

    Channel和I/O中的Stream(流)類似,只不過Stream是單向的,而Channel是雙向的,既可以操作讀操作,也可以操作寫操作。

  2、Buffer

    Buffer實際上是一個容器,其內部通過一個連續的位元組陣列儲存I/O上的資料,在NIO中,Channel檔案、網路上對資料的讀取或寫入都必須經過Buffer。

 

  3、Selector

    Selector用於檢測在多個註冊的Channel上是否有I/O事件發生,並檢測到的I/O事件進行相應的響應和處理。因此通過一個Selector執行緒就可以實現對多個Channel的管理,不必為每個連結都創立一個執行緒,避免執行緒資源的浪費和多執行緒上下文切換導致的開銷。同時,Selector只有在Channel上有讀寫事件發生時,才會呼叫I/O函式進行讀寫操作,可極大的減少開支,提高系統併發量。

  4、java NIO的使用

  MyServer類:

    

package cn.com.yitong.ares.A20200818;

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;

public class MyServer {
    private int size = 1024;
    private ServerSocketChannel serverSocketChannel;
    private ByteBuffer byteBuffer;
    private Selector selector;
    private int remoteClientNum = 0;
    public MyServer(int port){
        try {
            initChannel(port);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
    //channel初始化
    private void initChannel(int port) throws Exception{
        //開啟channel
        serverSocketChannel = ServerSocketChannel.open();
        //設定為非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //繫結埠
        serverSocketChannel.bind(new InetSocketAddress(port));
        System.out.println("port is :" + port);
        //選擇器的建立
        selector = Selector.open();
        //向選擇器注入通道
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //分配緩衝區的大小
        byteBuffer = ByteBuffer.allocate(size);
    }
    //監聽器,用於監聽channel上的資料變化
    private void listrner() throws Exception{
        while(true){
            //返回的int值表示有多個channel處於就緒狀態
            int n = selector.select();
            if(n == 0){
                continue;
            }
            //每個selector 對應多個selectionKey,每個selectionKey對應一個channel
            Iterator<SelectionKey> iterable = selector.selectedKeys().iterator();
            while (iterable.hasNext()){
                SelectionKey key = iterable.next();
                //如果selectionKey處於就緒狀態,則開始接收客戶端的連結
                if(key.isAcceptable()){
                    //獲取channel
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    //channel接收連結
                    SocketChannel channel = server.accept();
                    //channel註冊
                    registerChannel(selector,channel,SelectionKey.OP_READ);
                    //遠端客戶端的連結數
                    remoteClientNum++;
                    System.out.println("online client num = " + remoteClientNum);
                    write(channel,"hello world".getBytes());
                }
                //如果通道已經處於就緒狀態
                if(key.isReadable()){
                    read(key);
                }
                iterable.remove();
            }
        }

    }

    private void read(SelectionKey key) throws Exception{
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        byteBuffer.clear();
        //從通道中讀取資料到緩衝區
        while ((count = socketChannel.read(byteBuffer)) >0){
            //byteBuffer寫模式變為讀模式
            byteBuffer.flip();
            StringBuffer stringBuffer = new StringBuffer();
            boolean firstLine = true;
            while(byteBuffer.hasRemaining()){
                if(firstLine){
                    firstLine = false;
                }else {
//                    stringBuffer.append("\n");
                }
                stringBuffer.append((char)byteBuffer.get());
                System.out.println("+++++++" + stringBuffer.toString());
            };
            System.out.println("+++++++" + stringBuffer.toString());
            byteBuffer.clear();
        }
        if(count < 0 ){
            socketChannel.close();
        }
    }

    private void write(SocketChannel channel, byte[] bytes) throws Exception{
        byteBuffer.clear();
        byteBuffer.put(bytes);
        byteBuffer.flip();
        //將緩衝區的資料寫入通道中
        channel.write(byteBuffer);
    }

    private void registerChannel(Selector selector, SocketChannel channel, int opRead) throws Exception{

        if(channel == null){
            return;
        }
        channel.configureBlocking(false);
        channel.register(selector,opRead);
    }
    public static void main(String[] args){
        MyServer server = new MyServer(9000);
        try {
            server.listrner();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  Myclient類:

 

package cn.com.yitong.ares.A20200818;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Myclient {
    private int size = 1024;
    private ByteBuffer byteBuffer;
    private SocketChannel socketChannel;

    public void connectServer() throws IOException {
        socketChannel = socketChannel.open();
        socketChannel.connect(new InetSocketAddress("10.2.189.172",9000));
        socketChannel.configureBlocking(false);
        byteBuffer = ByteBuffer.allocate(size);
        receive();
    }

    private void receive() throws IOException {
        while (true){
            byteBuffer.clear();
            int count;
            //如果沒有資料,則read方法一直阻塞,直到讀取到新的資料
            while ((count = socketChannel.read(byteBuffer)) > 0){
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()){
                    System.out.println((char)byteBuffer.get());
                }
                send2Server("<head></head>".getBytes());
                byteBuffer.clear();
            }
        }
    }

    private void send2Server(byte[] bytes) throws IOException {
        byteBuffer.clear();
        byteBuffer.put(bytes);
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
    }
    public static void main(String[] args){
        try {
            new Myclient().connectServer();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  此文字非原創!!