• <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網絡編程一:NIO

    寫這些東西的原因,因為本人14年畢業,從事工作也有幾年了,中間關于書,一直看了很多,有的甚至看過很多遍,但是總是覺得自己沒有熟透一門技術,所以在學習使用技術之余,把自己所學的東西記錄下來,便于加深印象,提升自我!

    目錄

    1 傳統BIO socket通信
    2 NIO 編程
    

    一:傳統socket通信

    在NIO編程沒出來之前,java使用的socket編程時
    socket 服務端:

    public class OldSocketServer {
    
        private void start(int port) throws IOException {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(port);
                Socket socket = null;
                while (true){
                    socket = serverSocket.accept();
                    new Thread(new OldSocketServerHandler(socket)).start();
                }
            }finally {
                System.out.println("關閉服務端...");
                serverSocket.close();
            }
        }
    
        public static void main(String[] args) throws IOException {
            new OldSocketServer().start(8090);
        }
    }
    

    對應的handler處理:

    public class OldSocketServerHandler implements Runnable {
    
        private Socket socket;
    
        public OldSocketServerHandler(Socket socket) {
            this.socket = socket;
        }
    
        public void run() {
            BufferedReader in = null;
            try {
                in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
                String body = null;
                while ((body = in.readLine())!=null){
                    System.out.println(" 服務端收到消息 : " + body);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(socket != null){
                    try {
                        if(in != null){
                            in.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    socket客戶端:

    public class OldSocketClient {
    
        private void connect(String ip, int port) throws IOException {
            Socket socket = null;
            PrintWriter out = null;
            try {
                socket = new Socket(ip, port);
                out = new PrintWriter(socket.getOutputStream(), true);
                out.write("this is socket client to server msg.");
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if(out !=null){
                    out.close();
                }
                if(socket != null){
                    socket.close();
                }
            }
        }
    
        public static void main(String[] args) throws IOException {
            new OldSocketClient().connect("localhost",8090);
        }
    }
    

    通過上面簡單的一個socket通信問題,可以找出幾個問題:
    問題1. 對于上面的服務端,當每接一個客戶端請求,服務端必須new一個線程處理新的請求,對于java這種線程資源非常珍 貴的語言中,這種設計顯示是有問題的。
    問題2:對于IO數據的讀取上,對于InputStream輸入流,當對socket的輸入流進行讀取操作的時候,線程會一直阻塞,直到讀取到數據,或數據讀取完畢,又或者發生空指針或I/O異常時。 同樣輸出流輸出數據,OutputStream輸出流會把所有的數據全部發送出去或者發生異常才會停止阻塞。顯然,同步阻塞這種設計是十分浪費系統資源的。

    二:nio編程

    NIO是JDK1.4引入的,通過快的形式處理數據。NIO常用的幾個概念:

    1、Buffer(緩沖區)
    在傳統的面向流的I/O中,數據是直接寫入或讀取到流對象中的,而在NIO中,所有的數據都是用緩沖區來處理的。緩沖區實質上就是一個數組,通過定義數據的結構,是的緩沖區能被重復利用。
    NIO定義的Buffer的繼承關系圖:
    在這里插入圖片描述

    緩沖區定義四個屬性來提供關于其所包含的數據元素的信息:
    1).容量 ( Capacity )
    緩沖區能夠容納的數據元素的最大數量。這一容量在緩沖區創建時被設定,并且永遠不能 被改變。
    2).上界 ( Limit )
    緩沖區的第一個不能被讀或寫的元素。或者說,緩沖區中現存元素的計數。
    3).位置 ( Position )
    下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。
    4).標記 ( Mark )
    一個備忘位置。調用 mark( )來設定 mark = postion。調用 reset( )設定 position = mark。標記在設定前是未定義的 (undefined)。 這四個屬性之間總是遵循以下關系:
    0 <= mark <= position <= limit <= capacity
    新初始化的Buffer圖:
    在這里插入圖片描述

    2、通道Channel
    Channel是一個通道,是全雙工的,就像是自來水管一樣,網絡數據通過Channel讀取和寫入。與流IO的不同之處在意,流IO只能進行讀InputStream 或者寫OutputStream, 而Channel可讀可寫。
    Channel繼承關系類圖如下,主要有:ServerSocketChannel, SocketChannel, DatagramChannel
    在這里插入圖片描述

    3、多路復用器Selector
    多路復用器Selector提供選擇已經就緒的任務的能力。selector會不斷的輪詢注冊在其上的Channel, 如果某個Channel發生讀或者寫事件,則這個Channel就處于就緒狀態,會被Selector輪詢出來,然后通過SelectionKey獲取就緒Channel的集合,進行后續的I/O操作。
    在JDK中selector使用了epoll()來實現select, 故此沒有輪詢Channel數量限制。

    一個NIO的案例:
    服務端代碼:

    public class NioServer {
    
        private ByteBuffer readBuf = ByteBuffer.allocate(1024);
    
        private void start(int port) throws IOException {
            ServerSocketChannel serverSocketChannel = null;
            try{
                // 1、打開ServerSocketChannel,用于監聽客戶端連接,是所有客戶端連接的父管道
                serverSocketChannel = ServerSocketChannel.open();
                // 2、綁定監聽端口,設置連接為非阻塞模式
                serverSocketChannel.socket().bind(new InetSocketAddress(port));
                serverSocketChannel.configureBlocking(false);
                // 3、創建Reactor線程,創建多路復用器并啟動線程
                Selector selector = Selector.open();
                // 4、將ServerSocketChannel注冊到Reactor線程的多路復用器Selector上,監聽ACCEPT事件
                serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
                System.out.println(" 服務端開始工作.....................");
                run(selector);
            }finally {
                if(serverSocketChannel!=null){
                    serverSocketChannel.close();
                }
            }
    
        }
    
        private void run(Selector selector) {
            while(true){
                try {
                    //1.讓多路復用器開始監聽
                    selector.select();
                    //2.返回多路復用器已經選擇的結果集
                    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
                    while(keys.hasNext()){
                        SelectionKey key = keys.next();
                        keys.remove();
                        if(key.isValid()){
                            if(key.isConnectable()){
                                System.out.println("connectable....");
                            }
                            if(key.isWritable()){
                                System.out.println("writable.........");
                            }
                            if(key.isAcceptable()){
                                System.out.println("acceptable....");
                                accept(key,selector);//這里的key就是服務器端的Channel的key
                            }
                            if(key.isReadable()){
                                System.out.println("readable....");
                                read(key);
                            }
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private void read(SelectionKey key) {
            try {
                // 1. 清空舊的緩沖區
                readBuf.clear();
                //2.獲取之前注冊的socket通道對象
                SocketChannel sc = (SocketChannel) key.channel();
                //3.讀取數據
                int count = sc.read(readBuf);
                //4.如果沒有數據
                if(count == -1){
                    key.channel().close();
                    key.cancel();
                    System.out.println("已無可讀數據");
                    return;
                }
                //5.有數據則進行讀取,讀取之前需要進行復位方法(把position和limit進行復位)
                readBuf.flip();
                //6.根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
                byte[] bytes = new byte[readBuf.remaining()];
                //7.接收緩沖區數據
                readBuf.get(bytes);
                //8.打印結果
                String body = new String(bytes).trim();
                System.out.println("Server: " + body);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private void accept(SelectionKey key, Selector selector) {
            try {
                //1.獲取服務端通道
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                //2.執行客戶端Channel的阻塞方法
                SocketChannel sc = ssc.accept();
                //3.設置阻塞模式
                sc.configureBlocking(false);
                //4.注冊到多路復用器上,并設置讀取標識
                sc.register(selector, SelectionKey.OP_READ);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws IOException {
            new NioServer().start(8090);
        }
    }
    
    客戶端代碼:
    
    public class NioClient {
    
        public static void main(String[] args) {
            SocketChannel sc = null;
            ByteBuffer buf = ByteBuffer.allocate(1024);
            try {
                //打開通道
                sc = SocketChannel.open();
                //進行連接
                sc.connect(new InetSocketAddress("127.0.0.1", 8090));
                //把數據放到緩沖區
                buf.put("fangyouyun".getBytes());
                //復位
                buf.flip();
                //寫出數據
                sc.write(buf);
                //清空緩沖區數據
                buf.clear();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    sc.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    總結:NIO以多路復用的設計方式,以及Buffer緩沖區的設計,都使得性能得到很大的提升,但是API太復雜了,所以選擇Netty,因為Netty是基于NIO再次封裝,便于開發。

    博客中案例代碼:https://download.csdn.net/download/qq_22871607/11072379

    版權聲明:本文為qq_22871607原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/qq_22871607/article/details/88187135

    智能推薦

    網絡編程——NIO編程

    我們在之前介紹NIO的時候,提到過會使用Selector(選擇器)來實現IO的多路復用,那么Selector是什么呢? Selector Selector是“選擇器”,也可以稱為為“輪詢代理器”、“事件訂閱器”、“channel容器管理機”都行。主要作用是應用程序將向Selector對象注冊需要它關注的...

    nio與netty編程(二)

    文章目錄 四 netty Netty 整體設計 線程模型 單線程模型 image-20200808113429095 線程池模型 netty模型 異步模型 核心API ChannelHandler 及其實現類 Pipeline 和 和 ChannelPipeline ChannelHandlerContext ChannelOption ChannelFuture EventLoopGroup 和...

    NIO_網絡編程

    NIO_網絡編程的全部的全部...

    NIO網絡編程

    文章目錄 1.Java NIO 2.Buffer緩存區 3.Buffer工作原理 3.1.Buffer API使用練習 3.2ByteBuffer內存類型 4.Channel通道 4.1SocketChannel 4.2ServerSocketChannel 5.NIO模擬客戶端和服務端 5.1阻塞式的方式 5.2升級之后的服務端 6.Selector選擇器 6.1用Selector來優化服務端代...

    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_...

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