溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java?IO網(wǎng)絡(luò)模型如何實現(xiàn)

發(fā)布時間:2023-03-16 13:58:52 來源:億速云 閱讀:92 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Java IO網(wǎng)絡(luò)模型如何實現(xiàn)”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Java IO網(wǎng)絡(luò)模型如何實現(xiàn)”吧!

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

    Java?IO網(wǎng)絡(luò)模型如何實現(xiàn)

    那么整個通信流程如下所示。

    • 服務(wù)端運行后,會在服務(wù)端創(chuàng)建listen-socket,listen-socket會綁定服務(wù)端的ipport,然后服務(wù)端進入監(jiān)聽狀態(tài);

    • 客戶端請求服務(wù)端時,客戶端創(chuàng)建connect-socketconnect-socket描述了其要連接的服務(wù)端的listen-socket,然后connect-socketlisten-socket發(fā)起連接請求;

    • connect-socketlisten-socket成功連接后(TCP三次握手成功),服務(wù)端會為已連接的客戶端創(chuàng)建一個代表該客戶端的client-socket,用于后續(xù)和客戶端進行通信;

    • 客戶端與服務(wù)端通過socket進行網(wǎng)絡(luò)IO操作,此時就實現(xiàn)了客戶端和服務(wù)端中的不同進程的通信。

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

    • listen-socket。是服務(wù)端用于監(jiān)聽客戶端建立連接的socket

    • connect-socket。是客戶端用于連接服務(wù)端的socket;

    • client-socket。是服務(wù)端監(jiān)聽到客戶端連接請求后,在服務(wù)端生成的與客戶端連接的socket。

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

    正文

    一. BIO

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

    BIO示意圖如下。

    Java?IO網(wǎng)絡(luò)模型如何實現(xiàn)

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

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

    public class BioServer {
    
        public static void main(String[] args) throws IOException {
            // 創(chuàng)建listen-socket
            ServerSocket listenSocket = new ServerSocket(8080);
            // 進入監(jiān)聽狀態(tài),是一個阻塞狀態(tài)
            // 有客戶端連接時從監(jiān)聽狀態(tài)返回
            // 并創(chuàng)建代表這個客戶端的client-socket
            Socket clientSocket = listenSocket.accept();
            // 獲取client-socket輸入流
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(clientSocket.getInputStream()));
            // 讀取客戶端發(fā)送的數(shù)據(jù)
            // 如果數(shù)據(jù)沒準備好,會進入阻塞狀態(tài)
            String data = bufferedReader.readLine();
            System.out.println(data);
    
            // 獲取client-socket輸出流
            BufferedWriter bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(clientSocket.getOutputStream()));
            // 服務(wù)端向客戶端發(fā)送數(shù)據(jù)
            bufferedWriter.write("來自服務(wù)端的返回數(shù)據(jù)\n");
            // 刷新流
            bufferedWriter.flush();
        }
    
    }

    客戶端實現(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 {
            // 客戶端創(chuàng)建connect-socket
            Socket connectSocket = new Socket(SERVER_IP, SERVER_PORT);
            // 獲取connect-socket輸出流
            BufferedWriter bufferedWriter = new BufferedWriter(
                    new OutputStreamWriter(connectSocket.getOutputStream()));
            // 客戶端向服務(wù)端發(fā)送數(shù)據(jù)
            bufferedWriter.write("來自客戶端的請求數(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ù)端在accept時是阻塞的,并且在主線程中,一次只能accept一個Socket,acceptSocket后,讀取客戶端數(shù)據(jù)時又是阻塞的。

    二. Non Blocking IO

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

    Non Blocking IO示意圖如下所示。

    Java?IO網(wǎng)絡(luò)模型如何實現(xiàn)

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

    public class NonbioServer {
    
        public static final List<SocketChannel> clientSocketChannels = new ArrayList<>();
    
        public static void main(String[] args) throws Exception {
            // 客戶端創(chuàng)建listen-socket管道
            // 管道支持非阻塞模式和同時讀寫
            ServerSocketChannel listenSocketChannel = ServerSocketChannel.open();
            // 設(shè)置為非阻塞模式
            listenSocketChannel.configureBlocking(false);
            // 綁定監(jiān)聽的端口號
            listenSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 在子線程中遍歷clientSocketChannels并讀取客戶端數(shù)據(jù)
            handleSocketChannels();
    
            while (true) {
                // 非阻塞方式監(jiān)聽客戶端連接
                // 如果無客戶端連接則返回空
                // 有客戶端連接則創(chuàng)建代表這個客戶端的client-socket管道
                SocketChannel clientSocketChannel = listenSocketChannel.accept();
                if (clientSocketChannel != null) {
                    // 設(shè)置為非阻塞模式
                    clientSocketChannel.configureBlocking(false);
                    // 添加到clientSocketChannels中
                    // 用于子線程遍歷并讀取客戶端數(shù)據(jù)
                    clientSocketChannels.add(clientSocketChannel);
                } else {
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
            }
        }
    
        public static void handleSocketChannels() {
            new Thread(() -> {
                while (true) {
                    // 遍歷每一個client-socket管道
                    Iterator<SocketChannel> iterator = clientSocketChannels.iterator();
                    while (iterator.hasNext()) {
                        SocketChannel clientSocketChannel = iterator.next();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        int read = 0;
                        try {
                            // 將客戶端發(fā)送的數(shù)據(jù)讀取到ByteBuffer中
                            // 這一步的操作也是非阻塞的
                            read = clientSocketChannel.read(byteBuffer);
                        } catch (IOException e) {
                            // 移除發(fā)生異常的client-socket管道
                            iterator.remove();
                            e.printStackTrace();
                        }
                        if (read == 0) {
                            System.out.println("客戶端數(shù)據(jù)未就緒");
                        } else {
                            System.out.println("客戶端數(shù)據(jù)為:" + new String(byteBuffer.array()));
                        }
                    }
                    LockSupport.parkNanos(1000 * 1000 * 1000);
                }
            }).start();
        }
    
    }

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

    • 一次只能accept一個Socket

    • 需要在用戶進程中遍歷所有的SocketChannel并調(diào)用read() 方法獲取客戶端數(shù)據(jù),此時如果客戶端數(shù)據(jù)未準備就緒,那么這一次的read() 操作的開銷就是浪費的。

    三. IO多路復(fù)用

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

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

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

    • 在用戶進程中將需要監(jiān)控的IO文件描述符(Socket)注冊到IO多路復(fù)用器中;

    • 執(zhí)行select操作,此時用戶進程由用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)(一次系統(tǒng)調(diào)用),然后在內(nèi)核態(tài)中會輪詢注冊到IO多路復(fù)用器中的IO是否準備就緒,并得到所有準備就緒的IO的文件描述符列表,最后返回這些文件描述符列表;

    • 用戶進程在select操作返回前會一直阻塞,直至select操作返回,此時用戶進程就獲得了所有就緒的IO的文件描述符列表;

    • 用戶進程獲得了就緒的IO的文件描述符列表后,就可以對這些IO進行相應(yīng)的操作了。

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

    四. NIO

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

    • channel(管道)。介于buffer(字節(jié)緩沖區(qū))和Socket(套接字)之間,用于數(shù)據(jù)的讀寫操作;

    • buffer(字節(jié)緩沖區(qū))。是用戶程序和channel(管道)之間進行讀寫數(shù)據(jù)的中間區(qū)域;

    • selectorIO多路復(fù)用器)。服務(wù)端的listen-socketclient-socket,客戶端的connect-socket,都可以注冊在selector上,注冊的時候還需要指定監(jiān)聽的事件,比如為listen-socket指定監(jiān)聽的事件為ACCEPT事件,該事件發(fā)生則表示客戶端建立了連接,還比如為client-socket指定監(jiān)聽的事件為READ事件,該事件發(fā)生則表示客戶端發(fā)送的數(shù)據(jù)已經(jīng)可讀。

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

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

    public class NioServer {
    
        private static Selector selector;
    
        public static void main(String[] args) {
    
            try {
                // 開啟并得到多路復(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管道注冊到多路復(fù)用器上,并指定監(jiān)聽ACCEPT事件
                listenSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
                while (true) {
                    // 獲取發(fā)生的事件,這個操作是阻塞的
                    selector.select();
                    // 拿到有事件發(fā)生的SelectionKey集合
                    // SelectionKey表示管道與多路復(fù)用器的綁定關(guān)系
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    // 遍歷每個發(fā)生的事件,然后判斷事件類型
                    // 根據(jù)事件類型,進行不同的處理
                    Iterator<SelectionKey> iterator = selectionKeys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey selectionKey = iterator.next();
                        iterator.remove();
                        if (selectionKey.isAcceptable()) {
                            // 處理客戶端連接事件
                            handlerAccept(selectionKey);
                        } else if (selectionKey.isReadable()) {
                            // 處理客戶端數(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 {
                // 為連接的客戶端創(chuàng)建client-socket管道
                SocketChannel clientSocketChannel = listenSocketChannel.accept();
                // 設(shè)置為非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 將client-socket管道注冊到多路復(fù)用器上,并指定監(jiān)聽READ事件
                clientSocketChannel.register(selector, SelectionKey.OP_READ);
                // 給客戶端發(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 {
                // 讀取客戶端數(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();
            }
        }
    
    }

    客戶端實現(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 {
                // 開啟并得到多路復(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管道注冊到多路復(fù)用器上,并指定監(jiān)聽CONNECT事件
                connectSocketChannel.register(selector, SelectionKey.OP_CONNECT);
    
                while (true) {
                    // 獲取發(fā)生的事件,這個操作是阻塞的
                    selector.select();
                    // 拿到有事件發(fā)生的SelectionKey集合
                    // SelectionKey表示管道與多路復(fù)用器的綁定關(guān)系
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    // 遍歷每個發(fā)生的事件,然后判斷事件類型
                    // 根據(jù)事件類型,進行不同的處理
                    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管道注冊到多路復(fù)用器上,并指定監(jiān)聽READ事件
            connectSocketChannel.register(selector, SelectionKey.OP_READ);
            // 向服務(wù)端發(fā)送數(shù)據(jù)
            connectSocketChannel.write(ByteBuffer.wrap("客戶端發(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();
            }
        }
    
    }

    到此,相信大家對“Java IO網(wǎng)絡(luò)模型如何實現(xiàn)”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

    向AI問一下細節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI