1. Netty總體結構
1.1 Netty簡介
? Netty是一款用于創建高性能網路應用程式的高級框架,它的基于 Java NIO 的異步的和事件驅動的實作,保證了高負載下應用程式性能的最大化和可伸縮性,
? 其次,Netty 也包含了一組設計模式,將應用程式邏輯從網路層解耦,簡化了開發程序,同時也最大限度地提高了可測驗性、模塊化以及代碼的可重用性,
1.2 Netty組件
Netty主要包含以下幾個組件:

1.2.1 Netty 網路抽象的代表組件
Channel
? 個人理解:Channel類似對Socket的封裝,代表一個網路連接(類似WebServer專案中的http_conn類),可以進行讀寫操作,
? 以往基本的 I/O 操作(bind()、connect()、read()和 write())依賴于底層網路傳輸所提供的原語,而Netty 的 Channel 介面所提供的 API,大大地降低了直接使用 Socket 類的復雜性,
不同協議、不同的阻塞型別的連接都有不同的 Channel 型別與之對應,常用的 Channel 型別:
-
NioSocketChannel,NIO的客戶端 TCP Socket 連接,
-
NioServerSocketChannel,NIO的服務器端 TCP Socket 連接,
-
NioDatagramChannel, UDP 連接,
-
NioSctpChannel,客戶端 Sctp 連接,
-
NioSctpServerChannel,Sctp 服務器端連接,
這些通道涵蓋了 UDP 和 TCP 網路 IO 以及檔案 IO,
EventLoop
? 有了 Channel 連接服務,連接之間可以訊息流動,如果服務器發出的訊息稱作“出站”訊息,服務器接受的訊息稱作“入站”訊息,那么訊息的“出站”/“入站”就會產生事件(Event),
? 例如:連接已激活;資料讀取;用戶事件;例外事件;打開鏈接;關閉鏈接等等,
? 有了事件,就需要一個機制去監控和協調事件,這個機制(組件)就是EventLoop,
? Netty 通過觸發事件將 Selector 從應用程式中抽象出來,消除了所有本來將需要手動撰寫的派發代碼,在內部,將會為每個 Channel 分配一個 EventLoop,用以處理所有事件,包括:
- ? 注冊感興趣的事件;
- ? 將事件派發給 ChannelHandler;
- ? 安排進一步的動作,
? 每個 Channel 都會被分配到一個 EventLoop,一個 EventLoop 可以服務于多個 Channel,
? 每個 EventLoop 會占用一個 Thread,同時這個 Thread 會處理 EventLoop 上面發生的所有 IO 操作和事件,
? 個人理解,可以把EventLoop看作一個執行緒,而EventLoopGroup就是一個執行緒池,
? EventLoopGroup通過負載均衡演算法選擇某個EventLoop去系結Channel,
? EventLoop通過其Selector選擇器(類似epoll)去監聽其系結的Channel上發生的事件,并根據事件型別的不同,交給相應的ChannelHandler處理,
? 在Netty的執行緒模型為基于事件驅動的 Reactor 模型,可以通過配置實作幾種不同型別的Reactor模型,如 單執行緒模型、多執行緒模型和主從執行緒模型等,因此可能用到數量不同的
EventLoopGroup執行緒池,如主從執行緒模型就會用到兩個EventLoopGroup,一個用于監聽從客戶端發來的連接,一個用于讀寫資料、處理業務等,
? EventLoop 定義了 Netty 的核心抽象,用于處理連接的生命周期中所發生的事件,Channel、EventLoop、Thread 以及 EventLoopGroup 之間的關系如下圖所示:

這些關系是:
- 一個
EventLoopGroup包含一個或者多個EventLoop; - 一個
EventLoop在它的生命周期內只和一個Thread系結; - 所有由
EventLoop處理的 I/O 事件都將在它專有的Thread上被處理; - 一個
Channel在它的生命周期內只注冊于一個EventLoop; - 一個
EventLoop可能會被分配給一個或多個Channel,
注意,在這種設計中,一個給定 Channel 的 I/O 操作都是由相同的 Thread 執行的,實際上消除了對于同步的需要,
? EventLoopGroup類似于一個執行緒池,其中放著多個EventLoop,每個EventLoop都是一個單獨的執行緒,并且每個EventLoop都有一個Selector,用于監聽Channel狀態的變化(類似epoll),
? EventLoop監聽Channel的實作原理如下圖所示:

? 個人思考:為了便于理解,可以視為每個作業執行緒(EventLoop)都有屬于自己的epoll(Selector),
? 相比之下,之前的WebServer專案所有執行緒共用一個epoll:監聽客戶端連接的主執行緒,以及【決議請求、產生回應】的作業執行緒都共用一個epoll,
? 在web Server中:主執行緒呼叫
epoll_wait等待客戶端的連接,對于新到的連接呼叫accept系統呼叫生成一個用于通信的socket檔案描述符,并把這個檔案描述符與一個類似Channel的http連接物件(自己封裝的一個類)進行系結,然后注冊進epoll,
當有網路中有資料到達這個用于通信的socket檔案描述符的讀緩沖區,或服務器已經正確生成response并注冊了寫就緒事件時,epoll會將這個檔案描述符拿出,交給一個作業執行緒執行相應的操作,由于作業執行緒與主執行緒共用一個epoll,因此一個作業執行緒也只能處理一個連接,
? 在Netty中:如果使用主從執行緒模型,此時需要兩個EventLoopGroup,第一組將只包含一個 ServerChannel,代表服務器自身的已系結到某個本地埠的正在監聽的套接字,而第二組將包含所有已創建的用來處理傳入客戶端連接(對于每個服務器已經接受的連接都有一個)的 Channel(類似用于通信的socket檔案描述符),如下圖所示,
與 ServerChannel 相關聯的 EventLoopGroup 將分配一個負責為傳入的連接創建Channel 的EventLoop,一旦連接被接受,第二個EventLoopGroup 就會給它的 Channel分配一個 EventLoop,
與WebServer專案不同的是,在該執行緒模型中,Netty中每個作業執行緒EventLoop都有自己的Selector(可理解為epoll),因此一個EventLoop可以處理多個Channel,

? 為方便理解,貼上書中的兩段示例代碼:
- 未使用 Netty 的異步網路編程
//總體流程與WebServer專案中的main.cpp一致
public class PlainNioServer
{
public void serve(int port) throws IOException
{
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ssocket = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
//將服務器系結到選定的埠
ssocket.bind(address);
//打開Selector來處理Channel,只有一個Selector
Selector selector = Selector.open();
//將ServerSocket注冊到Selector以接收連接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;)
{
try
{
//阻塞,等待需要處理的新事件
selector.select();
}
catch (IOException ex)
{
ex.printStackTrace();
// handle exception
break;
}
//獲取所有接收事件的SelectionKey 實體
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext())
{
SelectionKey key = iterator.next();
iterator.remove();
try
{
//檢查事件是否是一個新的已經就緒可以被接受的連接(相當于判斷從epoll中取出的socketfd是否為當前服務端的fd)
if (key.isAcceptable())
{
ServerSocketChannel server =
(ServerSocketChannel)key.channel();
//accept客戶端
SocketChannel client = server.accept();
client.configureBlocking(false);
//將客戶端socket注冊到Selector選擇器
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
//檢查之前接收的socket是否已經準備好寫資料
if (key.isWritable())
{
SocketChannel client =
(SocketChannel)key.channel();
ByteBuffer buffer =
(ByteBuffer)key.attachment();
while (buffer.hasRemaining())
{
//寫資料到socket
if (client.write(buffer) == 0)
{
break;
}
}
//關閉連接
client.close();
}
}
catch (IOException ex)
{
key.cancel();
try
{
key.channel().close();
}
catch (IOException cex)
{
// ignore on close
}
}
}
}
}
}
- 使用 Netty 的異步網路處理
public class NettyNioServer
{
public void server(int port) throws Exception
{
final ByteBuf buf = Unpooled.copiedBuffer("Hi!\r\n",
Charset.forName("UTF-8"));
//為非阻塞模式使用NioEventLoopGroup
//Netty 內置了一些可開箱即用的傳輸,不僅有NIO,還有Netty自己實作的epoll等
EventLoopGroup group = new NioEventLoopGroup();
try {
//創建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
//此處沒有使用Reactor主從執行緒模型,只用了一個EventLoopGroup執行緒池
b.group(group).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
//指定 ChannelInitializer,對于每個已接受的連接都呼叫它
.childHandler(new ChannelInitializer<SocketChannel>()
{
@Override
public void initChannel(SocketChannel ch)
throws Exception
{
//添加 ChannelInboundHandlerAdapter 以接收和處理事件
ch.pipeline().addLast(
new ChannelInboundHandlerAdapter()
{
@Override
public void channelActive(
ChannelHandlerContext ctx) throws Exception
{
//將訊息寫到客戶端,并添加ChannelFutureListener,以便訊息一被寫完就關閉連接
ctx.writeAndFlush(buf.duplicate())
.addListener(
ChannelFutureListener.CLOSE);
}
});
}
});
//系結服務器以接受連接
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} finally {
//釋放所有的資源
group.shutdownGracefully().sync();
}
}
}
ChannelFuture
? Netty 中所有的 I/O 操作都是異步的,因為一個操作可能不會立即回傳,所以我們需要一種用于在之后的某個時間點確定其結果的方法,為此,Netty 提供了ChannelFuture 介面,其 addListener()方法注冊了一個ChannelFutureListener,以便在某個操作完成時(無論是否成功)得到通知,
? 說人話就是ChannelFuture用于在異步操作執行完成后,記錄結果并發送通知,
1.2.2 管理資料流以及執行應用程式處理邏輯的組件
ChannelHandler
? 從應用程式開發人員的角度來看,Netty 的主要組件是 ChannelHandler,它充當了所有處理入站和出站資料的應用程式邏輯的容器,
? ChannelHandler 的方法是由網路事件(其中術語“事件”的使用非常廣泛)觸發的,事實上,ChannelHandler 可專門用于幾乎任何型別的動作,例如將資料從一種格式轉換為另外一種格式,或者處理轉換程序中所拋出的例外,
ChannelPipeline
? 將多個ChannelHandler串聯起來,形成一個處理鏈,用于處理Channel中的事件,當 Channel 被創建時,它會被自動地分配到它專屬的 ChannelPipeline,
? ChannelHandler安裝到 ChannelPipeline 中的程序如下所示(流程與上面Netty 的異步網路處理代碼一致):
- 一個
ChannelInitializer的實作被注冊到了ServerBootstrap中 ; - 當
ChannelInitializer.initChannel()方法被呼叫時,ChannelInitializer將在ChannelPipeline中安裝一組自定義的 ChannelHandler; ChannelInitializer將它自己從ChannelPipeline中移除,
? ChannelHandler 的觸發順序是根據 pipeline().addLast() 的注冊順序來確定的,在 Netty 中,ChannelPipeline 是一個事件處理的鏈表,每個 ChannelHandler 負責處理特定的事件,當一個事件被觸發時,Netty 會按照 ChannelPipeline 中 ChannelHandler 的添加順序依次呼叫它們的事件處理方法,因此,如果想要控制事件處理的順序,可以通過添加 ChannelHandler 的順序來實作,
? 當ChannelHandler被添加到ChannelPipeline時,它將會被分配一個
ChannelHandlerContext,通過使用作為引數傳遞到每個方法的ChannelHandlerContext,事件可以被傳遞給當前ChannelHandler鏈中的下一個 ChannelHandler,因為你有時會忽略那些不感興趣的事件,所以 Netty提供了抽象基類ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapters,通過呼叫ChannelHandlerContext上的對應方法,每個都提供了簡單地將事件傳遞給下一個ChannelHandler的方法的實作,隨后,你可以通過重寫你所感興趣的那些方法來擴展這些類,? 個人理解:可以將ChannelHandlerContext類比為Linux中的socket檔案描述符,在Netty框架中,ChannelHandlerContext是一個背景關系物件,用于表示一個通道的背景關系資訊,包括該通道的事件處理器、通道的配置資訊以及與該通道相關的一些狀態資訊,類似于Linux中的socket檔案描述符,它可以用于發送和接收資料,以及進行其他與通道相關的操作,但是需要注意的是,ChannelHandlerContext并不是一個作業系統底層的檔案描述符,而是Netty框架中的一個抽象概念,
具體實體會在后面給出,
Bootstrap和ServerBootstrap
? Bootstrap和ServerBootstrap分別用于啟動客戶端和服務端,
? 在Netty中,有兩種型別的引導:一種用于客戶端(簡單地稱為 Bootstrap),而另一種(ServerBootstrap)用于服務器,它們都包含EventLoopGroup和一個ChannelPipeline,用于管理連接和處理事件,無論你的應用程式使用哪種協議或者處理哪種型別的資料,唯一決定它使用哪種引導類的是它是作為一個客戶端還是作為一個服務器,
1.3 Netty執行流程
? 1. 創建一個EventLoopGroup:EventLoopGroup是Netty中的一個執行緒池,用于處理所有的I/O操作,
? 2.創建一個ServerBootstrap(客戶端為Bootstrap):ServerBootstrap是Netty中用于創建服務器的類,它包含了一些配置項,如埠號、協議等,
? 3.配置ServerBootstrap:通過ServerBootstrap的一些方法,如group()、channel()、option()、handler()等,來配置ServerBootstrap,
? 4.系結埠號:呼叫ServerBootstrap的bind()方法來系結埠號,啟動服務器,
? 5.創建一個ChannelPipeline:當一個客戶端連接到服務器時,Netty會為該連接創建一個Channel物件,每個Channel物件都包含一個ChannelPipeline,ChannelPipeline是一個事件處理器鏈,它包含了一系列的ChannelHandler,
? 6.添加ChannelHandler:通過呼叫ChannelPipeline的addLast()方法,將自定義的ChannelHandler添加到ChannelPipeline中,
? 7.處理事件:當有事件發生時,Netty會將事件封裝成一個物件,并將該物件傳遞給ChannelPipeline中的第一個ChannelHandler,然后由ChannelHandler處理事件,或將事件傳遞給下一個ChannelHandler,
? 8.發送回應:當ChannelHandler處理完事件后,可以通過ChannelHandlerContext的write()方法將回應發送給客戶端,
? 9.關閉連接:當客戶端關閉連接時,Netty會將該事件封裝成一個物件,并將該物件傳遞給ChannelPipeline中的最后一個ChannelHandler,然后由ChannelHandler處理該事件,釋放資源,
2. Netty整體架構
? Netty使用了典型的三層網路架構,Reactor 通信調度層 -> 職責鏈 PipeLine -> 業務邏輯處理層
? Reactor層主要監聽網路的讀寫和連接操作,負責將網路層的資料 讀取到記憶體緩沖區中,然后觸發各種網路事件,例如連接創建、連接激活、讀事 件、寫事件等等,將這些事件觸發到 PipeLine 中,由 PipeLine 充當的職責鏈來 進行后續的處理,涉及到的類包括:Reactor 執行緒 NioEventLoop 以及其父類、NioSocketChannel/NioServerSocketChannel 以及其父 類、ByteBuffer 以及由其衍生出來的各種 Buffer、Unsafe 以及其衍生出的各種內 部類等
? Pipeline層負責事件在職責鏈中有序的傳播,職責鏈可以選擇監聽和處理自己關心的事件,它可以攔截處理和向 后 / 向前傳播事件,不同的應用的 Handler 節點的功能也不同,通常情況下,往往會開發編解碼 Hanlder 用于訊息的編解碼,它可以將外部的協議訊息轉換成內部 的 POJO 物件,這樣上層業務側只需要關心處理業務邏輯即可,不需要感知底層 的協議差異和執行緒模型差異,實作了架構層面的分層隔離,
? Service層有純粹的業務邏輯 處理,例如訂單處理;也有應用層協議管理,例如 HTTP 協議、FTP 協議等(在書中還未看到),

3. Netty實體
Echo服務器與客戶端的主要功能為:服務器接收到客戶端發送的請求,將訊息原封不動回傳,如下圖所示:

所有的 Netty 服務器都需要以下兩部分,
- 至少一個 ChannelHandler—該組件實作了服務器對從客戶端接收的資料的處理,即它的業務邏輯,
- 引導—這是配置服務器的啟動代碼,至少,它會將服務器系結到它要監聽連接請求的埠上,
在本小節的剩下部分,我們將描述 Echo 服務器的業務邏輯以及引導代碼,
3.1 maven結構
<groupId>org.example</groupId>
<artifactId>netty-demo-1</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>server</module>
<module>client</module>
</modules>
<!-- ...省略引入的依賴及插件等 -->
3.2 server模塊
? server模塊包含兩個.java檔案,其中EchoServer為服務器的主流程,EchoServerHandler為業務邏輯,

具體作用如注釋所示:
//EchoServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/*
* @Sharable:標示一個ChannelHandler 可以被多個 Channel 安全地共享
* EchoServerHandler:處理業務邏輯
* */
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter { //繼承 ChannelInboundHandlerAdapter 類,以定義回應入站事件的方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
{
//客戶端寫來的訊息會被放入msg中
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
//將收到的msg原封不動寫回去
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
{
//ctx.writeAndFlush(Unpooled.EMPTY_BUFFER) 表示將一個空的緩沖區寫入到當前的 ChannelHandlerContext 中,并將其發送給客戶端,
//addListener(ChannelFutureListener.CLOSE) 表示在發送完成后添加一個監聽器,當發送完成后會自動關閉當前的連接,
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
{
cause.printStackTrace();
ctx.close();
}
}
//EchoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
public class EchoServer {
private final int port;
public EchoServer(int port)
{
this.port = port;
}
public static void main(String[] args) throws Exception
{
if(args.length != 1)
{
//設定埠值(如果埠引數的格式不正確,則拋出一個NumberFormatException)
System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
int port = Integer.parseInt(args[0]);
//呼叫服務器的start方法
new EchoServer(port).start();
}
}
public void start() throws Exception
{
final EchoServerHandler serverHandler = new EchoServerHandler();
//創建EventLoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//創建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class) //適用于NIO傳輸的Channel型別
.localAddress(new InetSocketAddress(port)) //設定socket埠和地址,服務器系結該地址監聽新的請求
//此處當一個新的連接被接受時,一個新的子 Channel 將會被創建
//而 ChannelInitializer 將會把一個你的EchoServerHandler 的實體添加到該 Channel 的 ChannelPipeline 中,
//如EchoServerHandler類所示,這個 ChannelHandler 將會收到有關入站訊息的通知
.childHandler(new ChannelInitializer<SocketChannel>(){ //此處SocketChannel是netty的不是nio的
//添加一個EchoServerHandler 到子Channel的 ChannelPipeline
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(serverHandler);
}
});
//異步地系結服務器,呼叫sync()方法阻塞等待,直到系結完成
ChannelFuture f = b.bind().sync();
//獲取 Channel 的CloseFuture,并且阻塞當前執行緒直到它完成
f.channel().closeFuture().sync();
}
finally {
//關閉 EventLoopGroup,釋放所有的資源
group.shutdownGracefully().sync();
}
}
}
3.3 client模塊
? client模塊同樣包含兩個.java檔案,其中EchoClient為客戶端的主流程,EchoClientHandler為業務邏輯,

//EchoClientHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
@ChannelHandler.Sharable //標記該類的實體可以被多個 Channel 共享
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>
{
/*
channelActive()——在到服務器的連接已經建立之后將被呼叫;
這確保了資料將會被盡可能快地寫入服務器,其在這個場景下是一個編碼了字串"Netty rocks!"的位元組緩沖區
channelRead0() —當從服務器接收到一條訊息時被呼叫;
需要注意的是,由服務器發送的訊息可能會被分塊接收,
也就是說,如果服務器發送了 5 位元組,那么不能保證這 5 位元組會被一次性接收,
即使是對于這么少量的資料,channelRead0()方法也可能會被呼叫兩次,第一次使用一個持有 3 位元組的 ByteBuf(Netty 的位元組容器),第二次使用一個持有 2 位元組的 ByteBuf,
exceptionCaught()——在處理程序中引發例外時被呼叫,
*/
@Override
public void channelActive(ChannelHandlerContext ctx)
{
/*
Netty 中的 Unpooled 是一個工具類,用于創建不需要池化的 ByteBuf 實體,
ByteBuf 是 Netty 中的緩沖區型別,用于存盤資料,
與池化的 ByteBuf 實體不同,不需要池化的 ByteBuf 實體是一次性的,使用完后會被釋放,
因此,如果你需要創建一些臨時的 ByteBuf 實體,可以使用 Unpooled 來創建,
例如,如果你需要將一個字串轉換為 ByteBuf,
可以使用 Unpooled.copiedBuffer(str, Charset.defaultCharset()) 方法來創建一個不需要池化的 ByteBuf 實體,
*/
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf in) throws Exception {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause)
{
cause.printStackTrace();
ctx.close();
}
}
//EchoClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host,int port)
{
this.host = host;
this.port = port;
}
public void start() throws Exception
{
EventLoopGroup group = new NioEventLoopGroup();
try
{
Bootstrap b = new Bootstrap();
//指定 EventLoopGroup 以處理客戶端事件;需要適用于 NIO 的實作
b.group(group)
//適用于NIO傳輸的Channel型別,注意不是NioServerSocketChannel
.channel(NioSocketChannel.class)
//設定服務器的InetSocketAddress
.remoteAddress(new InetSocketAddress(host,port))
//在創建Channel時,向 ChannelPipeline中添加一個 EchoClientHandler 實體
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync(); //阻塞,直到 Channel 關閉
}
finally {
//關閉執行緒池并且釋放所有的資源
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception
{
if(args.length != 2)
{
System.err.println(
"Usage: " + EchoClient.class.getSimpleName() + "<host><port>"
);
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host,port).start();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553936.html
標籤:Java
上一篇:Spring MVC官方檔案學習筆記(二)之DispatcherServlet
下一篇:返回列表
