JAVA網絡通信IO-NIO-AIO-Netty


通信協議的分層規定

把用戶應用層作為最高層,把物理通信線路作為最底層,期間的協議處理分為若干層,規定每層處理的任務,也規定每層的接口標准。

目前分層國際的標准有兩種:OSI參考模型和TCP/IP參考模型

一台機器想把一句話送出去的流程

需要從應用層一步步的把數據封裝傳遞到最底層直到物理層的底層轉換為二進制0100101010然后在送出去

送出去之后 對方最底層接收到0101010101然后一步步的翻譯到應用層 這是數據在網絡之間傳輸的一個過程。

底層對我們透明 看似是應用層之間通信,並不是。

問題:說下http訪問一個頁面的全流程???

回答:通過我們輸入的網址URL在應用層進行DNS進行域名解析,找到與它相對應的IP地址

並將請求的數據放到HTTP數據中去。前提是看是否有緩存,如果有緩存,可以直接拿

取數據,如果沒有緩存,那就需要進行請求(get請求才會緩存)在傳輸層利用TCP協議進行可靠的傳輸,

加上TCP首部封裝。在網絡層加上IP首部的封裝,經過網絡層傳輸到服務器。 然后在

依次向上(解封)找到應用層,得到數據。然后在從服務器返回到客戶端,加載整個

頁面。

HTTPS加密

在使用HTTPS時,所有的HTTP請求和響應數據在發送到網絡之前,都要進行加密。

HTTPS在HTTP下面提供了一個傳輸級的密碼安全層SSL/TSL(Transport Layer Security)。即在應用層和傳輸層之間加了一個安全層。

對稱秘鑰的加密技術:編/解碼時使用相同秘鑰的算法。

在對稱加密技術中,發送端和接收端要共享相同的秘鑰K才能進行通信。發送端用共享的秘鑰加密報文,並將得到的密文發送給接收端。

接收端接收到密文,並對其應用解密函數和相同的共享秘鑰,恢復出原始的明文。

對稱秘鑰加密技術的缺點之一就是發送者和接受者在互相對話之前,一定要有一個共享的保密秘鑰。

流行的對稱秘鑰加密算法包括: DES Triple-DES RC2和RC4。64為的秘鑰應該是大多數公司所采用的。

不對稱秘鑰加密系統:編/解碼使用不同秘鑰的算法。

非對稱加密使用兩個秘鑰,一個用來對主機報文編碼,另一個用來對主機報文解碼。

編碼秘鑰是眾所周知的,只有主機才知道私有的解密秘鑰。

通過公開秘鑰的加密技術,全球所有的計算機用戶就都可以使用安全協議了,其中RSA算法是一個比較流行的公開秘鑰的加密算法。

用證書對服務器進行認證。

通過HTTPS建立了一個安全的Web事物之后,瀏覽器都會自動獲取所連接服務器的數字證書。

瀏覽器收到證書時,會對簽名頒發機構進行檢查。如果這個機構是個很有權威的公共簽名機構,瀏覽器可以知道其公開秘鑰了(瀏覽器會預裝很多簽名頒發機構的證書)。

參考文章 https://blog.csdn.net/zmx729618/article/details/78485665

數字簽名

數字簽名是附加在報文上特殊加密校驗碼,使用數字簽名有以下兩個好處

  簽名可以證明是作者(服務器)編寫了這條報文

  簽名可以防止報文被篡改。如果有惡意攻擊者在報文傳輸過程中對其進行了修改,校驗和就不匹配了。

數字簽名技術將摘要信息用發送者(服務器)的私鑰加密,與原文一起傳送給接收者。

數字簽名是個加密的過程,數字簽名驗證是個解密的過程。私鑰加密,公鑰解密。

公鑰加密 私鑰解密 和私鑰加密 公鑰解密一起使用,既保證了安全性也保證了唯一性。

Socket實現網絡通信(阻塞式IO)

  • 在使用IO和Socket構造網絡服務時 接收連接:accept(),接收請求數據,發送響應數據都可能引起阻塞的操作。(Handler必須使用多線程異步操作,不然別的連接進不來)
  • 線程從Socket輸入流讀數據時,如果沒有足夠的數據就會進入阻塞狀態,直到讀夠了足夠的數據,或者達到輸入流的末尾,或者出現了異常,才能從輸入流的read()方法返回或異常中斷。
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket();
        ss.bind(new InetSocketAddress("127.0.0.1", 8888));
        while(true) {
            Socket s = ss.accept(); //阻塞方法

            new Thread(() -> {
                handle(s);
            }).start();
        }

    }

    static void handle(Socket s) {
        try {
            byte[] bytes = new byte[1024];
            int len = s.getInputStream().read(bytes);
            System.out.println(new String(bytes, 0, len));

            s.getOutputStream().write(bytes, 0, len);
            s.getOutputStream().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

NIO對網絡通信改進

網絡通信在阻塞模式下,

  1. read()方法會爭取讀到n個字節,如果輸入流中不足n個字節,就進入阻塞狀態,直到讀取了n個字節,或者讀到了輸入流末尾,或者出現了I/O異常。
  2. socket.accept()方法如果沒有接收到連接,也會一直等待

大量線程連接進來的時候,效率比較低(所有線程都阻塞在那里)

網絡通信在非阻塞模式下(NIO對BIO的改進)

  1. read()方法奉行能讀到多少數據就讀到多少數據的原則。read()方法讀取當前通道中的可讀數據,有可能不足n個字節,或者為0個字節,read()方法總會立刻返回。而不會等到讀取了n個字節才返回,read()方法返回實際上讀入的字節數。SocketChannel extends AbstractSelectableChannel 類的中 int read(ByteBuffer dst)方法是非阻塞式的。
  2. ServerSocketChannel或SockeChannel通過register()方法向Selector注冊事件時register()方法會創建一個SelectionKey對象,這個SelectionKey對象是跟蹤注冊事件的句柄。在SelectionKey對象有效期,Selector會一直監控與SelectorKey對象相關的事件,如果事件發生,就會把SelectionKey對象加入到Selector-keys集合中。

1)NIO-Single(單線程模型)

大管家selector

除了管理客戶端的連接之外

連接通道建立后:還盯着有沒有需要讀寫的數據(一個大管家 領着一幫工人)

使用Selector創建一個非阻塞的服務器。

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
        ssc.configureBlocking(false);

        System.out.println("server started, listening on :" + ssc.getLocalAddress());
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while(true) {
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            while(it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                handle(key);
            }
        }

    }

handler(key)沒有異步的去處理,這是單線程模型的NIO

NIO的讀寫都是用ByteBuffer 一塊一塊的讀 相比BIO的一個byte的讀,提高了很多效率 但是特別難用:推薦NIO類庫介紹

當你忘記flip復位的操作,你可以把消息讀成一半,這也是Netty受歡迎的原因。

2)NIO-Reactor(多線程模型)

Selector任務是BOSS 不干別的事,就負責客戶端的連接

要不要寫 交給Worker工人來做,工人是一個池子(線程池)

 

也就是NIO單線程模型中 Handler用線程池調用

AIO對網絡通信改進

AIO是類似觀察者模式的事件回調,而不在需要輪循

當客戶端需要連接的時候 交給操作系統去鏈接

操作系統一旦連接上了客戶端,會給大管家Selector有人要連上來了

大管家只負責連接的功能 而不需要輪循環,連好的通過交給工人worker處理通道里面的信息

public class Server {
    public static void main(String[] args) throws Exception {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()
                .bind(new InetSocketAddress(8888));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel client, Object attachment) {
                serverChannel.accept(null, this);
                try {
                    System.out.println(client.getRemoteAddress());
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer attachment) {
                            attachment.flip();
                            System.out.println(new String(attachment.array(), 0, result));
                            client.write(ByteBuffer.wrap("HelloClient".getBytes()));
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            exc.printStackTrace();
                        }
                    });


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        while (true) {
            Thread.sleep(1000);
        }

    }
}

serverChannel.accept的時候 就可以走了,回調函數 觀察者設計模式,把這個CompletionHandler方法交給操作系統去執行

下面之所以寫while(true)是為了防止程序結束,如果想寫的嚴謹一些,可以用countDownLatch

Netty

Netty是對NIO進行了封裝,封裝的API更像AIO

netty的寫法和AIO差不多

netty把NIO中難用的byteBuffer封裝的特別好

疑問點:有了AIO為什么還需要NIO

因為AIO和NIO在linux底層都是用的epoll模型實現的,epoll本身就是輪循模型

所以你上層在怎么封裝,下層還是輪循(Netty就是用的NIO而不是AIO),在Linux中AIO的效率未必比NIO高

而Windows的AIO是自己單獨實現的,不是輪訓模型而是事件模型(Windows的Server比較少 Netty未做重點)

package com.example.nio.io.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;

public class HelloNetty {
    public static void main(String[] args) {
        new NettyServer(8888).serverStart();
    }
}

class NettyServer {


    int port = 8888;

    public NettyServer(int port) {
        this.port = port;
    }

    public void serverStart() {
        /**定義兩個線程池**/
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();
        /**把這兩個group傳給Server啟動的封裝類**/
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)/**指定Server啟動之后 客戶端連接上來的通道類型**/
                .childHandler(new ChannelInitializer<SocketChannel>() {/**每一個客戶端連上來之后 監聽器處理**/
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        /**通道一旦init 在這個通道上就添加對這個通道的處理器**/
                        ch.pipeline().addLast(new Handler());
                    }
                });

        try {
            ChannelFuture f = b.bind(port).sync();

            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }


    }
}

class Handler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //super.channelRead(ctx, msg);
        System.out.println("server: channel read");
        ByteBuf buf = (ByteBuf)msg;

        System.out.println(buf.toString(CharsetUtil.UTF_8));

        ctx.writeAndFlush(msg);

        ctx.close();

        //buf.release();
    }

    /**發生異常的回調方法**/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
        ctx.close();
    }
}
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM