Netty 快速開始(websocket)

一、網絡IO的基本知識與概念

1. 同步、異步、阻塞、非阻塞概念

怎樣理解阻塞非阻塞與同步異步的區別?
參考URL: https://www.zhihu.com/question/19732473

  • 同步和異步
    同步和異步是針對應用程序和內核的交互而言的,同步指的是用戶進程觸發IO 操作並等待或者輪詢的去查看IO 操作是否就緒,而異步是指用戶進程觸發IO 操作以後便開始做自己的事情,而當IO 操作已經完成的時候會得到IO 完成的通知。

  • 阻塞和非阻塞
    阻塞和非阻塞是針對於進程在訪問數據的時候,根據IO操作的就緒狀態來採取的不同方式,說白了是一種讀取或者寫入操作方法的實現方式,阻塞方式下讀取或者寫入函數將一直等待,而非阻塞方式下,讀取或者寫入方法會立即返回一個狀態值。

總結: 同步異步與阻塞非阻塞的主要區別是針對對象不同

同步異步是針對調用者來說的。同步與異步:針對數據訪問的方式,程序是主動去詢問操作系統數據準備好了麼,還是操作系統在數據準備好的時候通知程序。
 阻塞非阻塞是針對被調用者來說的。阻塞與非阻塞:針對函數(程序)運行的方式,在IO未就緒時,是等待就緒還是直接返回(執行別的操作)。比如: 被調用者收到一個請求後,做完請求任務後纔給出反饋就是阻塞,收到請求直接給出反饋再去做任務就是非阻塞

IO多路複用是同步阻塞模型還是異步阻塞模型?

同步是需要主動等待消息通知,而異步則是被動接收消息通知,通過回調、通知、狀態等方式來被動獲取消息。IO多路複用在阻塞到select階段時,用戶進程是主動等待並調用select函數獲取數據就緒狀態消息,並且其進程狀態爲阻塞。所以,把IO多路複用歸爲同步阻塞模式。

2. IO模型

聊聊Linux 五種IO模型
參考URL: https://www.jianshu.com/p/486b0965c296
聊聊同步、異步、阻塞與非阻塞
參考URL: https://my.oschina.net/xianggao/blog/661085
[推薦,寫的清晰簡單]十年架構經驗工程師,帶你讀懂IO/NIO模型
參考URL: https://baijiahao.baidu.com/s?id=1646205346507623593&wfr=spider&for=pc

  1. 阻塞IO
    如果數據沒有準備就緒,就一直等待,直到數據準備就緒;整個進程會被阻塞。

    典型的阻塞IO模型的例子爲: data = socket.read();如果數據沒有就緒,就會一直阻塞在 read()方法。

  2. 非阻塞IO
    需不斷詢問內核是否已經準備好數據,非阻塞雖然不用等待但是一直佔用CPU。

    典型的非阻塞IO模型一般如下:

    while(true){
    data = socket.read();
    if(data != error){
    // 處理數據 break;
    }
    }
    

    非阻塞IO又一個非常嚴重的問題,在while 循環中需要不斷的去詢問內核數據是否就緒,這樣會導致CPU佔用率非常高,因此一般情況下很少使用while循環這種方式來讀取數據。

  3. 多路複用IO NIO
    多路複用IO模型是目前使用的比較多的模型。Java NIO實際上就是多路複用IO,在多路複用IO模型中會有一個線程不斷去輪詢多個socket的狀態,只有當socket真正有讀寫事件時,才真正調用實際的IO讀寫操作。

    在Java NIO中,是通過 selector.select()去查詢每個通道是否有達到事件,如果沒有事件,則一直阻塞在那裏,因此這種方式會導致用戶線程的阻塞。

    多路複用IO爲何比非阻塞IO模型的效率高?是因爲在非阻塞IO中,不斷的詢問socket狀態時通過用戶線程去進行的,而在多路複用IO中,輪詢每個socket狀態是內核在進行的,這個效率要比用戶線程要高的多。

  4. 信號驅動IO模型
    在信號驅動IO模型中,當用戶線程發起一個IO請求操作,會給對應的socket註冊一個信號函數,然後用戶線程會繼續執行,當內核數據就緒時會發送一個信號給用戶線程,用戶線程接收到信號之後,便在信號函數中調用IO讀寫操作來進行實際的IO請求操作。

  5. 異步IO模型(asynchronous I/O)
    異步IO模型纔是最理想的IO模型,在異步IO模型中,當用戶線程發起read操作之後,立刻就可以開始去做其它的事情。而另一方面,從內核的角度,當它受到一個asynchronout read之後,它會立刻返回,說明read請求已經成功發起來,因此不會對用戶線程產生任何block。然後,內核會等待數據準備完成,然後將數據拷貝到用戶線程,當這一切都完成之後,內核會給用戶線程發送一個信號,告訴它read操作完成了。
    也就是說用戶線程完全不需要實際的整個IO操作是如何進行的,只需要發起一個請求,當接收內核返回的成功信號時表示IO操作已完成,可以直接去使用數據了。也就是說在異步IO模型中,IO操作的兩個階段都不會阻塞用戶線程,這兩個階段都是由內核自動完成,然後發送一個信號告知用戶線程操作已完成。用戶線程中不需要再次調用IO函數進行具體的讀寫。這點是和信號驅動模型有所不同的,在信號驅動模型中,當用戶線程接收到信號表示數據已經就緒,然後需要用戶線程調用IO函數進行實際的讀寫操作;而在異步IO模型中,收到信號表示IO操作已經完成,不需要再在用戶線程中調用IO函數進行實際的讀寫操作。注意,異步IO是需要操作系統的底層支持,在Java 7中,提供了Asynchronous IO。

3. NIO和IO有什麼區別?

NIO即New IO,這個庫是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但實現方式不同,NIO主要用到的是塊,所以NIO的效率要比IO高很多。在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。

在這裏插入圖片描述

  1. 面向流與面向緩衝

    Java IO和NIO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。 Java NIO的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。

    Channel是傳統IO中的Stream(流)的升級版,Stream是單向的、讀寫分離,Channel是雙向的,既可以用來進行讀操作,又可以用來進行寫操作。

  2. 阻塞與非阻塞IO
    Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。

  3. 選擇器(Selectors)
    Selector 一般稱 爲選擇器 ,當然你也可以翻譯爲 多路複用器 。它是Java NIO核心組件中的一個,用於檢查一個或多個NIO Channel(通道)的狀態是否處於可讀、可寫。如此可以實現單線程管理多個channels,也就是可以管理多個網絡鏈接。

    使用Selector的好處在於: 使用更少的線程來就可以來處理通道了, 相比使用多個線程,避免了線程上下文切換帶來的開銷。

4. Java NIO 工作流程

【推薦,寫的非常清晰】Java NIO之Selector(選擇器)
參考URL: https://www.cnblogs.com/snailclimb/p/9086334.html

在這裏插入圖片描述
工作原理核心就2個概念 ,一個是 Selector(選擇器),一個是通道Channel

  • Selector(選擇器)
    其中select 調用可能是阻塞的,也可以是非阻塞的。但是read/write是非阻塞的!

    Selector(選擇器)的使用方法介紹
    參考URL: https://www.cnblogs.com/snailclimb/p/9086334.html

    源碼關鍵字: Selector、SelectableChannel

  • 通道Channel
    主要實現類:

    • FileChannel:用於讀取、寫入、映射和操作文件的通道。
    • DatagramChannel:通過UDP讀寫網絡中的數據通道。
    • SocketChannel:通過tcp讀寫網絡中的數據。
    • ServerSocketChannel:可以監聽新進來的tcp連接,對每一個連接都創建一個SocketChannel。

二、netty

1. 什麼是netty?

官網: https://netty.io/

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
Netty是一個異步事件驅動的網絡應用框架
用於快速開發可維護的高性能協議服務器和客戶端。

在實際的網絡開發中,其實很少使用Java NIO原生的API。主要有以下原因:

  • 原生API使用單線程模型,不能很好利用多核優勢,如果自己去寫多線程結合起來比較麻煩;
  • 原生API是直接使用的IO數據,沒有做任何封裝處理,對數據的編解碼、TCP的粘包和拆包、客戶端斷連、網絡的可靠性和安全性方面沒有做處理;

Netty基於Java NIO,並且做了一些優化,netty提供更高層次的封裝,提供更爲豐富的功能。

2. 什麼是Channel?

自頂向下深入分析Netty(六)–Channel總述
參考URL: https://www.cnblogs.com/549294286/p/10785365.html

在JDK中就有Channel的概念了. 數據的讀寫都要通過Channel進行。Netty對JDK原生的ServerSocketChannel進行了封裝和增強封裝成了NioXXXChannel。 一個是服務端Chanel(NioServerSocketChannel),另一個是客戶端Channel(NioSocketChannel)。

Netty的Channel只是把新的和老的IO進行了更高層的封裝。在 Netty 中, Channel 是一個 Socket 連接的抽象, 它爲用戶提供了關於底層 Socket 狀態(是否是連接還是斷開) 以及對 Socket 的讀寫等操作。 每當 Netty 建立了一個連接後, 都會有一個對應的 Channel 實例。

並且,有父子channel 的概念。 服務器連接監聽的channel ,也叫 parent channel。 對應於每一個 Socket 連接的channel,也叫 child channel。

相對於原生的JdkChannel, Netty的Channel增加了如下的組件

  • id 標識唯一身份信息
  • 可能存在的parent Channel
  • 管道 pepiline
  • 用於數據讀寫的unsafe內部類
  • 關聯上相伴終生的NioEventLoop

Channel通過ChannelPipeline中的多個Handler處理器,Channel使用它處理IO數據。

Channel中的所有Io操作都是異步的,一經調用就馬上返回,於是Netty基於Jdk原生的Future進行了封裝, ChannelFuture, 讀寫操作會返回這個對象,實現自動通知IO操作已完成。

當一個Channel不再使用時,須調用close()或者close(ChannelPromise)方法釋放資源。

總結:在netty中,Channel相當於一個Socket的抽象,它爲用戶提供額關於Socket狀態(連接是否斷開)以及對Socket的讀、寫等操作。每當Netty創建一個連接,都創建一個與其對應的Channel實例。

Netty-Channel架構體系

[推薦-作者寫的很全面]深入理解 Netty-Channel架構體系
參考URL: https://www.cnblogs.com/ZhuChangwu/p/11204057.html
[推薦-作者寫的很全面-源碼分析]自頂向下深入分析Netty(六)–Channel總述
參考URL: https://www.cnblogs.com/549294286/p/10785365.html

如下圖:從頂級接口Channel開始,在接口中定義了一套方法當作規範,緊接着的是來兩個抽象的接口實現類,在這個抽象類中對接口中的方法,進行了部分實現,然後開始根據不同的功能分支,分成服務端的Channel和客戶端的Channel
在這裏插入圖片描述
Channel的分類:
根據服務端和客戶端,Channel可以分成兩類(這兩大類的分支見上圖):

  • 服務端: NioServerSocketChannel
  • 客戶端: NioSocketChannel
  1. AbstractChannel
    AbstractChannel實現Channel接口,比較重要的對象是pipeline和unsafe,它們提供對read,write,bind等操作的具體實現。

  2. AbstractNioChannel
    AbstractNioChannel繼承AbstractChannel,從這個類開始涉及到JDK的socket。

    這裏定義真正的Socket Channel(SelectableChannel),關心的事件,註冊後的key。將Socket設置爲非阻塞,這是所有異步IO的關鍵。這裏重點要關注一下register函數,這個函數是將Channel和事件循環進行關聯的關鍵。每個事件循環都有一個自己的selector,channel實際上是註冊到了相應eventloop的selector中,這也是Nio Socket編程的基礎。
    從這個類中已經可以看到netty的channel是如何和socket 的nio channel關聯的了,以及channel是如何和eventloop關聯的了。

Unsafe

Channel重要的內部接口 unsafe
Netty中,真正幫助Channel完成IO讀寫操作的是它的內部類unsafe。

Unsafe?直譯中文爲不安全,這曾給我帶來極大的困擾。如果你是第一次遇到這種接口,一定會和我感同身受。一個Unsafe對象是不安全的?**這裏說的不安全,是相對於用戶程序員而言的,也就是說,用戶程序員使用Netty進行編程時不會接觸到這個接口和相關類。**爲什麼不會接觸到呢?因爲類似的接口和類是Netty的大量內部實現細節,不會暴露給用戶程序員。

源碼如下, 很多重要的功能在這個接口中定義, 下面列舉的常用的方法

interface Unsafe {
//  把channel註冊進EventLoop
void register(EventLoop eventLoop, ChannelPromise promise);
 
 // todo 給channel綁定一個 adress,
void bind(SocketAddress localAddress, ChannelPromise promise);

// 把channel註冊進Selector
void deregister(ChannelPromise promise);

// 從channel中讀取IO數據
void beginRead();

// 往channe寫入數據
void write(Object msg, ChannelPromise promise);
...
...

接着往下看,下面來到Channel接口的直接實現類,AbstractChannel 他是個抽象類, AbstractChannel重寫部分Channel接口預定義的方法, 它的抽象內部類AbstractUnsafe實現了Channel的內部接口unsafe

總結: channel的io操作是unsafe內部類完成的。服務端從channel,讀取出新連接NioMessageUnsafe,客戶端從channel,讀取出數據NioByteUnsafe。

3. EventLoop 線程與線程組

在Netty 中,每一個 channel 綁定了一個thread 線程。一個 thread 線程,封裝到一個 EventLoop , 多個EventLoop ,組成一個線程組 EventLoopGroup。

EventLoop 這個相當於一個處理線程,是Netty接收請求和處理IO請求的線程。 EventLoopGroup 可以理解爲將多個EventLoop進行分組管理的一個類,是EventLoop的一個組。

三、netty常用類

1. Bootstrap

[推薦]Bootstrap — 客戶端
參考URL: https://www.jianshu.com/p/63b890528766
Netty源碼分析之客戶端啓動流程(Bootstrap)
參考URL: https://www.jianshu.com/p/22bea53023c8

Bootstrap 是 Netty 提供的一個便利的工廠類,可以通過它來完成 Netty 的客戶端Netty 初始化。

1.1 netty client 連接超時設置

bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);

1.2 對 connect 的 future 設置監聽器

Bootstrap bootstrap = new Bootstrap();
...
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000);
bootstrap.connect("hostname", 80).addListener(new ChannelFutureListener() {
    @Override
    public void operationComplete(ChannelFuture future) throws Exception {
        if (!future.isSuccess()) {
            System.err.println("Connect to host error: " + future.cause());
        }
    }
});

1.3 連接服務器後,代碼讓阻塞等待

很多Netty的例子都在末尾加上了這句話:future.channel().closeFuture().sync();
不是沒執行,是主線程到這裏就 wait 子線程退出了,子線程纔是真正監聽和接受請求的。

            // 鏈接服務器
            ChannelFuture f = bootstrap.connect(host, port).sync();
            // 阻塞,等待客戶端鏈路關閉;連接斷開時,這句代碼纔會往下執行
            f.channel().closeFuture().sync();

2. ServerBootstrap

Netty Bootstrap(圖解)|秒懂
參考URL: https://www.cnblogs.com/crazymakercircle/p/9998643.html#eventloop-%E7%BA%BF%E7%A8%8B%E4%B8%8E%E7%BA%BF%E7%A8%8B%E7%BB%8

3. netty中的ByteBuf

netty中的ByteBuf
參考URL: https://www.cnblogs.com/duanxz/p/3724448.html

網絡數據的基本單位總是字節。Java NIO 提供了 ByteBuffer 作爲字節容器,但是這個類使用起來過於複雜,而且也有些繁瑣。

Netty 的 使用的是 ByteBuf,用於替代JDK的 ByteBuffer ,一個強大的實現,既解決了 JDK API 的侷限性, 又爲網絡應用程序的開發者提供了更好的 API。

總結:ByteBuf是Netty整個結構裏面最爲底層的模塊,主要負責把數據從底層I/O 讀到ByteBuf,然後傳遞給應用程序,應用程序處理完成之後再把數據封裝成ByteBuf寫回I/O。

3.1 ByteBuf的創建

Netty中設計了一個專門負責分配ByteBuf的接口:ByteBufAllocator。該接口有一個抽象子類和兩個實現類,分別對應了用來分配池化的ByteBuf和非池化的ByteBuf。

有了Allocator之後,Netty又爲我們提供了兩個工具類:Pooled、Unpooled,分類用來分配池化的和未池化的ByteBuf,進一步簡化了創建ByteBuf的步驟,只需要調用這兩個工具類的靜態方法即可。

以Unpooled類爲例,查看Unpooled的源碼可以發現,他爲我們提供了許多創建ByteBuf的方法,但最終都是以下這幾種,只是參數不一樣而已:

// 在堆上分配一個ByteBuf,並指定初始容量和最大容量
public static ByteBuf buffer(int initialCapacity, int maxCapacity) {
    return ALLOC.heapBuffer(initialCapacity, maxCapacity);
}
// 在堆外分配一個ByteBuf,並指定初始容量和最大容量
public static ByteBuf directBuffer(int initialCapacity, int maxCapacity) {
    return ALLOC.directBuffer(initialCapacity, maxCapacity);
}
// 使用包裝的方式,將一個byte[]包裝成一個ByteBuf後返回
public static ByteBuf wrappedBuffer(byte[] array) {
    if (array.length == 0) {
        return EMPTY_BUFFER;
    }
    return new UnpooledHeapByteBuf(ALLOC, array, array.length);
}
// 返回一個組合ByteBuf,並指定組合的個數
public static CompositeByteBuf compositeBuffer(int maxNumComponents){
    return new CompositeByteBuf(ALLOC, false, maxNumComponents);
}

4. EventLoopGroup

當系統在運行過程中,如果頻繁的進行線程上下文切換,會帶來額外的性能損耗。多線程併發執行某個業務流程,業務開發者還需要時刻對線程安全保持警惕,哪些數據可能會被併發修改,如何保護?這不僅降低了開發效率,也會帶來額外的性能損耗。

爲了解決上述問題,Netty採用了串行化設計理念,從消息的讀取、編碼以及後續 ChannelHandler 的執行,始終都由 IO 線程 EventLoop 負責,這就意外着整個流程不會進行線程上下文的切換,數據也不會面臨被併發修改的風險。

EventLoopGroup 是一組 EventLoop 的抽象,一個 EventLoopGroup 當中會包含一個或多個 EventLoop,EventLoopGroup 提供 next 接口,可以從一組 EventLoop 裏面按照一定規則獲取其中一個 EventLoop 來處理任務。

EventLoop定義了Netty的核心抽象,用於處理連接的生命週期中所發生的事件。

  1. 一個EventLoopGroup包含一個或者多個EventLoop。
  2. 一個EventLoop在它的生命週期內只和一個Thread綁定。
  3. 所有由EventLoop處理的I/O事件都將在它專有的Thread上被處理。
  4. 一個Channel在它的生命週期內只註冊於一個EventLoop。
  5. 一個EventLoop可能會被分配給一個或多個Channel。

在這種設計中,一個給定Channel的I/O操作都是由相同的Thread執行的,實際上消除了對於同步的需要。

總結: EventLoop和Thread是一對一綁定的。一個netty程序啓動時,至少要指定一個EventLoopGroup。

4.1 EventLoop 任務執行者 - NioEventLoop

【Netty】學習NioEventLoop
參考URL: https://www.jianshu.com/p/8a7519c8997d

  • nioEventloop(簡稱loop),其內部持有一個thread對象,而該loop的run方法正是由該線程執行的。
  • loop還持有兩個最重要的對象–selector和queue,前者就是我們的多路複用器後者則是存放我們的任務隊列。

NioEventLoop啓動後主要的工作

1.select() -- 檢測IO事件,輪詢註冊到selector上面的io事件
2.processSelectedKeys() -- 處理io事件
3.runAllTasks() -- 處理外部線程扔到TaskQueue裏面

四、websocket

1. 什麼是websocket

websocket,在單個TCP鏈接上進行全雙工的通訊協議,使客戶端和服務器端能夠實時通信。

  • HTTP是運行在TCP協議傳輸層上的應用協議,而WebSocket是通過HTTP協議協商如何連接,然後獨立運行在TCP協議傳輸層上的應用協議。
  • Websocket是一個持久化的協議,相對於HTTP這種非持久的協議來說。
  • websocket約定了一個通信的規範,通過一個握手的機制,客戶端和服務器之間能建立一個類似tcp的連接,從而方便它們之間的通信。

2. 相關常用類

2.1 SimpleChannelInboundHandler

SimpleChannelInboundHandler 繼承自 ChannelInboundHandlerAdapter

SimpleChannelInboundHandler是抽象類,而ChannelInboundHandlerAdapter是普通類。

SimpleChannelInboundHandler支持泛型的消息處理,而ChannelInboundHandlerAdapter不支持泛型

重寫 channelRead0() 方法

在客戶端,當 channelRead0() 方法完成時,你已經有了傳入消息,並且已經處理完它了。當該方法返回時,SimpleChannelInboundHandler負責釋放指向保存該消息的ByteBuf的內存引用。

3. netty websocket客戶端使用流程

[推薦-寫的比較詳細]WebSocket快速上手
參考URL: https://www.pianshen.com/article/9344104913/
基於Netty的websocket client 和server
參考URL: https://blog.csdn.net/u010939285/article/details/81231221

建議參考作者文章。

總結:實現分成客戶端 和服務器端。

3.1 maven 引入

maven 引入

        <!--netty-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
            <version>4.1.34.Final</version>
        </dependency>

3.2 netty websocket客戶端實現

1)客戶端的核心類就是自定義一個XxxxWebSocketClientHandler 類繼承SimpleChannelInboundHandler。
客戶端中WebSocketClientHandler實現中的this.handshaker,是通過WebSocketClientHandshakerFactory工廠類創建,傳參定義了一些握手細節。

   @Override
   /**
    * 連接建立成功後,發起握手請求
    */
   public void channelActive(ChannelHandlerContext ctx) throws Exception {
       System.out.println("連接成功!" + ctx.name());
       this.handshaker.handshake(ctx.channel());
   }

	WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, EmptyHttpHeaders.INSTANCE, 1280000)

2) websocket客戶端往服務器端寫消息
使用Channel類實例channel的 writeAndFlush方法寫消息給服務器端。

	channel.writeAndFlush(new TextWebSocketFrame(msg));

3.3 過程常見問題總結

Netty handler中 channelInactive方法不斷觸發

Netty:channelInactive、exceptionCaught方法不斷觸發
參考URL: https://www.jianshu.com/p/903498747f47

自定義類繼承SimpleChannelInboundHandler,覆蓋channelInactive方法。

    /**
     * 客戶端與服務器端斷開連接的時候調用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.warn("websocket client disconnected.");
        client.start();
    }

Netty 是先將 Channel 關閉後,再回調 channelInactive 的,也就是說執行到 channelInactive 時,channel早就關閉了! 但是什麼導致channel被關閉呢?

總結: 關閉Channel之前,先清除掉Channel中的各種handler。

注意: 經過測試:channelInactive方法只有在 bootstrap.connect成功建立連接之後,關閉channel時纔會觸發。舉例,bootstrap.connect如果因爲timeout 連接失敗,說明channel都沒有創建成功,這種情況是不會調channelInactive方法的。所以一些重連場景需要考慮這種情況,如果第一次就沒有連接上,就不能依靠該方法嘗試重連。該方法只適合連接成功了,channel關閉後,回調channelInactive方法,你編碼讓重連場景。

java.nio.channels.ClosedChannelException異常

情況一:有可能是你的代碼有問題,也有可能僅是客戶端主動關閉了連接,導致服務端的寫失敗。
情況二:
在開發過程中,進行單元測試時,你的另一個項目在跑,佔用了連接,造成nio讀寫操作失敗,將另一個項目停止解決。

五、參考

Netty 4.0中的那些變化
參考URL: https://blog.csdn.net/alex_xfboy/article/details/93920019

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章