• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • Netty編程之基礎BIO NIO AIO網絡編程模型

    標簽: Netty  netty  網絡  java

    前言

    公司業務需求,需要用到tcp數據傳輸,基于java自帶的socket太low,而且編程復雜,市面上比較流行的netty比較適合。學習netty之前,先簡單總結一下各種數據傳輸類型。

    java支持的類型

    Java 共支持 3 種網絡編程模型/IO 模式:BIO、NIO、AIO
    Java BIO:

    理解: 同步并阻塞(傳統阻塞型),服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器
    端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷
    適用場景:BIO 方式適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,JDK1.4
    以前的唯一選擇,但程序簡單易理解。
    通俗理解:一個請求需要創建一個新的線程,如果客戶端比較多的話,服務器肯定是受不了的

    Java NIO :

    理解: 同步非阻塞,服務器實現模式為一個線程處理多個請求(連接),即客戶端發送的連接請求都會注
    冊到多路復用器上,多路復用器輪詢到連接有 I/O 請求就進行處理
    適用場景:NIO 方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,彈幕系統,服務器間通訊等。
    編程比較復雜,JDK1.4 開始支持。
    通俗理解:NIO有緩沖池,有通道。有選擇器,一個選擇器一個線程,多個通道對應多個緩沖池,可對應一個通道,這樣有請求過來,直接放到選擇器,由選擇器一個個執行,這樣就做到了同步,并且非阻塞。

    Java AIO

    理解: 異步非阻塞,AIO 引入異步通道的概念,采用了 Proactor 模式,簡化了程序編寫,有效
    的請求才啟動線程,它的特點是先由操作系統完成后才通知服務端程序啟動線程去處理,一般適用于連接數較
    多且連接時間較長的應用,JDK1.4 開始支持。
    適用場景:AIO 方式使用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用 OS 參與并發操作,
    編程比較復雜,JDK7 開始支持。
    通俗理解:這個是1.7以后才支持的,用的比較少,跟netty關系也不大,感興趣的可自行研究。

    NIO三大核心

    因為netty是基礎NIO模型的框架,所以下面著重講一下NIO的三大核心。

    NIO模型圖解:

    在這里插入圖片描述

    緩沖區

    理解:緩沖區本質上是一個 可以讀寫數據的內存塊可以理解成是一個 容器對象(包含數組的對象)。它能夠跟蹤和記錄緩沖區內的狀態變化。
    主要實現方法
    頂級父類是buffer。
    在這里插入圖片描述
    屬性
    在這里插入圖片描述

    通道

    理解:NIO 的通道類似于流,但有些區別如下

    1. 通道可以同時進行讀寫,而流只能讀或者只能寫

    2. 通道可以實現異步讀寫數據

    3. 通道可以從緩沖讀數據,也可以寫數據到緩沖
      主要實現方法
      頂級父類是channel
      實現子類

    4. FileChannel 用于文件的數據讀寫

    5. DatagramChannel 用于 UDP 的數據讀寫

    6. ServerSocketChannel是TCP服務端數據讀寫

    7. SocketChannel 是客戶端TCP 的數據讀寫。
      常見方法

    8. public int read(ByteBuffer dst) ,從通道讀取數據并放到緩沖區中

    9. public int write(ByteBuffer src) ,把緩沖區的數據寫到通道中

    10. public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道

    11. public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道

    選擇器

    理解:用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到 Selector(選擇器),一個選擇器管理多個通道,其管理的通道注冊到選擇器上,然后選擇器會監聽通道內是否發生變化,如果變化了,才會去處理,這樣就降低了連接的開銷。

    常用方法
    open()得到一個選擇器對象
    select(long timeout) 監控所有注冊的通道。當其中有IO操作的時候,將對應的selectkey加入到內部集合并且返回。參數用來設置超時時間,
    selectkeys()用來獲取所有注冊到選擇器上面的通道。

    流程圖
    在這里插入圖片描述

    文字解釋
    在這里插入圖片描述

    代碼示例

    客戶端代碼

    package com.huali.nio.groupchat;
    
    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.Scanner;
    import java.util.Set;
    
    public class GroupChatClient {
    
        //定義相關的屬性
        private final String HOST = "127.0.0.1"; // 服務器的ip
        private final int PORT = 6667; //服務器端口
        private Selector selector;
        private SocketChannel socketChannel;
        private String username;
    
        //構造器, 完成初始化工作
        public GroupChatClient() throws IOException {
    
            selector = Selector.open();
            //連接服務器
            socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
            //設置非阻塞
            socketChannel.configureBlocking(false);
            //將channel 注冊到selector
            socketChannel.register(selector, SelectionKey.OP_READ);
            //得到username
            username = socketChannel.getLocalAddress().toString().substring(1);
            System.out.println(username + " is ok...");
    
        }
    
        //向服務器發送消息
        public void sendInfo(String info) {
    
            info = username + " 說:" + info;
    
            try {
                socketChannel.write(ByteBuffer.wrap(info.getBytes()));
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        //讀取從服務器端回復的消息
        public void readInfo() {
    
            try {
    
                int readChannels = selector.select();
                if(readChannels > 0) {//有可以用的通道
    
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while (iterator.hasNext()) {
    
                        SelectionKey key = iterator.next();
                        if(key.isReadable()) {
                            //得到相關的通道
                           SocketChannel sc = (SocketChannel) key.channel();
                           //得到一個Buffer
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            //讀取
                            sc.read(buffer);
                            //把讀到的緩沖區的數據轉成字符串
                            String msg = new String(buffer.array());
                            System.out.println(msg.trim());
                        }
                    }
                    iterator.remove(); //刪除當前的selectionKey, 防止重復操作
                } else {
                    //System.out.println("沒有可以用的通道...");
    
                }
    
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws Exception {
    
            //啟動我們客戶端
            GroupChatClient chatClient = new GroupChatClient();
    
            //啟動一個線程, 每個3秒,讀取從服務器發送數據
            new Thread() {
                public void run() {
    
                    while (true) {
                        chatClient.readInfo();
                        try {
                            Thread.currentThread().sleep(3000);
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
    
            //發送數據給服務器端
            Scanner scanner = new Scanner(System.in);
    
            while (scanner.hasNextLine()) {
                String s = scanner.nextLine();
                chatClient.sendInfo(s);
            }
        }
    
    
    }
    
    

    客戶端代碼

    
    package com.huali.nio.groupchat;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    
    public class GroupChatServer {
        //定義屬性
        private Selector selector;
        private ServerSocketChannel listenChannel;
        private static final int PORT = 6667;
    
        //構造器
        //初始化工作
        public GroupChatServer() {
    
            try {
    
                //得到選擇器
                selector = Selector.open();
                //ServerSocketChannel
                listenChannel =  ServerSocketChannel.open();
                //綁定端口
                listenChannel.socket().bind(new InetSocketAddress(PORT));
                //設置非阻塞模式
                listenChannel.configureBlocking(false);
                //將該listenChannel 注冊到selector
                listenChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            }catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
        //監聽
        public void listen() {
    
            System.out.println("監聽線程: " + Thread.currentThread().getName());
            try {
    
                //循環處理
                while (true) {
    
                    int count = selector.select();
                    if(count > 0) {//有事件處理
    
                        //遍歷得到selectionKey 集合
                        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                        while (iterator.hasNext()) {
                            //取出selectionkey
                            SelectionKey key = iterator.next();
    
                            //監聽到accept
                            if(key.isAcceptable()) {
                                SocketChannel sc = listenChannel.accept();
                                sc.configureBlocking(false);
                                //將該 sc 注冊到seletor
                                sc.register(selector, SelectionKey.OP_READ);
    
                                //提示
                                System.out.println(sc.getRemoteAddress() + " 上線 ");
    
                            }
                            if(key.isReadable()) { //通道發送read事件,即通道是可讀的狀態
                                //處理讀 (專門寫方法..)
    
                                readData(key);
    
                            }
                            //當前的key 刪除,防止重復處理
                            iterator.remove();
                        }
    
                    } else {
                        System.out.println("等待....");
                    }
                }
    
            }catch (Exception e) {
                e.printStackTrace();
    
            }finally {
                //發生異常處理....
    
            }
        }
    
        //讀取客戶端消息
        private void readData(SelectionKey key) {
    
            //取到關聯的channle
            SocketChannel channel = null;
    
            try {
               //得到channel
                channel = (SocketChannel) key.channel();
                //創建buffer
                ByteBuffer buffer = ByteBuffer.allocate(1024);
    
                int count = channel.read(buffer);
                //根據count的值做處理
                if(count > 0) {
                    //把緩存區的數據轉成字符串
                    String msg = new String(buffer.array());
                    //輸出該消息
                    System.out.println("form 客戶端: " + msg);
    
                    //向其它的客戶端轉發消息(去掉自己), 專門寫一個方法來處理
                    sendInfoToOtherClients(msg, channel);
                }
    
            }catch (IOException e) {
                try {
                    System.out.println(channel.getRemoteAddress() + " 離線了..");
                    //取消注冊
                    key.cancel();
                    //關閉通道
                    channel.close();
                }catch (IOException e2) {
                    e2.printStackTrace();;
                }
            }
        }
    
        //轉發消息給其它客戶(通道)
        private void sendInfoToOtherClients(String msg, SocketChannel self ) throws  IOException{
    
            System.out.println("服務器轉發消息中...");
            System.out.println("服務器轉發數據給客戶端線程: " + Thread.currentThread().getName());
            //遍歷 所有注冊到selector 上的 SocketChannel,并排除 self
            for(SelectionKey key: selector.keys()) {
    
                //通過 key  取出對應的 SocketChannel
                Channel targetChannel = key.channel();
    
                //排除自己
                if(targetChannel instanceof  SocketChannel && targetChannel != self) {
    
                    //轉型
                    SocketChannel dest = (SocketChannel)targetChannel;
                    //將msg 存儲到buffer
                    ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
                    //將buffer 的數據寫入 通道
                    dest.write(buffer);
                }
            }
    
        }
    
        public static void main(String[] args) {
    
            //創建服務器對象
            GroupChatServer groupChatServer = new GroupChatServer();
            groupChatServer.listen();
        }
    }
    
    //可以寫一個Handler
    class MyHandler {
        public void readData() {
    
        }
        public void sendInfoToOtherClients(){
    
        }
    }
    
    
    
    版權聲明:本文為qq_28483283原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/qq_28483283/article/details/109167218

    智能推薦

    從零開始學Netty (一)-- Linux網絡IO模型 及 Java實現BIO、NIO、AIO

    下面準備做一個Netty的系列教程,適合初次接觸Netty或網絡通訊的同學了解閱讀。本文主要介紹Linux網絡IO模型、BIO、NIO、AIO及代碼實現。至于更基礎的OSI、TCP/IP、HTTP等,相信大家都不陌生就不一一贅述了。由于Netty5停止維護了,所以后面所涉及的Netty都是指Netty4這個版本,故也不支持AIO。 Linux網絡IO模型 一般服務器都運行在Linux環境下,故這里...

    結合代碼詳細聊聊 Java 網絡編程中的 BIO、NIO 和 AIO

    本文從操作系統的角度來解釋BIO,NIO,AIO的概念,含義和背后的那些事。本文主要分為3篇。 第一篇 講解BIO和NIO以及IO多路復用 第二篇 講解磁盤IO和AIO 第三篇 講解在這些機制上的一些應用的實現方式,比如nginx,nodejs,Java NIO等 到底什么是“IO Block” 很多人說BIO不好,會“block&rdquo...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    猜你喜歡

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    styled-components —— React 中的 CSS 最佳實踐

    https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    精品国产乱码久久久久久蜜桃不卡