欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?IO網(wǎng)絡(luò)模型實(shí)現(xiàn)解析

 更新時(shí)間:2023年03月16日 11:46:21   作者:半夏之沫  
這篇文章主要為大家介紹了Java?IO網(wǎng)絡(luò)模型實(shí)現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

本篇文章會(huì)對(duì)Java中的網(wǎng)絡(luò)IO模型的概念進(jìn)行解釋?zhuān)⒔o出具體的Java代碼實(shí)現(xiàn),主要涉及如下部分。

  • BIO(同步阻塞IO模型)的概念和Java編程實(shí)現(xiàn);
  • Non Blocking IO(同步非阻塞IO模型)的概念和Java編程實(shí)現(xiàn);
  • IO多路復(fù)用的概念;
  • NIONew IO)的概念和Java編程實(shí)現(xiàn)。

在開(kāi)始本篇文章內(nèi)容之前,有一個(gè)簡(jiǎn)單的關(guān)于Socket的知識(shí)需要說(shuō)明:在進(jìn)行網(wǎng)絡(luò)通信的時(shí)候,需要一對(duì)Socket,一個(gè)運(yùn)行于客戶(hù)端,一個(gè)運(yùn)行于服務(wù)端,同時(shí)服務(wù)端還會(huì)有一個(gè)服務(wù)端Socket,用于監(jiān)聽(tīng)客戶(hù)端的連接。下圖進(jìn)行一個(gè)簡(jiǎn)單示意。

那么整個(gè)通信流程如下所示。

  • 服務(wù)端運(yùn)行后,會(huì)在服務(wù)端創(chuàng)建listen-socket,listen-socket會(huì)綁定服務(wù)端的ipport,然后服務(wù)端進(jìn)入監(jiān)聽(tīng)狀態(tài);
  • 客戶(hù)端請(qǐng)求服務(wù)端時(shí),客戶(hù)端創(chuàng)建connect-socket,connect-socket描述了其要連接的服務(wù)端的listen-socket,然后connect-socketlisten-socket發(fā)起連接請(qǐng)求;
  • connect-socketlisten-socket成功連接后(TCP三次握手成功),服務(wù)端會(huì)為已連接的客戶(hù)端創(chuàng)建一個(gè)代表該客戶(hù)端的client-socket,用于后續(xù)和客戶(hù)端進(jìn)行通信;
  • 客戶(hù)端與服務(wù)端通過(guò)socket進(jìn)行網(wǎng)絡(luò)IO操作,此時(shí)就實(shí)現(xiàn)了客戶(hù)端和服務(wù)端中的不同進(jìn)程的通信。

需要知道的就是,在客戶(hù)端與服務(wù)端通信的過(guò)程中,出現(xiàn)了三種socket,分別是。

  • listen-socket。是服務(wù)端用于監(jiān)聽(tīng)客戶(hù)端建立連接的socket;
  • connect-socket。是客戶(hù)端用于連接服務(wù)端的socket;
  • client-socket。是服務(wù)端監(jiān)聽(tīng)到客戶(hù)端連接請(qǐng)求后,在服務(wù)端生成的與客戶(hù)端連接的socket。

(注:上述中的socket,可以被稱(chēng)為套接字,也可以被稱(chēng)為文件描述符。)

正文

一. BIO

BIO,即同步阻塞IO模型。用戶(hù)進(jìn)程調(diào)用read時(shí)發(fā)起IO操作,此時(shí)用戶(hù)進(jìn)程由用戶(hù)態(tài)轉(zhuǎn)換到內(nèi)核態(tài),只有在內(nèi)核態(tài)中將IO操作執(zhí)行完后,才會(huì)從內(nèi)核態(tài)切換回用戶(hù)態(tài),這期間用戶(hù)進(jìn)程會(huì)一直阻塞。

BIO示意圖如下。

簡(jiǎn)單的BIOJava編程實(shí)現(xiàn)如下。

服務(wù)端實(shí)現(xiàn)

public class BioServer {

    public static void main(String[] args) throws IOException {
        // 創(chuàng)建listen-socket
        ServerSocket listenSocket = new ServerSocket(8080);
        // 進(jìn)入監(jiān)聽(tīng)狀態(tài),是一個(gè)阻塞狀態(tài)
        // 有客戶(hù)端連接時(shí)從監(jiān)聽(tīng)狀態(tài)返回
        // 并創(chuàng)建代表這個(gè)客戶(hù)端的client-socket
        Socket clientSocket = listenSocket.accept();
        // 獲取client-socket輸入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
        // 讀取客戶(hù)端發(fā)送的數(shù)據(jù)
        // 如果數(shù)據(jù)沒(méi)準(zhǔn)備好,會(huì)進(jìn)入阻塞狀態(tài)
        String data = bufferedReader.readLine();
        System.out.println(data);

        // 獲取client-socket輸出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(clientSocket.getOutputStream()));
        // 服務(wù)端向客戶(hù)端發(fā)送數(shù)據(jù)
        bufferedWriter.write("來(lái)自服務(wù)端的返回?cái)?shù)據(jù)\n");
        // 刷新流
        bufferedWriter.flush();
    }

}

客戶(hù)端實(shí)現(xiàn)

public class BioClient {

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) throws IOException {
        // 客戶(hù)端創(chuàng)建connect-socket
        Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
        // 獲取connect-socket輸出流
        BufferedWriter bufferedWriter = new BufferedWriter(
                new OutputStreamWriter(connectSocket.getOutputStream()));
        // 客戶(hù)端向服務(wù)端發(fā)送數(shù)據(jù)
        bufferedWriter.write("來(lái)自客戶(hù)端的請(qǐng)求數(shù)據(jù)\n");
        // 刷新流
        bufferedWriter.flush();

        // 獲取connect-socket輸入流
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(connectSocket.getInputStream()));
        // 讀取服務(wù)端發(fā)送的數(shù)據(jù)
        String returnData = bufferedReader.readLine();
        System.out.println(returnData);
    }

}

BIO的問(wèn)題就在于服務(wù)端在accept時(shí)是阻塞的,并且在主線(xiàn)程中,一次只能accept一個(gè)Socket,acceptSocket后,讀取客戶(hù)端數(shù)據(jù)時(shí)又是阻塞的。

二. Non Blocking IO

Non Blocking IO,即同步非阻塞IO。是用戶(hù)進(jìn)程調(diào)用read時(shí),用戶(hù)進(jìn)程由用戶(hù)態(tài)轉(zhuǎn)換到內(nèi)核態(tài)后,此時(shí)如果沒(méi)有系統(tǒng)資源數(shù)據(jù)能夠被讀取到內(nèi)核緩沖區(qū)中,返回read失敗,并從內(nèi)核態(tài)切換回用戶(hù)態(tài)。也就是用戶(hù)進(jìn)程發(fā)起IO操作后會(huì)立即得到一個(gè)操作結(jié)果。

Non Blocking IO示意圖如下所示。

簡(jiǎn)單的Non Blocking IOJava編程實(shí)現(xiàn)如下。

public class NonbioServer {

    public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        // 客戶(hù)端創(chuàng)建listen-socket管道
        // 管道支持非阻塞模式和同時(shí)讀寫(xiě)
        ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
        // 設(shè)置為非阻塞模式
        listenSocketChannel.configureBlocking(false);
        // 綁定監(jiān)聽(tīng)的端口號(hào)
        listenSocketChannel.socket().bind(new InetSocketAddress(8080));
        // 在子線(xiàn)程中遍歷clientSocketChannels并讀取客戶(hù)端數(shù)據(jù)
        handleSocketChannels();

        while (true) {
            // 非阻塞方式監(jiān)聽(tīng)客戶(hù)端連接
            // 如果無(wú)客戶(hù)端連接則返回空
            // 有客戶(hù)端連接則創(chuàng)建代表這個(gè)客戶(hù)端的client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            if (clientSocketChannel != null) {
                // 設(shè)置為非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 添加到clientSocketChannels中
                // 用于子線(xiàn)程遍歷并讀取客戶(hù)端數(shù)據(jù)
                clientSocketChannels.add(clientSocketChannel);
            } else {
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }
    }

    public static void handleSocketChannels() {
        new Thread(() -> {
            while (true) {
                // 遍歷每一個(gè)client-socket管道
                Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
                while (iterator.hasNext()) {
                    SocketChannel clientSocketChannel = iterator.next();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = 0;
                    try {
                        // 將客戶(hù)端發(fā)送的數(shù)據(jù)讀取到ByteBuffer中
                        // 這一步的操作也是非阻塞的
                        read = clientSocketChannel.read(byteBuffer);
                    } catch (IOException e) {
                        // 移除發(fā)生異常的client-socket管道
                        iterator.remove();
                        e.printStackTrace();
                    }
                    if (read == 0) {
                        System.out.println("客戶(hù)端數(shù)據(jù)未就緒");
                    } else {
                        System.out.println("客戶(hù)端數(shù)據(jù)為:" + new String(byteBuffer.array()));
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        }).start();
    }

}

上述是Non Blocking IO的一個(gè)簡(jiǎn)單服務(wù)端的實(shí)現(xiàn),相較于BIO,服務(wù)端在accept時(shí)是非阻塞的,在讀取客戶(hù)端數(shù)據(jù)時(shí)也是非阻塞的,但是還是存在如下問(wèn)題。

  • 一次只能accept一個(gè)Socket;
  • 需要在用戶(hù)進(jìn)程中遍歷所有的SocketChannel并調(diào)用read() 方法獲取客戶(hù)端數(shù)據(jù),此時(shí)如果客戶(hù)端數(shù)據(jù)未準(zhǔn)備就緒,那么這一次的read() 操作的開(kāi)銷(xiāo)就是浪費(fèi)的。

三. IO多路復(fù)用

在上述的BIONon Blocking IO中,一次系統(tǒng)調(diào)用,只會(huì)獲取一個(gè)IO的狀態(tài),而如果采取IO多路復(fù)用機(jī)制,則可以一次系統(tǒng)調(diào)用獲取多個(gè)IO的狀態(tài)。

也就是獲取多個(gè)IO的狀態(tài)可以復(fù)用一次系統(tǒng)調(diào)用。

最簡(jiǎn)單的IO多路復(fù)用方式是基于select模型實(shí)現(xiàn),步驟如下。

  • 在用戶(hù)進(jìn)程中將需要監(jiān)控的IO文件描述符(Socket)注冊(cè)到IO多路復(fù)用器中;
  • 執(zhí)行select操作,此時(shí)用戶(hù)進(jìn)程由用戶(hù)態(tài)轉(zhuǎn)換到內(nèi)核態(tài)(一次系統(tǒng)調(diào)用),然后在內(nèi)核態(tài)中會(huì)輪詢(xún)注冊(cè)到IO多路復(fù)用器中的IO是否準(zhǔn)備就緒,并得到所有準(zhǔn)備就緒的IO的文件描述符列表,最后返回這些文件描述符列表;
  • 用戶(hù)進(jìn)程在select操作返回前會(huì)一直阻塞,直至select操作返回,此時(shí)用戶(hù)進(jìn)程就獲得了所有就緒的IO的文件描述符列表;
  • 用戶(hù)進(jìn)程獲得了就緒的IO的文件描述符列表后,就可以對(duì)這些IO進(jìn)行相應(yīng)的操作了。

換言之,IO多路復(fù)用中,只需要一次系統(tǒng)調(diào)用,IO多路復(fù)用器就可以告訴用戶(hù)進(jìn)程,哪些IO已經(jīng)準(zhǔn)備就緒可以進(jìn)行操作了,而如果不采用IO多路復(fù)用,則需要用戶(hù)進(jìn)程自己遍歷每個(gè)IO并調(diào)用accept() 或者read() 方法去判斷,且一次accept() 或者read() 方法調(diào)用只能判斷一個(gè)IO。

四. NIO

NIO,即New IO。關(guān)于NIO,有如下三大組件。

  • channel(管道)。介于buffer(字節(jié)緩沖區(qū))和Socket(套接字)之間,用于數(shù)據(jù)的讀寫(xiě)操作;
  • buffer(字節(jié)緩沖區(qū))。是用戶(hù)程序和channel(管道)之間進(jìn)行讀寫(xiě)數(shù)據(jù)的中間區(qū)域;
  • selectorIO多路復(fù)用器)。服務(wù)端的listen-socketclient-socket,客戶(hù)端的connect-socket,都可以注冊(cè)在selector上,注冊(cè)的時(shí)候還需要指定監(jiān)聽(tīng)的事件,比如為listen-socket指定監(jiān)聽(tīng)的事件為ACCEPT事件,該事件發(fā)生則表示客戶(hù)端建立了連接,還比如為client-socket指定監(jiān)聽(tīng)的事件為READ事件,該事件發(fā)生則表示客戶(hù)端發(fā)送的數(shù)據(jù)已經(jīng)可讀。

NIO的代碼實(shí)現(xiàn)如下所示。

服務(wù)端實(shí)現(xiàn)

public class NioServer {

    private static Selector selector;

    public static void main(String[] args) {

        try {
            // 開(kāi)啟并得到多路復(fù)用器
            selector = Selector.open();
            // 服務(wù)端創(chuàng)建listen-socket管道
            ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
            // 設(shè)置為非阻塞模式
            listenSocketChannel.configureBlocking(false);
            // 為管道綁定端口
            listenSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 將listen-socket管道注冊(cè)到多路復(fù)用器上,并指定監(jiān)聽(tīng)ACCEPT事件
            listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 獲取發(fā)生的事件,這個(gè)操作是阻塞的
                selector.select();
                // 拿到有事件發(fā)生的SelectionKey集合
                // SelectionKey表示管道與多路復(fù)用器的綁定關(guān)系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍歷每個(gè)發(fā)生的事件,然后判斷事件類(lèi)型
                // 根據(jù)事件類(lèi)型,進(jìn)行不同的處理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isAcceptable()) {
                        // 處理客戶(hù)端連接事件
                        handlerAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 處理客戶(hù)端數(shù)據(jù)可讀事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerAccept(SelectionKey selectionKey) {
        // 從事件中獲取到listen-socket管道
        ServerSocketChannel listenSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            // 為連接的客戶(hù)端創(chuàng)建client-socket管道
            SocketChannel clientSocketChannel = listenSocketChannel.accept();
            // 設(shè)置為非阻塞模式
            clientSocketChannel.configureBlocking(false);
            // 將client-socket管道注冊(cè)到多路復(fù)用器上,并指定監(jiān)聽(tīng)READ事件
            clientSocketChannel.register(selector, SelectionKey.OP_READ);
            // 給客戶(hù)端發(fā)送數(shù)據(jù)
            clientSocketChannel.write(ByteBuffer.wrap("連接已建立\n".getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 從事件中獲取到client-socket管道
        SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 讀取客戶(hù)端數(shù)據(jù)
            int read = clientSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 關(guān)閉管道
                clientSocketChannel.close();
                // 從多路復(fù)用器移除綁定關(guān)系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 關(guān)閉管道
                clientSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 從多路復(fù)用器移除綁定關(guān)系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

客戶(hù)端實(shí)現(xiàn)

public class NioClient {

    private static Selector selector;

    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 8080;

    public static void main(String[] args) {
        try {
            // 開(kāi)啟并得到多路復(fù)用器
            selector = Selector.open();
            // 創(chuàng)建connect-socket管道
            SocketChannel connectSocketChannel = SocketChannel.open();
            // 設(shè)置為非阻塞模式
            connectSocketChannel.configureBlocking(false);
            // 設(shè)置服務(wù)端IP和端口
            connectSocketChannel.connect(new InetSocketAddress(SERVER_IP, SERVER_PORT));
            // 將connect-socket管道注冊(cè)到多路復(fù)用器上,并指定監(jiān)聽(tīng)CONNECT事件
            connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                // 獲取發(fā)生的事件,這個(gè)操作是阻塞的
                selector.select();
                // 拿到有事件發(fā)生的SelectionKey集合
                // SelectionKey表示管道與多路復(fù)用器的綁定關(guān)系
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍歷每個(gè)發(fā)生的事件,然后判斷事件類(lèi)型
                // 根據(jù)事件類(lèi)型,進(jìn)行不同的處理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();
                    if (selectionKey.isConnectable()) {
                        // 處理連接建立事件
                        handlerConnect(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        // 處理服務(wù)端數(shù)據(jù)可讀事件
                        handlerRead(selectionKey);
                    }
                }
                LockSupport.parkNanos(1000 * 1000 * 1000);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handlerConnect(SelectionKey selectionKey) throws IOException {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        if (connectSocketChannel.isConnectionPending()) {
            connectSocketChannel.finishConnect();
        }
        // 設(shè)置為非阻塞模式
        connectSocketChannel.configureBlocking(false);
        // 將connect-socket管道注冊(cè)到多路復(fù)用器上,并指定監(jiān)聽(tīng)READ事件
        connectSocketChannel.register(selector, SelectionKey.OP_READ);
        // 向服務(wù)端發(fā)送數(shù)據(jù)
        connectSocketChannel.write(ByteBuffer.wrap("客戶(hù)端發(fā)送的數(shù)據(jù)\n".getBytes()));
    }

    private static void handlerRead(SelectionKey selectionKey) {
        // 拿到connect-socket管道
        SocketChannel connectSocketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            // 讀取服務(wù)端數(shù)據(jù)
            int read = connectSocketChannel.read(byteBuffer);
            if (read <= 0) {
                // 關(guān)閉管道
                connectSocketChannel.close();
                // 從多路復(fù)用器移除綁定關(guān)系
                selectionKey.cancel();
            } else {
                System.out.println(new String(byteBuffer.array()));
            }
        } catch (IOException e1) {
            try {
                // 關(guān)閉管道
                connectSocketChannel.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
            // 從多路復(fù)用器移除綁定關(guān)系
            selectionKey.cancel();
            e1.printStackTrace();
        }
    }

}

總結(jié)

本篇文章中所討論的IO模型,都是同步IO模型,而何謂同步異步,何謂阻塞非阻塞,可以總結(jié)如下。

  • 同步異步同步IO表示需要在用戶(hù)進(jìn)程中主動(dòng)的去詢(xún)問(wèn)操作系統(tǒng)數(shù)據(jù)是否準(zhǔn)備好,如果沒(méi)有準(zhǔn)備好,還需要持續(xù)的詢(xún)問(wèn),直到數(shù)據(jù)準(zhǔn)備好為止;而異步IO則是在用戶(hù)進(jìn)程中只需要詢(xún)問(wèn)操作系統(tǒng)一次,后續(xù)數(shù)據(jù)準(zhǔn)備好后操作系統(tǒng)會(huì)主動(dòng)的將數(shù)據(jù)給到用戶(hù)進(jìn)程;
  • 阻塞非阻塞阻塞IO就是發(fā)起一次系統(tǒng)調(diào)用后,會(huì)一直阻塞直到有結(jié)果返回;而非阻塞IO就是發(fā)起一次系統(tǒng)調(diào)用后,會(huì)立即得到一個(gè)返回結(jié)果。

實(shí)際上在Java中是有對(duì)異步IOAIO)做支持,但是AIO依賴(lài)操作系統(tǒng)的底層實(shí)現(xiàn),而目前Linux對(duì)AIO的支持不成熟,所以AIO的使用并不多,像主流的網(wǎng)絡(luò)應(yīng)用框架Netty也都沒(méi)有使用到AIO。

以上就是Java IO網(wǎng)絡(luò)模型實(shí)現(xiàn)解析的詳細(xì)內(nèi)容,更多關(guān)于Java IO網(wǎng)絡(luò)模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論