• <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

    標簽: #Netty  java  netty

    網絡編程-NETTY

    【基礎篇】


    1、TCP、UDP的區別?

    • TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接
    • TCP提供可靠的服務,UDP盡最大努力交付,即不保證可靠交付
    • UDP具有較好的實時性,工作效率比TCP高,適用于對高速傳輸和實時性有較高的通信或廣播通信。
    • .每一條TCP連接只能是點到點的;UDP支持一對一、一對多、多對一和多對多的交互通信。
    • TCP對系統資源要求較多,UDP對系統資源要求較少。

    2、TCP協議如何保證可靠傳輸?-(校 序 重 流 擁)

    • 校驗和:發送的數據包的二進制相加然后取反,目的是檢測數據在傳輸過程中的任何變化。如果收到段的檢驗和有差錯,TCP將丟棄這個報文段和不確認收到此報文段。 
    • 編號排序:TCP 給發送的每一個包進行編號,接收方對數據包進行排序,把有序數據傳送給應用層。
    • 超時重傳:當TCP發出一個段后,它啟動一個定時器等待目的端確認收到這個報文段如果不能及時收到一個確認,將重發這個報文段
    • 流量控制: TCP 連接的每一方都有固定大小的緩沖空間,TCP的接收端只允許發送端發送接收端緩沖區能接納的數據。當接收方來不及處理發送方的數據,能提示發送方降低發送的速率,防止包丟失。TCP 使用的流量控制協議是可變大小的滑動窗口協議。 (TCP 利用滑動窗口實現流量控制)
    • 擁塞控制: 當網絡擁塞時,減少數據的發送。

    3、TCP的握手、揮手機制?

    我們需要掌握哪些標志量

    • SYN:請求同步標志,為1的時候為有效 
    • ACK:應答標志,表示接受到所發的數據,1為有效 
    • FIN:結束請求標志,1為有效
    • ack:應答,值為告訴對方下一次所發數據地址 
    • seq:值為所發數據地址

    TCP的三次握手:建立連接

               

    (1)第一次握手,客戶機給服務器發送SYN報文,請求連接(其中有一個***為x)

    (2)第二次握手,服務器接收到這個SYN包,反饋客戶機ACK(這個ACK有兩個***,第一個是用來回答客戶機的,第二個是用來校驗第三次握手)

    (3)第三次握手:客戶機接收到服務器的報文,反饋給服務器成功接受的信息。然后這個TCP連接就建立了,通信可以開始。

    TCP的四次揮手:關閉連接。

    (1)客戶端A發送一個FIN,用來關閉客戶A到服務器B的數據傳送。

    (2)服務器B收到這個FIN,它發回一個ACK,確認序號為收到的序號加1。

    (3)服務器B關閉與客戶端A的連接,發送一個FIN給客戶端A。

    (4)客戶端A發回ACK報文確認,并將確認序號設置為收到序號加1。

      問題: 為什么建立連接協議是三次握手,而關閉連接卻是四次握手呢?

    這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求后,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可以未必會馬上會關閉SOCKET,也即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這里的ACK報文和FIN報文多數情況下都是分開發送的.

    4、TCP的粘包/拆包原因及其解決方法是什么?

    發生TCP粘包、拆包主要是由于下面一些原因:

    • 1. 應用程序寫入的數據大于套接字緩沖區大小,這將會發生拆包。
    • 2.應用程序寫入數據小于套接字緩沖區大小,網卡將應用多次寫入的數據發送到網絡上,這將會發生粘包。
    • 3.進行MSS(最大報文長度)大小的TCP分段,當TCP報文長度-TCP頭部長度>MSS的時候將發生拆包。
    • 4.接收方法不及時讀取套接字緩沖區數據,這將發生粘包。

    粘包、拆包解決辦法

    TCP本身是面向流的,作為網絡服務器,如何從這源源不斷涌來的數據流中拆分出或者合并出有意義的信息呢?通常會有以下一些常用的方法:

    • 消息定長。報文大小固定長度,不夠空格補全。發送和接收方遵循相同的約定,這樣即使粘包了通過接收方編程實現獲取定長報文也能區分。  
    • 包尾添加特殊分隔符,例如每條報文結束都添加回車換行符(例如FTP協議)或者指定特殊字符作為報文分隔符,接收方通過特殊分隔符切分報文區分。
    • 將消息分為消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段

    5、Netty的粘包/拆包是怎么處理的,有哪些實現?

    Netty的解決之道

    LineBasedFrameDecoder

    廢話不多說直接上代碼

    服務端

    public class PrintServer {
     
     public void bind(int port) throws Exception {
     // 配置服務端的NIO線程組
     EventLoopGroup bossGroup = new NioEventLoopGroup();
     EventLoopGroup workerGroup = new NioEventLoopGroup();
     try {
      ServerBootstrap b = new ServerBootstrap();
      b.group(bossGroup, workerGroup)
       .channel(NioServerSocketChannel.class)
       .option(ChannelOption.SO_BACKLOG, 1024)
       .childHandler(new ChildChannelHandler());
      // 綁定端口,同步等待成功
      ChannelFuture f = b.bind(port).sync();
     
      // 等待服務端監聽端口關閉
      f.channel().closeFuture().sync();
     } finally {
      // 優雅退出,釋放線程池資源
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
     }
     }
     
     private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
     @Override
     protected void initChannel(SocketChannel arg0) throws Exception {
      arg0.pipeline().addLast(new LineBasedFrameDecoder(1024)); //1
      arg0.pipeline().addLast(new StringDecoder());    //2
      arg0.pipeline().addLast(new PrintServerHandler());
     }
     }
     
     
     public static void main(String[] args) throws Exception {
     int port = 8080;
     new TimeServer().bind(port);
     }
    }
    

    服務端Handler

    
    
    public class PrintServerHandler extends ChannelHandlerAdapter {
     
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg)
      throws Exception {
     ByteBuf buf = (ByteBuf) msg;          
     byte[] req = new byte[buf.readableBytes()]; 
     buf.readBytes(req); //將緩存區的字節數組復制到新建的req數組中
     String body = new String(req, "UTF-8");
     System.out.println(body);
     String response= "打印成功";
     ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());      
     ctx.write(resp);            
     } 
     
     @Override
     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
     ctx.flush();              
     }
     
     
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
     ctx.close();
     }
    }
     
    
    
    

    客戶端

    public class PrintClient {
     
     public void connect(int port, String host) throws Exception {
     EventLoopGroup group = new NioEventLoopGroup();     
     try {
      Bootstrap b = new Bootstrap();        
       b.group(group)            
       .channel(NioSocketChannel.class)      
       .option(ChannelOption.TCP_NODELAY, true)    
       .handler(new ChannelInitializer<SocketChannel>() {  
       @Override
       public void initChannel(SocketChannel ch)    
        throws Exception {
         ch.pipeline().addLast(
         new LineBasedFrameDecoder(1024));   //3
        ch.pipeline().addLast(new StringDecoder());  //4
        ch.pipeline().addLast(new PrintClientHandler());
       }
       });
     
      ChannelFuture f = b.connect(host, port).sync();    
      f.channel().closeFuture().sync();       
     } finally {
      // 優雅退出,釋放NIO線程組
      group.shutdownGracefully();
     }
     }
     
     /**
      * @param args
      * @throws Exception
      */
     public static void main(String[] args) throws Exception {
     int port = 8080;
     new TimeClient().connect(port, "127.0.0.1");
     }
    }
    

    客戶端的Handler

    public class PrintClientHandler extends ChannelHandlerAdapter {
     
     private static final Logger logger = Logger
      .getLogger(TimeClientHandler.class.getName());
     
     private final ByteBuf firstMessage;
     
     /**
      * Creates a client-side handler.
      */
     public TimeClientHandler() {
     byte[] req = "你好服務端".getBytes();
     firstMessage = Unpooled.buffer(req.length);        
     firstMessage.writeBytes(req);
     
     }
     
     @Override
     public void channelActive(ChannelHandlerContext ctx) {
     ctx.writeAndFlush(firstMessage);            
     }
     
     @Override
     public void channelRead(ChannelHandlerContext ctx, Object msg)   
      throws Exception {
     ByteBuf buf = (ByteBuf) msg; 
     byte[] req = new byte[buf.readableBytes()];
     buf.readBytes(req);
     String body = new String(req, "UTF-8");
     System.out.println("服務端回應消息 : " + body);
     }
     
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 
     // 釋放資源
     System.out.println("Unexpected exception from downstream : "
      + cause.getMessage());
     ctx.close();
     }
    }
    

     

    上訴代碼邏輯與上一章代碼邏輯相同,客戶端接受服務端數據答應,并回復客戶端信息,客戶端接受到數據后打印數據。

    我們觀察代碼可以發現,要想Netty解決粘包拆包問題,只需在編寫服務端和客戶端的pipeline上加上相應的解碼器即可,上訴注釋 1,2,3,4處。其余代碼無需做任何修改。

    LineBasedFrameDecoder+StringDecoder的組合就是按行切換的文本解碼器,它被設計用來支持TCP的粘包和拆包。

    原理為:如果連續讀取到最大長度后任然沒有發現換行符,就會拋出異常,同時忽略掉之前督導的異常碼流。

    DelimiteBasedFrameDecoder

    該解碼器的可以自動完成以分割符作為碼流結束標識的消息解碼。

    (其實上一個解碼器類似,如果指定分隔符為換行符,那么與上一個編碼器的作用基本相同)

    使用也很簡單:

    只需要修改服務端和客戶端對應代碼中的initChannel代碼即可

    public void initChannel(SocketChannel ch) {
      ByteBuf delimiter = Unpooled.copiedBuffer("_".getBytes());//1首先創建分隔符緩沖對象ByteBuf,并指定以"_"作為分隔符。
      ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));//2:將分隔符緩沖對象ByteBuf傳入DelimiterBasedFrameDecoder,并指定最大長度。
      ch.pipeline().addLast(new StringDecoder()); //3指定為字符串字節流
      ch.pipeline().addLast(new PrintHandler());
     }
    

     

    FixedLengthFrameDecoder

    該解碼器為固定長度解碼器,它能夠按照指定的長度對詳細進行自動解碼。

    使用同樣也很簡單:

    同樣只需要修改服務端和客戶端對應代碼中的initChannel代碼即可

    public void initChannel(SocketChannel ch)
       throws Exception {
       ch.pipeline().addLast(new FixedLengthFrameDecoder(20));
       ch.pipeline().addLast(new StringDecoder());
       ch.pipeline().addLast(new PrintHandler());
      }
      });
    

    這樣我們就指定了,每接收20個字符大小的字符串字節流就將其看作一個包來經行處理。

    6、同步與異步、阻塞與非阻塞的區別?

    同步與異步

    • 同步: 同步就是發起一個調用后,被調用者未處理完請求之前,調用不返回。
    • 異步: 異步就是發起一個調用后,立刻得到被調用者的回應表示已接收到請求,但是被調用者并沒有返回結果,此時我們可以處理其他的請求,被調用者通常依靠事件,回調等機制來通知調用者其返回結果。

    同步和異步的區別最大在于異步的話調用者不需要等待處理結果,被調用者會通過回調等機制來通知調用者其返回結果

    阻塞和非阻塞

    • 阻塞: 阻塞就是發起一個請求,調用者一直等待請求結果返回,也就是當前線程會被掛起,無法從事其他任務,只有當條件就緒才能繼續。
    • 非阻塞: 非阻塞就是發起一個請求,調用者不用一直等著結果返回,可以先去干其他事情。

    同步阻塞、同步非阻塞(輪訓機制)、異步非阻塞

    舉個生活中簡單的例子,你媽媽讓你燒水,小時候你比較笨啊,在那里傻等著水開(同步阻塞)。等你稍微再長大一點,你知道每次燒水的空隙可以去干點其他事,然后只需要時不時來看看水開了沒有(同步非阻塞:輪訓機制)。后來,你們家用上了水開了會發出聲音的壺,這樣你就只需要聽到響聲后就知道水開了,在這期間你可以隨便干自己的事情,你需要去倒水了(異步非阻塞)。

    7、說說網絡IO模型?

    所有的系統I/O都分為兩個階段:等待就緒和操作。舉例來說,讀函數,分為等待系統可讀和真正的讀;同理,寫函數分為等待網卡可以寫和真正的寫。

    需要說明的是等待就緒的阻塞是不使用CPU的,是在“空等”;而真正的讀寫操作的阻塞是使用CPU的,真正在"干活",而且這個過程非常快,屬于memory copy,帶寬通常在1GB/s級別以上,可以理解為基本不耗時。

    下圖是幾種常見I/O模型的對比:

     

    以socket.read()為例子:

    傳統的BIO里面socket.read(),如果TCP RecvBuffer里沒有數據,函數會一直阻塞,直到收到數據,返回讀到的數據。

    對于NIO,如果TCP RecvBuffer有數據,就把數據從網卡讀到內存,并且返回給用戶;反之則直接返回0,永遠不會阻塞。

    最新的AIO(Async I/O)里面會更進一步:不但等待就緒是非阻塞的,就連數據從網卡到內存的過程也是異步的。

    換句話說,BIO里用戶最關心“我要讀”,NIO里用戶最關心"我可以讀了",在AIO模型里用戶更需要關注的是“讀完了”。

    NIO一個重要的特點是:socket主要的讀、寫、注冊和接收函數,在等待就緒階段都是非阻塞的,真正的I/O操作是同步阻塞的(消耗CPU但性能非常高)。

     

    【netty篇】


    1.Netty 是什么?

    Netty 是一款基于 NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比于 BIO(Blocking I/O,阻塞IO),他的并發性能得到了很大提高。難能可貴的是,在保證快速和易用性的同時,并沒有喪失可維護性和性能等優勢。

    2.Netty 的特點是什么?

    • 高并發:Netty 是一款基于 NIO(Nonblocking IO,非阻塞IO)開發的網絡通信框架,對比于 BIO(Blocking I/O,阻塞IO),他的并發性能得到了很大提高。
    • 傳輸快:Netty 的傳輸依賴于零拷貝特性,盡量減少不必要的內存拷貝,實現了更高效率的傳輸。
    • 封裝好:Netty 封裝了 NIO 操作的很多細節,提供了易于使用調用接口。

    3.什么是 Netty 的零拷貝?

    Netty 的零拷貝主要包含三個方面:

    1. Netty 的接收和發送 ByteBuffer 采用 DIRECT BUFFERS使用堆外直接內存進行 Socket 讀寫,不需要進行字節緩沖區的二次拷貝。如果使用傳統的堆內存(HEAP BUFFERS)進行 Socket 讀寫,JVM 會將堆內存 Buffer 拷貝一份到直接內存中,然后才寫入 Socket 中。相比于堆外直接內存,消息在發送過程中多了一次緩沖區的內存拷貝。
    2. Netty 提供了組合 Buffer 對象,可以聚合多個 ByteBuffer 對象,用戶可以像操作一個 Buffer 那樣方便的對組合 Buffer 進行操作,避免了傳統通過內存拷貝的方式將幾個小 Buffer 合并成一個大的 Buffer。
    3. Netty 的文件傳輸采用了 transferTo 方法,它可以直接將文件緩沖區的數據發送到目標 Channel,避免了傳統通過循環 write 方式導致的內存拷貝問題。

    4.Netty 的優勢有哪些?

    1. 使用簡單:封裝了 NIO 的很多細節,使用更簡單。
    2. 功能強大:預置了多種編解碼功能,支持多種主流協議。
    3. 定制能力強:可以通過 ChannelHandler 對通信框架進行靈活地擴展。
    4. 性能高:通過與其他業界主流的 NIO 框架對比,Netty 的綜合性能最優。
    5. 穩定:Netty 修復了已經發現的所有 NIO 的 bug,讓開發人員可以專注于業務本身。
    6. 社區活躍:Netty 是活躍的開源項目,版本迭代周期短,bug 修復速度快。

    5.Netty 的應用場景有哪些?

    典型的應用有:阿里分布式服務框架 Dubbo,默認使用 Netty 作為基礎通信組件,還有 RocketMQ 也是使用 Netty 作為通訊的基礎。

    6.Netty 高性能表現在哪些方面?

    1. IO 線程模型:同步非阻塞,用最少的資源做更多的事。
    2. 內存零拷貝:盡量減少不必要的內存拷貝,實現了更高效率的傳輸。
    3. 內存池設計:申請的內存可以重用,主要指直接內存。內部實現是用一顆二叉查找樹管理內存分配情況。
    4. 串形化處理讀寫:避免使用鎖帶來的性能開銷。
    5. 高性能序列化協議:支持 protobuf 等高性能序列化協議。

    7.Netty 和 Tomcat 的區別?

    • 作用不同:Tomcat 是 Servlet 容器,可以視為 Web 服務器,而 Netty 是異步事件驅動的網絡應用程序框架和工具用于簡化網絡編程,例如TCP和UDP套接字服務器。
    • 協議不同:Tomcat 是基于 http 協議的 Web 服務器,而 Netty 能通過編程自定義各種協議,因為 Netty 本身自己能編碼/解碼字節流,所有 Netty 可以實現,HTTP 服務器、FTP 服務器、UDP 服務器、RPC 服務器、WebSocket 服務器、Redis 的 Proxy 服務器、MySQL 的 Proxy 服務器等等。

    8.Netty 中有那種重要組件?

    • Channel:Netty 網絡操作抽象類,它除了包括基本的 I/O 操作,如 bind、connect、read、write 等。
    • EventLoop:主要是配合 Channel 處理 I/O 操作,用來處理連接的生命周期中所發生的事情。
    • ChannelFuture:Netty 框架中所有的 I/O 操作都為異步的,因此我們需要 ChannelFuture 的 addListener()注冊一個 ChannelFutureListener 監聽事件,當操作執行成功或者失敗時,監聽就會自動觸發返回結果。
    • ChannelHandler:充當了所有處理入站和出站數據的邏輯容器。ChannelHandler 主要用來處理各種事件,這里的事件很廣泛,比如可以是連接、數據接收、異常、數據轉換等。
    • ChannelPipeline:為 ChannelHandler 鏈提供了容器,當 channel 創建時,就會被自動分配到它專屬的 ChannelPipeline,這個關聯是永久性的。

    9.Netty 發送消息有幾種方式?

    Netty 有兩種發送消息的方式:

    • 直接寫入 Channel 中,消息從 ChannelPipeline 當中尾部開始移動;
    • 寫入和 ChannelHandler 綁定的 ChannelHandlerContext 中,消息從 ChannelPipeline 中的下一個 ChannelHandler 中移動。

    10.默認情況 Netty 起多少線程?何時啟動?

    Netty 默認是 CPU 處理器數的兩倍,bind 完之后啟動。

    11.Netty 支持哪些心跳類型設置?

    readerIdleTime:為讀超時時間(即測試端一定時間內未接受到被測試端消息)。

    writerIdleTime:為寫超時時間(即測試端一定時間內向被測試端發送消息)。

    allIdleTime:所有類型的超時時間。

    12.項目中使用Netty使用的功能

     服務的心跳報警,和監控中心通過netty服務傳輸心跳信息,一但收不到心跳信息,則發送報警email。

    發送異常報告包,監控中心用來統計異常。

     

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

    智能推薦

    電腦空間不夠了?教你一個小秒招快速清理 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 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...

    基于TCP/IP的網絡聊天室用Java來實現

    基于TCP/IP的網絡聊天室實現 開發工具:eclipse 開發環境:jdk1.8 發送端 接收端 工具類 運行截圖...

    19.vue中封裝echarts組件

    19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...

    劍指Offer39-調整數組順序使奇數位于偶數前面

    一開始想著用冒泡排序的方法來做,但是bug還是很多,后來看了評論區答案,發現直接空間換時間是最簡單的,而且和快排的寫法是類似的。...

    【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)

    資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...

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