Netty2020最新版介紹:代碼+方案+響應(yīng)協(xié)議服務(wù)+客戶端

本指南對(duì)Netty 進(jìn)行了介紹并指出其意義所在。

  1. 問題

現(xiàn)在,我們使用適合一般用途的應(yīng)用或組件來和彼此通信。例如,我們常常使用一個(gè)HTTP客戶端從遠(yuǎn)程服務(wù)器獲取信息或者通過web services進(jìn)行遠(yuǎn)程方法調(diào)用。

然而,一個(gè)適合普通目的的協(xié)議或其實(shí)現(xiàn)并不具備其規(guī)模上的擴(kuò)展性。例如,我們無法使用一個(gè)普通的HTTP服務(wù)器進(jìn)行大型文件,電郵信息的交互,或者處理金融信息和多人游戲數(shù)據(jù)那種要求準(zhǔn)實(shí)時(shí)消息傳遞的應(yīng)用場(chǎng)景。因此,這些都要求使用一個(gè)適用于特殊目的并經(jīng)過高度優(yōu)化的協(xié)議實(shí)現(xiàn)。例如,你可能想要實(shí)現(xiàn)一個(gè)對(duì)基于AJAX的聊天應(yīng)用,媒體流或大文件傳輸進(jìn)行過特殊優(yōu)化的HTTP服務(wù)器。你甚至可能想去設(shè)計(jì)和實(shí)現(xiàn)一個(gè)全新的,特定于你的需求的通信協(xié)議。

另一種無法避免的場(chǎng)景是你可能不得不使用一種專有的協(xié)議和原有系統(tǒng)交互。在這種情況下,你需要考慮的是如何能夠快速的開發(fā)出這個(gè)協(xié)議的實(shí)現(xiàn)并且同時(shí)還沒有犧牲最終應(yīng)用的性能和穩(wěn)定性。

  1. 方案

Netty 是一個(gè)異步的,事件驅(qū)動(dòng)的網(wǎng)絡(luò)編程框架和工具,使用Netty 可以快速開發(fā)出可維護(hù)的,高性能、高擴(kuò)展能力的協(xié)議服務(wù)及其客戶端應(yīng)用。

也就是說,Netty 是一個(gè)基于NIO的客戶,服務(wù)器端編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶,服務(wù)端應(yīng)用。Netty相當(dāng)簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開發(fā)過程,例如,TCP和UDP的socket服務(wù)開發(fā)。

“快速”和“簡(jiǎn)單”并不意味著會(huì)讓你的最終應(yīng)用產(chǎn)生維護(hù)性或性能上的問題。Netty 是一個(gè)吸收了多種協(xié)議的實(shí)現(xiàn)經(jīng)驗(yàn),這些協(xié)議包括FTP,SMPT,HTTP,各種二進(jìn)制,文本協(xié)議,并經(jīng)過相當(dāng)精心設(shè)計(jì)的項(xiàng)目,最終,Netty 成功地找到了一種方式,在保證易于開發(fā)的同時(shí)還保證了其應(yīng)用的性能,穩(wěn)定性和伸縮性。

一些用戶可能找到了某些同樣聲稱具有這些特性的編程框架,因此你們可能想問Netty 又有什么不一樣的地方。這個(gè)問題的答案是Netty 項(xiàng)目的設(shè)計(jì)哲學(xué)。從創(chuàng)立之初,無論是在API還是在其實(shí)現(xiàn)上Netty 都致力于為你提供最為舒適的使用體驗(yàn)。雖然這并不是顯而易見的,但你終將會(huì)認(rèn)識(shí)到這種設(shè)計(jì)哲學(xué)將令你在閱讀本指南和使用Netty 時(shí)變得更加的輕松和容易。

第一章. 開始

這一章節(jié)將圍繞Netty的核心結(jié)構(gòu)展開,同時(shí)通過一些簡(jiǎn)單的例子可以讓你更快的了解Netty的使用。當(dāng)你讀完本章,你將有能力使用Netty完成客戶端和服務(wù)端的開發(fā)。

如果你更喜歡自上而下式的學(xué)習(xí)方式,你可以首先完成 第二章:架構(gòu)總覽 的學(xué)習(xí),然后再回到這里。

1.1. 開始之前

運(yùn)行本章示例程序的兩個(gè)最低要求是:最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在項(xiàng)目下載頁 下載。下載正確版本的JDK,請(qǐng)到你偏好的JDK站點(diǎn)下載。

這就已經(jīng)足夠了嗎?實(shí)際上你會(huì)發(fā)現(xiàn),這兩個(gè)條件已經(jīng)足夠你完成任何協(xié)議的開發(fā)了。如果不是這樣,請(qǐng)聯(lián)系Netty項(xiàng)目社區(qū) ,讓我們知道還缺少了什么。

最終但不是至少,當(dāng)你想了解本章所介紹的類的更多信息時(shí)請(qǐng)參考API手冊(cè)。為方便你的使用,這篇文檔中所有的類名均連接至在線API手冊(cè)。此外,如果本篇文檔中有任何錯(cuò)誤信息,無論是語法錯(cuò)誤,還是打印排版錯(cuò)誤或者你有更好的建議,請(qǐng)不要顧慮,立即聯(lián)系Netty項(xiàng)目社區(qū) 。

1.2. 拋棄協(xié)議服務(wù)

在這個(gè)世界上最簡(jiǎn)化的協(xié)議不是“Hello,world!”而是拋棄協(xié)議 。這是一種丟棄接收到的任何數(shù)據(jù)并不做任何回應(yīng)的協(xié)議。

實(shí)現(xiàn)拋棄協(xié)議(DISCARD protocol),你僅需要忽略接受到的任何數(shù)據(jù)即可。讓我們直接從處理器(handler)實(shí)現(xiàn)開始,這個(gè)處理器處理Netty的所有I/O事件。

package org.jboss.netty.example.discard;  
@ChannelPipelineCoverage("all")1 
public class DiscardServerHandler extends SimpleChannelHandler {2 
 
    @Override 
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3 
    }  
 
 @Override 
 public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4 
     e.getCause().printStackTrace();  
      
     Channel ch = e.getChannel();  
     ch.close();  
 }  
 
 
    @Override 
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4 
        e.getCause().printStackTrace();  
         
        Channel ch = e.getChannel();  
        ch.close();  
    }  
} 

代碼說明

1)ChannelPipelineCoverage注解了一種處理器類型,這個(gè)注解標(biāo)示了一個(gè)處理器是否可被多個(gè)Channel通道共享(同時(shí)關(guān)聯(lián)著ChannelPipeline)。DiscardServerHandler沒有處理任何有狀態(tài)的信息,因此這里的注解是“all”。

2)DiscardServerHandler繼承了SimpleChannelHandler,這也是一個(gè)ChannelHandler 的實(shí)現(xiàn)。SimpleChannelHandler提供了多種你可以重寫的事件處理方法。目前直接繼承SimpleChannelHandler已經(jīng)足夠了,并不需要你完成一個(gè)自己的處理器接口。

3)我們這里重寫了messageReceived事件處理方法。這個(gè)方法由一個(gè)接收了客戶端傳送數(shù)據(jù)的MessageEvent事件調(diào)用。在這個(gè)例子中,我們忽略接收到的任何數(shù)據(jù),并以此來實(shí)現(xiàn)一個(gè)拋棄協(xié)議(DISCARD protocol)。

4)exceptionCaught 事件處理方法由一個(gè)ExceptionEvent異常事件調(diào)用,這個(gè)異常事件起因于Netty的I/O異?;蛞粋€(gè)處理器實(shí)現(xiàn)的內(nèi)部異常。多數(shù)情況下,捕捉到的異常應(yīng)當(dāng)被記錄下來,并在這個(gè)方法中關(guān)閉這個(gè)channel通道。當(dāng)然處理這種異常情況的方法實(shí)現(xiàn)可能因你的實(shí)際需求而有所不同,例如,在關(guān)閉這個(gè)連接之前你可能會(huì)發(fā)送一個(gè)包含了錯(cuò)誤碼的響應(yīng)消息。

目前進(jìn)展不錯(cuò),我們已經(jīng)完成了拋棄協(xié)議服務(wù)器的一半開發(fā)工作。下面要做的是完成一個(gè)可以啟動(dòng)這個(gè)包含DiscardServerHandler處理器服務(wù)的主方法

package org.jboss.netty.example.discard;  
 
import java.net.InetSocketAddress;  
import java.util.concurrent.Executors;  
 
public class DiscardServer {  
 
    public static void main(String[] args) throws Exception {  
        ChannelFactory factory =  
            new NioServerSocketChannelFactory (  
                    Executors.newCachedThreadPool(),  
                    Executors.newCachedThreadPool());  
 
        ServerBootstrap bootstrap = new ServerBootstrap (factory);  
 
        DiscardServerHandler handler = new DiscardServerHandler();  
        ChannelPipeline pipeline = bootstrap.getPipeline();  
        pipeline.addLast("handler", handler);  
 
        bootstrap.setOption("child.tcpNoDelay", true);  
        bootstrap.setOption("child.keepAlive", true);  
 
        bootstrap.bind(new InetSocketAddress(8080));  
    }  
} 
 代碼說明

1)ChannelFactory 是一個(gè)創(chuàng)建和管理Channel通道及其相關(guān)資源的工廠接口,它處理所有的I/O請(qǐng)求并產(chǎn)生相應(yīng)的I/O ChannelEvent通道事件。Netty 提供了多種 ChannelFactory 實(shí)現(xiàn)。這里我們需要實(shí)現(xiàn)一個(gè)服務(wù)端的例子,因此我們使用NioServerSocketChannelFactory實(shí)現(xiàn)。另一件需要注意的事情是這個(gè)工廠并自己不負(fù)責(zé)創(chuàng)建I/O線程。你應(yīng)當(dāng)在其構(gòu)造器中指定該工廠使用的線程池,這樣做的好處是你獲得了更高的控制力來管理你的應(yīng)用環(huán)境中使用的線程,例如一個(gè)包含了安全管理的應(yīng)用服務(wù)。

2)ServerBootstrap 是一個(gè)設(shè)置服務(wù)的幫助類。你甚至可以在這個(gè)服務(wù)中直接設(shè)置一個(gè)Channel通道。然而請(qǐng)注意,這是一個(gè)繁瑣的過程,大多數(shù)情況下并不需要這樣做。

3)這里,我們將DiscardServerHandler處理器添加至默認(rèn)的ChannelPipeline通道。任何時(shí)候當(dāng)服務(wù)器接收到一個(gè)新的連接,一個(gè)新的ChannelPipeline管道對(duì)象將被創(chuàng)建,并且所有在這里添加的ChannelHandler對(duì)象將被添加至這個(gè)新的 ChannelPipeline管道對(duì)象。這很像是一種淺拷貝操作(a shallow-copy operation);所有的Channel通道以及其對(duì)應(yīng)的ChannelPipeline實(shí)例將分享相同的DiscardServerHandler 實(shí)例。

4)你也可以設(shè)置我們?cè)谶@里指定的這個(gè)通道實(shí)現(xiàn)的配置參數(shù)。我們正在寫的是一個(gè)TCP/IP服務(wù),因此我們運(yùn)行設(shè)定一些socket選項(xiàng),例如 tcpNoDelay和keepAlive。請(qǐng)注意我們?cè)谂渲眠x項(xiàng)里添加的"child."前綴。這意味著這個(gè)配置項(xiàng)僅適用于我們接收到的通道實(shí)例,而不是ServerSocketChannel實(shí)例。因此,你可以這樣給一個(gè)ServerSocketChannel設(shè)定參數(shù):bootstrap.setOption("reuseAddress", true);

5)我們繼續(xù)。剩下要做的是綁定這個(gè)服務(wù)使用的端口并且啟動(dòng)這個(gè)服務(wù)。這里,我們綁定本機(jī)所有網(wǎng)卡(NICs,network interface cards)上的8080端口。當(dāng)然,你現(xiàn)在也可以對(duì)應(yīng)不同的綁定地址多次調(diào)用綁定操作。

大功告成!現(xiàn)在你已經(jīng)完成你的第一個(gè)基于Netty的服務(wù)端程序。

1.3. 查看接收到的數(shù)據(jù)

現(xiàn)在你已經(jīng)完成了你的第一個(gè)服務(wù)端程序,我們需要測(cè)試它是否可以真正的工作。最簡(jiǎn)單的方法是使用telnet 命令。例如,你可以在命令行中輸入“telnet localhost 8080 ”或其他類型參數(shù)。

然而,我們可以認(rèn)為服務(wù)器在正常工作嗎?由于這是一個(gè)丟球協(xié)議服務(wù),所以實(shí)際上我們無法真正的知道。你最終將收不到任何回應(yīng)。為了證明它在真正的工作,讓我們修改代碼打印其接收到的數(shù)據(jù)。我們已經(jīng)知道當(dāng)完成數(shù)據(jù)的接收后將產(chǎn)生MessageEvent消息事件,并且也會(huì)觸發(fā)messageReceived處理方法。所以讓我在DiscardServerHandler處理器的messageReceived方法內(nèi)增加一些代碼。

@Override 
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  
    ChannelBuffer  buf = (ChannelBuffer) e.getMessage();  
    while(buf.readable()) {  
        System.out.println((char) buf.readByte());  
    }  
} 
 代碼說明
  1. 基本上我們可以假定在socket的傳輸中消息類型總是ChannelBuffer。ChannelBuffer是Netty的一個(gè)基本數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)了一個(gè)字節(jié)序列。ChannelBuffer類似于NIO的ByteBuffer,但是前者卻更加的靈活和易于使用。例如,Netty允許你創(chuàng)建一個(gè)由多個(gè)ChannelBuffer構(gòu)建的復(fù)合ChannelBuffer類型,這樣就可以減少不必要的內(nèi)存拷貝次數(shù)。

  2. 雖然ChannelBuffer有些類似于NIO的ByteBuffer,但強(qiáng)烈建議你參考Netty的API手冊(cè)。學(xué)會(huì)如何正確的使用ChannelBuffer是無障礙使用Netty的關(guān)鍵一步。

如果你再次運(yùn)行telnet命令,你將會(huì)看到你所接收到的數(shù)據(jù)。拋棄協(xié)議服務(wù)的所有源代碼均存放在在分發(fā)版的org.jboss.netty.example.discard包下。

1.4. 響應(yīng)協(xié)議服務(wù)

目前,我們雖然使用了數(shù)據(jù),但最終卻未作任何回應(yīng)。然而一般情況下,一個(gè)服務(wù)都需要回應(yīng)一個(gè)請(qǐng)求。讓我們實(shí)現(xiàn)ECHO協(xié)議 來學(xué)習(xí)如何完成一個(gè)客戶請(qǐng)求的回應(yīng)消息,ECHO協(xié)議規(guī)定要返回任何接收到的數(shù)據(jù)。

與我們上一節(jié)實(shí)現(xiàn)的拋棄協(xié)議服務(wù)唯一不同的地方是,這里需要返回所有的接收數(shù)據(jù)而不是僅僅打印在控制臺(tái)之上。因此我們?cè)俅涡薷膍essageReceived方法就足夠了。

@Override 
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  
    Channel  ch = e.getChannel();  
    ch.write(e.getMessage());  
} 

代碼說明

  1. 一個(gè)ChannelEvent通道事件對(duì)象自身存有一個(gè)和其關(guān)聯(lián)的Channel對(duì)象引用。這個(gè)返回的Channel通道對(duì)象代表了這個(gè)接收 MessageEvent消息事件的連接(connection)。因此,我們可以通過調(diào)用這個(gè)Channel通道對(duì)象的write方法向遠(yuǎn)程節(jié)點(diǎn)寫入返回?cái)?shù)據(jù)。

現(xiàn)在如果你再次運(yùn)行telnet命令,你將會(huì)看到服務(wù)器返回的你所發(fā)送的任何數(shù)據(jù)。

相應(yīng)服務(wù)的所有源代碼存放在分發(fā)版的org.jboss.netty.example.echo包下。

1.5. 時(shí)間協(xié)議服務(wù)

這一節(jié)需要實(shí)現(xiàn)的協(xié)議是TIME協(xié)議 。這是一個(gè)與先前所介紹的不同的例子。這個(gè)例子里,服務(wù)端返回一個(gè)32位的整數(shù)消息,我們不接受請(qǐng)求中包含的任何數(shù)據(jù)并且當(dāng)消息返回完畢后立即關(guān)閉連接。通過這個(gè)例子你將學(xué)會(huì)如何構(gòu)建和發(fā)送消息,以及當(dāng)完成處理后如何主動(dòng)關(guān)閉連接。

因?yàn)槲覀儠?huì)忽略接收到的任何數(shù)據(jù)而只是返回消息,這應(yīng)當(dāng)在建立連接后就立即開始。因此這次我們不再使用messageReceived方法,取而代之的是使用channelConnected方法。下面是具體的實(shí)現(xiàn):

package org.jboss.netty.example.time;  
 
@ChannelPipelineCoverage("all")  
public class TimeServerHandler extends SimpleChannelHandler {  
 
    @Override 
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {  
        Channel ch = e.getChannel();  
         
        ChannelBuffer time = ChannelBuffers.buffer(4);  
        time.writeInt(System.currentTimeMillis() / 1000);  
         
        ChannelFuture f = ch.write(time);  
         
        f.addListener(new ChannelFutureListener() {  
            public void operationComplete(ChannelFuture future) {  
                Channel ch = future.getChannel();  
                ch.close();  
            }  
        });  
    }  
 
    @Override 
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  
        e.getCause().printStackTrace();  
        e.getChannel().close();  
    }  
} 

代碼說明

  1. 正如我們解釋過的,channelConnected方法將在一個(gè)連接建立后立即觸發(fā)。因此讓我們?cè)谶@個(gè)方法里完成一個(gè)代表當(dāng)前時(shí)間(秒)的32位整數(shù)消息的構(gòu)建工作。

  2. 為了發(fā)送一個(gè)消息,我們需要分配一個(gè)包含了這個(gè)消息的buffer緩沖。因?yàn)槲覀儗⒁獙懭胍粋€(gè)32位的整數(shù),因此我們需要一個(gè)4字節(jié)的 ChannelBuffer。ChannelBuffers是一個(gè)可以創(chuàng)建buffer緩沖的幫助類。除了這個(gè)buffer方法,ChannelBuffers還提供了很多和ChannelBuffer相關(guān)的實(shí)用方法。更多信息請(qǐng)參考API手冊(cè)。

另外,一個(gè)很不錯(cuò)的方法是使用靜態(tài)的導(dǎo)入方式:import static org.jboss.netty.buffer.ChannelBuffers.*;...ChannelBuffer dynamicBuf = dynamicBuffer(256);ChannelBuffer ordinaryBuf = buffer(1024);

  1. 像通常一樣,我們需要自己構(gòu)造消息。

但是打住,flip在哪?過去我們?cè)谑褂肗IO發(fā)送消息時(shí)不是常常需要調(diào)用 ByteBuffer.flip()方法嗎?實(shí)際上ChannelBuffer之所以不需要這個(gè)方法是因?yàn)?ChannelBuffer有兩個(gè)指針;一個(gè)對(duì)應(yīng)讀操作,一個(gè)對(duì)應(yīng)寫操作。當(dāng)你向一個(gè) ChannelBuffer寫入數(shù)據(jù)的時(shí)候?qū)懼羔樀乃饕当銜?huì)增加,但與此同時(shí)讀指針的索引值不會(huì)有任何變化。讀寫指針的索引值分別代表了這個(gè)消息的開始、結(jié)束位置。

與之相應(yīng)的是,NIO的buffer緩沖沒有為我們提供如此簡(jiǎn)潔的一種方法,除非你調(diào)用它的flip方法。因此,當(dāng)你忘記調(diào)用flip方法而引起發(fā)送錯(cuò)誤時(shí),你便會(huì)陷入困境。這樣的錯(cuò)誤不會(huì)再Netty中發(fā)生,因?yàn)槲覀儗?duì)應(yīng)不同的操作類型有不同的指針。你會(huì)發(fā)現(xiàn)就像你已習(xí)慣的這樣過程變得更加容易— 一種沒有flippling的體驗(yàn)!

另一點(diǎn)需要注意的是這個(gè)寫方法返回了一個(gè)ChannelFuture對(duì)象。一個(gè)ChannelFuture 對(duì)象代表了一個(gè)尚未發(fā)生的I/O操作。這意味著,任何已請(qǐng)求的操作都可能是沒有被立即執(zhí)行的,因?yàn)樵贜etty內(nèi)部所有的操作都是異步的。例如,下面的代碼可能會(huì)關(guān)閉一 個(gè)連接,這個(gè)操作甚至?xí)l(fā)生在消息發(fā)送之前:

Channel ch = ...;ch.write(message);ch.close();

因此,你需要這個(gè)write方法返回的ChannelFuture對(duì)象,close方法需要等待寫操作異步完成之后的ChannelFuture通知/監(jiān)聽觸發(fā)。需要注意的是,關(guān)閉方法仍舊不是立即關(guān)閉一個(gè)連接,它同樣也是返回了一個(gè)ChannelFuture對(duì)象。

  1. 在寫操作完成之后我們又如何得到通知?這個(gè)只需要簡(jiǎn)單的為這個(gè)返回的ChannelFuture對(duì)象增加一個(gè)ChannelFutureListener 即可。在這里我們創(chuàng)建了一個(gè)匿名ChannelFutureListener對(duì)象,在這個(gè)ChannelFutureListener對(duì)象內(nèi)部我們處理了異步操作完成之后的關(guān)閉操作。

另外,你也可以通過使用一個(gè)預(yù)定義的監(jiān)聽類來簡(jiǎn)化代碼。f.addListener(ChannelFutureListener.CLOSE);1.6. 時(shí)間協(xié)議服務(wù)客戶端

不同于DISCARD和ECHO協(xié)議服務(wù),我們需要一個(gè)時(shí)間協(xié)議服務(wù)的客戶端,因?yàn)槿藗儫o法直接將一個(gè)32位的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換一個(gè)日歷時(shí)間。在這一節(jié)我們將學(xué)習(xí)如何確保服務(wù)器端工作正常,以及如何使用Netty完成客戶端的開發(fā)。

使用Netty開發(fā)服務(wù)器端和客戶端代碼最大的不同是要求使用不同的Bootstrap及ChannelFactory。請(qǐng)參照以下的代碼:

package org.jboss.netty.example.time;  
 
import java.net.InetSocketAddress;  
import java.util.concurrent.Executors;  
 
public class TimeClient {  
 
    public static void main(String[] args) throws Exception {  
        String host = args[0];  
        int port = Integer.parseInt(args[1]);  
 
        ChannelFactory factory =  
            new NioClientSocketChannelFactory (  
                    Executors.newCachedThreadPool(),  
                    Executors.newCachedThreadPool());  
 
        ClientBootstrap bootstrap = new ClientBootstrap (factory);  
 
        TimeClientHandler handler = new TimeClientHandler();  
        bootstrap.getPipeline().addLast("handler", handler);  
         
        bootstrap.setOption("tcpNoDelay" , true);  
        bootstrap.setOption("keepAlive", true);  
 
        bootstrap.connect (new InetSocketAddress(host, port));  
    }  
} 

代碼說明

  1. 使用NioClientSocketChannelFactory而不是NioServerSocketChannelFactory來創(chuàng)建客戶端的Channel通道對(duì)象。

  2. 客戶端的ClientBootstrap對(duì)應(yīng)ServerBootstrap。

  3. 請(qǐng)注意,這里不存在使用“child.”前綴的配置項(xiàng),客戶端的SocketChannel實(shí)例不存在父級(jí)Channel對(duì)象。

  4. 我們應(yīng)當(dāng)調(diào)用connect連接方法,而不是之前的bind綁定方法。

正如你所看到的,這與服務(wù)端的啟動(dòng)過程是完全不一樣的。ChannelHandler又該如何實(shí)現(xiàn)呢?它應(yīng)當(dāng)負(fù)責(zé)接收一個(gè)32位的整數(shù),將其轉(zhuǎn)換為可讀的格式后,打印輸出時(shí)間,并關(guān)閉這個(gè)連接。
package org.jboss.netty.example.time;


import java.util.Date;  



@ChannelPipelineCoverage("all")  

public class TimeClientHandler extends SimpleChannelHandler {  



   @Override 

   public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  

       ChannelBuffer buf = (ChannelBuffer) e.getMessage();  

       long currentTimeMillis = buf.readInt() * 1000L;  

       System.out.println(new Date(currentTimeMillis));  

       e.getChannel().close();  

   }  



   @Override 

   public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  

       e.getCause().printStackTrace();  

       e.getChannel().close();  

   }  

這看起來很是簡(jiǎn)單,與服務(wù)端的實(shí)現(xiàn)也并沒有什么不同。然而,這個(gè)處理器卻時(shí)常會(huì)因?yàn)閽伋鯥ndexOutOfBoundsException異常而拒絕工作。我們將在下一節(jié)討論這個(gè)問題產(chǎn)生的原因。

1.7. 流數(shù)據(jù)的傳輸處理

1.7.1. Socket Buffer的缺陷

對(duì)于例如TCP/IP這種基于流的傳輸協(xié)議實(shí)現(xiàn),接收到的數(shù)據(jù)會(huì)被存儲(chǔ)在socket的接受緩沖區(qū)內(nèi)。不幸的是,這種基于流的傳輸緩沖區(qū)并不是一個(gè)包隊(duì)列,而是一個(gè)字節(jié)隊(duì)列。這意味著,即使你以兩個(gè)數(shù)據(jù)包的形式發(fā)送了兩條消息,操作系統(tǒng)卻不會(huì)把它們看成是兩條消息,而僅僅是一個(gè)批次的字節(jié)序列。因此,在這種情況下我們就無法保證收到的數(shù)據(jù)恰好就是遠(yuǎn)程節(jié)點(diǎn)所發(fā)送的數(shù)據(jù)。例如,讓我們假設(shè)一個(gè)操作系統(tǒng)的TCP/IP堆棧收到了三個(gè)數(shù)據(jù)包:

+-----+-----+-----+| ABC | DEF | GHI |+-----+-----+-----+

由于這種流傳輸協(xié)議的普遍性質(zhì),在你的應(yīng)用中有較高的可能會(huì)把這些數(shù)據(jù)讀取為另外一種形式:

+----+-------+---+---+| AB | CDEFG | H | I |+----+-------+---+---+

因此對(duì)于數(shù)據(jù)的接收方,不管是服務(wù)端還是客戶端,應(yīng)當(dāng)重構(gòu)這些接收到的數(shù)據(jù),讓其變成一種可讓你的應(yīng)用邏輯易于理解的更有意義的數(shù)據(jù)結(jié)構(gòu)。在上面所述的這個(gè)例子中,接收到的數(shù)據(jù)應(yīng)當(dāng)重構(gòu)為下面的形式:

+-----+-----+-----+| ABC | DEF | GHI |+-----+-----+-----+

1.7.2. 第一種方案現(xiàn)在讓我們回到時(shí)間協(xié)議服務(wù)客戶端的例子中。我們?cè)谶@里遇到了同樣的問題。一個(gè)32位的整數(shù)是一個(gè)非常小的數(shù)據(jù)量,因此它常常不會(huì)被切分在不同的數(shù)據(jù)段內(nèi)。然而,問題是它確實(shí)可以被切分在不同的數(shù)據(jù)段內(nèi),并且這種可能性隨著流量的增加而提高。

最簡(jiǎn)單的方案是在程序內(nèi)部創(chuàng)建一個(gè)可準(zhǔn)確接收4字節(jié)數(shù)據(jù)的累積性緩沖。下面的代碼是修復(fù)了這個(gè)問題后的TimeClientHandler實(shí)現(xiàn)。

package org.jboss.netty.example.time;  
 
import static org.jboss.netty.buffer.ChannelBuffers.*;  
 
import java.util.Date;  
 
@ChannelPipelineCoverage("one")  
public class TimeClientHandler extends SimpleChannelHandler {  
 
    private final ChannelBuffer buf = dynamicBuffer();  
 
    @Override 
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  
        ChannelBuffer m = (ChannelBuffer) e.getMessage();  
        buf.writeBytes(m);  
         
        if (buf.readableBytes() >= 4) {  
            long currentTimeMillis = buf.readInt() * 1000L;  
            System.out.println(new Date(currentTimeMillis));  
            e.getChannel().close();  
        }  
    }  
 
    @Override 
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {  
        e.getCause().printStackTrace();  
        e.getChannel().close();  
    }  
} 

代碼說明

  1. 這一次我們使用“one”做為ChannelPipelineCoverage的注解值。這是由于這個(gè)修改后的TimeClientHandler不在不在內(nèi)部保持一個(gè)buffer緩沖,因此這個(gè)TimeClientHandler實(shí)例不可以再被多個(gè)Channel通道或ChannelPipeline共享。否則這個(gè)內(nèi)部的buffer緩沖將無法緩沖正確的數(shù)據(jù)內(nèi)容。

  2. 動(dòng)態(tài)的buffer緩沖也是ChannelBuffer的一種實(shí)現(xiàn),其擁有動(dòng)態(tài)增加緩沖容量的能力。當(dāng)你無法預(yù)估消息的數(shù)據(jù)長度時(shí),動(dòng)態(tài)的buffer緩沖是一種很有用的緩沖結(jié)構(gòu)。

  3. 首先,所有的數(shù)據(jù)將會(huì)被累積的緩沖至buf容器。

  4. 之后,這個(gè)處理器將會(huì)檢查是否收到了足夠的數(shù)據(jù)然后再進(jìn)行真實(shí)的業(yè)務(wù)邏輯處理,在這個(gè)例子中需要接收4字節(jié)數(shù)據(jù)。否則,Netty將重復(fù)調(diào)用messageReceived方法,直至4字節(jié)數(shù)據(jù)接收完成。

這里還有另一個(gè)地方需要進(jìn)行修改。你是否還記得我們把TimeClientHandler實(shí)例添加到了這個(gè)ClientBootstrap實(shí)例的默認(rèn)ChannelPipeline管道里?這意味著同一個(gè)TimeClientHandler實(shí)例將被多個(gè)Channel通道共享,因此接受的數(shù)據(jù)也將受到破壞。為了給每一個(gè)Channel通道創(chuàng)建一個(gè)新的TimeClientHandler實(shí)例,我們需要實(shí)現(xiàn)一個(gè) ChannelPipelineFactory管道工廠:

package org.jboss.netty.example.time;  
 
public class TimeClientPipelineFactory implements ChannelPipelineFactory {  
 
    public ChannelPipeline getPipeline() {  
        ChannelPipeline pipeline = Channels.pipeline();  
        pipeline.addLast("handler", new TimeClientHandler());  
        return pipeline;  
    }  
}

現(xiàn)在,我們需要把TimeClient下面的代碼片段:

TimeClientHandler handler = new TimeClientHandler();  
bootstrap.getPipeline().addLast("handler", handler); 

替換為:

 bootstrap.setPipelineFactory(new TimeClientPipelineFactory()); 

雖然這看上去有些復(fù)雜,并且由于在TimeClient內(nèi)部我們只創(chuàng)建了一個(gè)連接(connection),因此我們?cè)谶@里確實(shí)沒必要引入TimeClientPipelineFactory實(shí)例。

然而,當(dāng)你的應(yīng)用變得越來越復(fù)雜,你就總會(huì)需要實(shí)現(xiàn)自己的ChannelPipelineFactory,這個(gè)管道工廠將會(huì)令你的管道配置變得更加具有靈活性。

1.7.3. 第二種方案

雖然第二種方案解決了時(shí)間協(xié)議客戶端遇到的問題,但是這個(gè)修改后的處理器實(shí)現(xiàn)看上去卻不再那么簡(jiǎn)潔。設(shè)想一種更為復(fù)雜的,由多個(gè)可變長度字段組成的協(xié)議。你的ChannelHandler實(shí)現(xiàn)將變得越來越難以維護(hù)。

正如你已注意到的,你可以為一個(gè)ChannelPipeline添加多個(gè)ChannelHandler,因此,為了減小應(yīng)用的復(fù)雜性,你可以把這個(gè)臃腫的ChannelHandler切分為多個(gè)獨(dú)立的模塊單元。例如,你可以把TimeClientHandler切分為兩個(gè)獨(dú)立的處理器:

TimeDecoder,解決數(shù)據(jù)分段的問題。

TimeClientHandler,原始版本的實(shí)現(xiàn)。

幸運(yùn)的是,Netty提供了一個(gè)可擴(kuò)展的類,這個(gè)類可以直接拿過來使用幫你完成TimeDecoder的開發(fā):

package org.jboss.netty.example.time; 
public class TimeDecoder extends FrameDecoder {  
 
    @Override 
    protected Object decode(  
            ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)  {  
             
        if (buffer.readableBytes() < 4) {  
            return null;   
        }  
         
        return buffer.readBytes(4);  
    }  
} 

代碼說明

  1. 這里不再需要使用ChannelPipelineCoverage的注解,因?yàn)镕rameDecoder總是被注解為“one”。

  2. 當(dāng)接收到新的數(shù)據(jù)后,F(xiàn)rameDecoder會(huì)調(diào)用decode方法,同時(shí)傳入一個(gè)FrameDecoder內(nèi)部持有的累積型buffer緩沖。

  3. 如果decode返回null值,這意味著還沒有接收到足夠的數(shù)據(jù)。當(dāng)有足夠數(shù)量的數(shù)據(jù)后FrameDecoder會(huì)再次調(diào)用decode方法。

  4. 如果decode方法返回一個(gè)非空值,這意味著decode方法已經(jīng)成功完成一條信息的解碼。FrameDecoder將丟棄這個(gè)內(nèi)部的累計(jì)型緩沖。請(qǐng)注意你不需要對(duì)多條消息進(jìn)行解碼,F(xiàn)rameDecoder將保持對(duì)decode方法的調(diào)用,直到decode方法返回非空對(duì)象。

如果你是一個(gè)勇于嘗試的人,你或許應(yīng)當(dāng)使用ReplayingDecoder,ReplayingDecoder更加簡(jiǎn)化了解碼的過程。為此你需要查看API手冊(cè)獲得更多的幫助信息。

package org.jboss.netty.example.time;  
 
public class TimeDecoder extends ReplayingDecoder<VoidEnum> {  
 
    @Override 
    protected Object decode(  
            ChannelHandlerContext ctx, Channel channel,  
            ChannelBuffer buffer, VoidEnum state) {  
             
        return buffer.readBytes(4);  
    }  
} 

此外,Netty還為你提供了一些可以直接使用的decoder實(shí)現(xiàn),這些decoder實(shí)現(xiàn)不僅可以讓你非常容易的實(shí)現(xiàn)大多數(shù)協(xié)議,并且還會(huì)幫你避免某些臃腫、難以維護(hù)的處理器實(shí)現(xiàn)。請(qǐng)參考下面的代碼包獲得更加詳細(xì)的實(shí)例:

org.jboss.netty.example.factorial for a binary protocol, and

 org.jboss.netty.example.telnet for a text line-based protocol

1.8. 使用POJO代替ChannelBuffer

目前為止所有的實(shí)例程序都是使用ChannelBuffer做為協(xié)議消息的原始數(shù)據(jù)結(jié)構(gòu)。在這一節(jié),我們將改進(jìn)時(shí)間協(xié)議服務(wù)的客戶/服務(wù)端實(shí)現(xiàn),使用POJO 而不是ChannelBuffer做為協(xié)議消息的原始數(shù)據(jù)結(jié)構(gòu)。

在你的ChannelHandler實(shí)現(xiàn)中使用POJO的優(yōu)勢(shì)是很明顯的;從你的ChannelHandler實(shí)現(xiàn)中分離從 ChannelBuffer獲取數(shù)據(jù)的代碼,將有助于提高你的ChannelHandler實(shí)現(xiàn)的可維護(hù)性和可重用性。在時(shí)間協(xié)議服務(wù)的客戶/服務(wù)端代碼中,直接使用ChannelBuffer讀取一個(gè)32位的整數(shù)并不是一個(gè)主要的問題。然而,你會(huì)發(fā)現(xiàn),當(dāng)你試圖實(shí)現(xiàn)一個(gè)真實(shí)的協(xié)議的時(shí)候,這種代碼上的分離是很有必要的。

首先,讓我們定義一個(gè)稱之為UnixTime的新類型。

package org.jboss.netty.example.time;  
 
import java.util.Date;  
 
public class UnixTime {  
    private final int value;  
     
    public UnixTime(int value) {  
        this.value = value;  
    }  
     
    public int getValue() {  
        return value;  
    }  
     
    @Override 
    public String toString() {  
        return new Date(value * 1000L).toString();  
    }  
} 

現(xiàn)在讓我們重新修改TimeDecoder實(shí)現(xiàn),讓其返回一個(gè)UnixTime,而不是一個(gè)ChannelBuffer。

@Override 
protected Object decode(  
        ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {  
    if (buffer.readableBytes() < 4) {  
        return null;  
    }  
 
    return new UnixTime(buffer.readInt());  
}

FrameDecoder和ReplayingDecoder允許你返回一個(gè)任何類型的對(duì)象。如果它們僅允許返回一個(gè)ChannelBuffer類型的對(duì)象,我們將不得不插入另一個(gè)可以從ChannelBuffer對(duì)象轉(zhuǎn)換 為UnixTime對(duì)象的ChannelHandler實(shí)現(xiàn)。

有了這個(gè)修改后的decoder實(shí)現(xiàn),這個(gè)TimeClientHandler便不會(huì)再依賴ChannelBuffer。

@Override 
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {  
    UnixTime m = (UnixTime) e.getMessage();  
    System.out.println(m);  
    e.getChannel().close();  
}

更加簡(jiǎn)單優(yōu)雅了,不是嗎?同樣的技巧也可以應(yīng)用在服務(wù)端,讓我們現(xiàn)在更新TimeServerHandler的實(shí)現(xiàn):

@Override 
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {  
    UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);  
    ChannelFuture f = e.getChannel().write(time);  
    f.addListener(ChannelFutureListener.CLOSE);  
} 

現(xiàn)在剩下的唯一需要修改的部分是這個(gè)ChannelHandler實(shí)現(xiàn),這個(gè)ChannelHandler實(shí)現(xiàn)需要把一個(gè)UnixTime對(duì)象重新轉(zhuǎn)換為一個(gè)ChannelBuffer。但這卻已是相當(dāng)簡(jiǎn)單了,因?yàn)楫?dāng)你對(duì)消息進(jìn)行編碼的時(shí)候你不再需要處理數(shù)據(jù)包的拆分及組裝。

package org.jboss.netty.example.time;  
     
import static org.jboss.netty.buffer.ChannelBuffers.*;  
 
@ChannelPipelineCoverage("all")  
public class TimeEncoder extends SimpleChannelHandler {  
 
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent  e) {  
        UnixTime time = (UnixTime) e.getMessage();  
         
        ChannelBuffer buf = buffer(4);  
        buf.writeInt(time.getValue());  
         
        Channels.write(ctx, e.getFuture(), buf);  
    }  
} 
 代碼說明
  1. 因?yàn)檫@個(gè)encoder是無狀態(tài)的,所以其使用的ChannelPipelineCoverage注解值是“all”。實(shí)際上,大多數(shù)encoder實(shí)現(xiàn)都是無狀態(tài)的。

  2. 一個(gè)encoder通過重寫writeRequested方法來實(shí)現(xiàn)對(duì)寫操作請(qǐng)求的攔截。不過請(qǐng)注意雖然這個(gè)writeRequested方法使用了和 messageReceived方法一樣的MessageEvent參數(shù),但是它們卻分別對(duì)應(yīng)了不同的解釋。一個(gè)ChannelEvent事件可以說是一個(gè)上升流事件(upstream event)也可以是一個(gè)下降流事件(downstream event),這取決于事件流的方向。例如:一個(gè)MessageEvent消息事件可以作為一個(gè)上升流事件(upstream event)被messageReceived方法調(diào)用,也可以作為一個(gè)下降流事件(downstream event)被writeRequested方法調(diào)用。請(qǐng)參考API手冊(cè)獲得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。

  3. 一旦完成了POJO和ChannelBuffer轉(zhuǎn)換,你應(yīng)當(dāng)確保把這個(gè)新的buffer緩沖轉(zhuǎn)發(fā)至先前的 ChannelDownstreamHandler處理,這個(gè)下降通道的處理器由某個(gè)ChannelPipeline管理。Channels提供了多個(gè)可以創(chuàng)建和發(fā)送ChannelEvent事件的幫助方法。在這個(gè)例子中,Channels.write(...)方法創(chuàng)建了一個(gè)新的 MessageEvent事件,并把這個(gè)事件發(fā)送給了先前的處于某個(gè)ChannelPipeline內(nèi)的 ChannelDownstreamHandler處理器。

另外,一個(gè)很不錯(cuò)的方法是使用靜態(tài)的方式導(dǎo)入Channels類:

import static org.jboss.netty.channel.Channels.*;
...
ChannelPipeline pipeline = pipeline();
write(ctx, e.getFuture(), buf);
fireChannelDisconnected(ctx)

最后的任務(wù)是把這個(gè)TimeEncoder插入服務(wù)端的ChannelPipeline,這是一個(gè)很簡(jiǎn)單的步驟。

1.9. 關(guān)閉你的應(yīng)用

如果你運(yùn)行了TimeClient,你肯定可以注意到,這個(gè)應(yīng)用并沒有自動(dòng)退出而只是在那里保持著無意義的運(yùn)行。跟蹤堆棧記錄你可以發(fā)現(xiàn),這里有一些運(yùn)行狀態(tài)的I/O線程。為了關(guān)閉這些I/O線程并讓應(yīng)用優(yōu)雅的退出,你需要釋放這些由ChannelFactory分配的資源。

一個(gè)典型的網(wǎng)絡(luò)應(yīng)用的關(guān)閉過程由以下三步組成:

關(guān)閉負(fù)責(zé)接收所有請(qǐng)求的server socket。

關(guān)閉所有客戶端socket或服務(wù)端為響應(yīng)某個(gè)請(qǐng)求而創(chuàng)建的socket。

釋放ChannelFactory使用的所有資源。

為了讓TimeClient執(zhí)行這三步,你需要在TimeClient.main()方法內(nèi)關(guān)閉唯一的客戶連接以及ChannelFactory使用的所有資源,這樣做便可以優(yōu)雅的關(guān)閉這個(gè)應(yīng)用。

package org.jboss.netty.example.time;  
 
public class TimeClient {  
    public static void main(String[] args) throws Exception {  
        ...  
        ChannelFactory factory = ...;  
        ClientBootstrap bootstrap = ...;  
        ...  
        ChannelFuture future  = bootstrap.connect(...);  
        future.awaitUninterruptible();  
        if (!future.isSuccess()) {  
            future.getCause().printStackTrace();  
        }  
        future.getChannel().getCloseFuture().awaitUninterruptibly();  
        factory.releaseExternalResources();  
    }  
} 

代碼說明

  1. ClientBootstrap對(duì)象的connect方法返回一個(gè)ChannelFuture對(duì)象,這個(gè)ChannelFuture對(duì)象將告知這個(gè)連接操作的成功或失敗狀態(tài)。同時(shí)這個(gè)ChannelFuture對(duì)象也保存了一個(gè)代表這個(gè)連接操作的Channel對(duì)象引用。

  2. 阻塞式的等待,直到ChannelFuture對(duì)象返回這個(gè)連接操作的成功或失敗狀態(tài)。

  3. 如果連接失敗,我們將打印連接失敗的原因。如果連接操作沒有成功或者被取消,ChannelFuture對(duì)象的getCause()方法將返回連接失敗的原因。

  4. 現(xiàn)在,連接操作結(jié)束,我們需要等待并且一直到這個(gè)Channel通道返回的closeFuture關(guān)閉這個(gè)連接。每一個(gè)Channel都可獲得自己的closeFuture對(duì)象,因此我們可以收到通知并在這個(gè)關(guān)閉時(shí)間點(diǎn)執(zhí)行某種操作。

并且即使這個(gè)連接操作失敗,這個(gè)closeFuture仍舊會(huì)收到通知,因?yàn)檫@個(gè)代表連接的 Channel對(duì)象將會(huì)在連接操作失敗后自動(dòng)關(guān)閉。

  1. 在這個(gè)時(shí)間點(diǎn),所有的連接已被關(guān)閉。剩下的唯一工作是釋放ChannelFactory通道工廠使用的資源。這一步僅需要調(diào)用 releaseExternalResources()方法即可。包括NIO Secector和線程池在內(nèi)的所有資源將被自動(dòng)的關(guān)閉和終止。

關(guān)閉一個(gè)客戶端應(yīng)用是很簡(jiǎn)單的,但又該如何關(guān)閉一個(gè)服務(wù)端應(yīng)用呢?你需要釋放其綁定的端口并關(guān)閉所有接受和打開的連接。為了做到這一點(diǎn),你需要使用一種數(shù)據(jù)結(jié)構(gòu)記錄所有的活動(dòng)連接,但這卻并不是一件容易的事。幸運(yùn)的是,這里有一種解決方案,ChannelGroup。

ChannelGroup是Java 集合 API的一個(gè)特有擴(kuò)展,ChannelGroup內(nèi)部持有所有打開狀態(tài)的Channel通道。如果一個(gè)Channel通道對(duì)象被加入到 ChannelGroup,如果這個(gè)Channel通道被關(guān)閉,ChannelGroup將自動(dòng)移除這個(gè)關(guān)閉的Channel通道對(duì)象。此外,你還可以對(duì)一個(gè)ChannelGroup對(duì)象內(nèi)部的所有Channel通道對(duì)象執(zhí)行相同的操作。例如,當(dāng)你關(guān)閉服務(wù)端應(yīng)用時(shí)你可以關(guān)閉一個(gè)ChannelGroup 內(nèi)部的所有Channel通道對(duì)象。

為了記錄所有打開的socket,你需要修改你的TimeServerHandler實(shí)現(xiàn),將一個(gè)打開的Channel通道加入全局的ChannelGroup對(duì)象,TimeServer.allChannels:

@Override 
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {  
    TimeServer.allChannels.add(e.getChannel());  
} 

代碼說明

是的,ChannelGroup是線程安全的

現(xiàn)在,所有活動(dòng)的Channel通道將被自動(dòng)的維護(hù),關(guān)閉一個(gè)服務(wù)端應(yīng)用有如關(guān)閉一個(gè)客戶端應(yīng)用一樣簡(jiǎn)單。

package org.jboss.netty.example.time;  
 
public class TimeServer {  
 
    static final ChannelGroup allChannels = new DefaultChannelGroup("time-server" );  
 
    public static void main(String[] args) throws Exception {  
        ...  
        ChannelFactory factory = ...;  
        ServerBootstrap bootstrap = ...;  
        ...  
        Channel channel  = bootstrap.bind(...);  
        allChannels.add(channel);  
        waitForShutdownCommand();  
        ChannelGroupFuture future = allChannels.close();  
        future.awaitUninterruptibly();  
        factory.releaseExternalResources();  
    }  
} 

代碼說明

  1. DefaultChannelGroup需要一個(gè)組名作為其構(gòu)造器參數(shù)。這個(gè)組名僅是區(qū)分每個(gè)ChannelGroup的一個(gè)標(biāo)示。

  2. ServerBootstrap對(duì)象的bind方法返回了一個(gè)綁定了本地地址的服務(wù)端Channel通道對(duì)象。調(diào)用這個(gè)Channel通道的close()方法將釋放這個(gè)Channel通道綁定的本地地址。

  3. 不管這個(gè)Channel對(duì)象屬于服務(wù)端,客戶端,還是為響應(yīng)某一個(gè)請(qǐng)求創(chuàng)建,任何一種類型的Channel對(duì)象都會(huì)被加入ChannelGroup。因此,你盡可在關(guān)閉服務(wù)時(shí)關(guān)閉所有的Channel對(duì)象。

  4. waitForShutdownCommand()是一個(gè)想象中等待關(guān)閉信號(hào)的方法。你可以在這里等待某個(gè)客戶端的關(guān)閉信號(hào)或者JVM的關(guān)閉回調(diào)命令。

  5. 你可以對(duì)ChannelGroup管理的所有Channel對(duì)象執(zhí)行相同的操作。在這個(gè)例子里,我們將關(guān)閉所有的通道,這意味著綁定在服務(wù)端特定地址的 Channel通道將解除綁定,所有已建立的連接也將異步關(guān)閉。為了獲得成功關(guān)閉所有連接的通知,close()方法將返回一個(gè) ChannelGroupFuture對(duì)象,這是一個(gè)類似ChannelFuture的對(duì)象。

1.10. 總述

在這一章節(jié),我們快速瀏覽并示范了如何使用Netty開發(fā)網(wǎng)絡(luò)應(yīng)用。下一章節(jié)將涉及更多的問題。同時(shí)請(qǐng)記住,為了幫助你以及能夠讓Netty基于你的回饋得到持續(xù)的改進(jìn)和提高,Netty社區(qū) 將永遠(yuǎn)歡迎你的問題及建議。

由于篇幅限制,就先將第一章的學(xué)習(xí)內(nèi)容展示出來了,希望可以大家有幫助,喜歡的小伙伴可以關(guān)注小編,后期每天不定時(shí)更新干貨~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容