netty常用API學習
netty簡介
- Netty是基于Java NIO的網(wǎng)絡應用框架.
- Netty是一個NIO client-server(客戶端服務器)框架狸剃,使用Netty可以快速開發(fā)網(wǎng)絡應用塑娇,例如服務器和客戶端協(xié)議。Netty提供了一種新的方式來使開發(fā)網(wǎng)絡應用程序被丧,這種新的方式使得它很容易使用和有很強的擴展性。Netty的內部實現(xiàn)時很復雜的,但是Netty提供了簡單易用的api從網(wǎng)絡處理代碼中解耦業(yè)務邏輯鸽扁。Netty是完全基于NIO實現(xiàn)的谁撼,所以整個Netty都是異步的歧胁。
- 網(wǎng)絡應用程序通常需要有較高的可擴展性,無論是Netty還是其他的基于Java NIO的框架,都會提供可擴展性的解決方案喊巍。Netty中一個關鍵組成部分是它的異步特性.
netty的helloworld
下載netty包
- 下載netty包屠缭,下載地址http://netty.io/-N/
服務端啟動類
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.nio.NioServerSocketChannel;
/**
* ? 配置服務器功能,如線程玄糟、端口 ? 實現(xiàn)服務器處理程序勿她,它包含業(yè)務邏輯,決定當有一個請求連接或接收數(shù)據(jù)時該做什么
*
*/
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
EventLoopGroup eventLoopGroup = null;
try {
//創(chuàng)建ServerBootstrap實例來引導綁定和啟動服務器
ServerBootstrap serverBootstrap = new ServerBootstrap();
//創(chuàng)建NioEventLoopGroup對象來處理事件阵翎,如接受新連接逢并、接收數(shù)據(jù)、寫數(shù)據(jù)等等
eventLoopGroup = new NioEventLoopGroup();
//指定通道類型為NioServerSocketChannel郭卫,設置InetSocketAddress讓服務器監(jiān)聽某個端口已等待客戶端連接砍聊。
serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress("localhost",port).childHandler(new ChannelInitializer<Channel>() {
//設置childHandler執(zhí)行所有的連接請求
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 最后綁定服務器等待直到綁定完成,調用sync()方法會阻塞直到服務器完成綁定,然后服務器等待通道關閉贰军,因為使用sync()玻蝌,所以關閉操作也會被阻塞。
ChannelFuture channelFuture = serverBootstrap.bind().sync();
System.out.println("開始監(jiān)聽词疼,端口為:" + channelFuture.channel().localAddress());
channelFuture.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new EchoServer(20000).start();
}
}
服務端回調方法
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("server 讀取數(shù)據(jù)……");
//讀取數(shù)據(jù)
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("接收客戶端數(shù)據(jù):" + body);
//向客戶端寫數(shù)據(jù)
System.out.println("server向client發(fā)送數(shù)據(jù)");
String currentTime = new Date(System.currentTimeMillis()).toString();
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("server 讀取數(shù)據(jù)完畢..");
ctx.flush();//刷新后才將數(shù)據(jù)發(fā)出到SocketChannel
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客戶端啟動類
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;
/**
* ? 連接服務器 ? 寫數(shù)據(jù)到服務器 ? 等待接受服務器返回相同的數(shù)據(jù) ? 關閉連接
*
*/
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 nioEventLoopGroup = null;
try {
//創(chuàng)建Bootstrap對象用來引導啟動客戶端
Bootstrap bootstrap = new Bootstrap();
//創(chuàng)建EventLoopGroup對象并設置到Bootstrap中俯树,EventLoopGroup可以理解為是一個線程池,這個線程池用來處理連接贰盗、接受數(shù)據(jù)许饿、發(fā)送數(shù)據(jù)
nioEventLoopGroup = new NioEventLoopGroup();
//創(chuàng)建InetSocketAddress并設置到Bootstrap中,InetSocketAddress是指定連接的服務器地址
bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
//添加一個ChannelHandler舵盈,客戶端成功連接服務器后就會被執(zhí)行
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// ? 調用Bootstrap.connect()來連接服務器
ChannelFuture f = bootstrap.connect().sync();
// ? 最后關閉EventLoopGroup來釋放資源
f.channel().closeFuture().sync();
} finally {
nioEventLoopGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new EchoClient("localhost", 20000).start();
}
}
netty中handler的執(zhí)行順序
- Handler在netty中陋率,無疑占據(jù)著非常重要的地位。Handler與Servlet中的filter很像秽晚,通過Handler可以完成通訊報文的解碼編碼瓦糟、攔截指定的報文、統(tǒng)一對日志錯誤進行處理赴蝇、統(tǒng)一對請求進行計數(shù)菩浙、控制Handler執(zhí)行與否。一句話句伶,沒有它做不到的只有你想不到的劲蜻。
- Netty中的所有handler都實現(xiàn)自ChannelHandler接口。按照輸出輸出來分熄阻,分為ChannelInboundHandler斋竞、ChannelOutboundHandler兩大類。ChannelInboundHandler對從客戶端發(fā)往服務器的報文進行處理秃殉,一般用來執(zhí)行解碼坝初、讀取客戶端數(shù)據(jù)浸剩、進行業(yè)務處理等;ChannelOutboundHandler對從服務器發(fā)往客戶端的報文進行處理鳄袍,一般用來進行編碼绢要、發(fā)送報文到客戶端。
- Netty中拗小,可以注冊多個handler重罪。ChannelInboundHandler按照注冊的先后順序執(zhí)行;ChannelOutboundHandler按照注冊的先后順序逆序執(zhí)行哀九,如下圖所示剿配,按照注冊的先后順序對Handler進行排序,request進入Netty后的執(zhí)行順序為
- mark
代碼示例
-
server
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; 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.nio.NioServerSocketChannel; /** * ? 配置服務器功能阅束,如線程呼胚、端口 ? 實現(xiàn)服務器處理程序,它包含業(yè)務邏輯息裸,決定當有一個請求連接或接收數(shù)據(jù)時該做什么 */ public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup eventLoopGroup = null; try { //server端引導類 ServerBootstrap serverBootstrap = new ServerBootstrap(); //連接池處理數(shù)據(jù) eventLoopGroup = new NioEventLoopGroup(); serverBootstrap.group(eventLoopGroup) .channel(NioServerSocketChannel.class)//指定通道類型為NioServerSocketChannel蝇更,一種異步模式,OIO阻塞模式為OioServerSocketChannel .localAddress("localhost",port)//設置InetSocketAddress讓服務器監(jiān)聽某個端口已等待客戶端連接呼盆。 .childHandler(new ChannelInitializer<Channel>() {//設置childHandler執(zhí)行所有的連接請求 @Override protected void initChannel(Channel ch) throws Exception { // 注冊兩個InboundHandler年扩,執(zhí)行順序為注冊順序,所以應該是InboundHandler1 InboundHandler2 // 注冊兩個OutboundHandler访圃,執(zhí)行順序為注冊順序的逆序厨幻,所以應該是OutboundHandler2 OutboundHandler1 ch.pipeline().addLast(new EchoInHandler1()); ch.pipeline().addLast(new EchoInHandler2()); ch.pipeline().addLast(new EchoOutHandler1()); ch.pipeline().addLast(new EchoOutHandler2()); } }); // 最后綁定服務器等待直到綁定完成,調用sync()方法會阻塞直到服務器完成綁定,然后服務器等待通道關閉挽荠,因為使用sync()克胳,所以關閉操作也會被阻塞平绩。 ChannelFuture channelFuture = serverBootstrap.bind().sync(); System.out.println("開始監(jiān)聽圈匆,端口為:" + channelFuture.channel().localAddress()); channelFuture.channel().closeFuture().sync(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(20000).start(); } }
-
EchoInHandler1
-
public class EchoInHandler1 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("in1"); // 通知執(zhí)行下一個InboundHandler ctx.fireChannelRead(msg); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush();//刷新后才將數(shù)據(jù)發(fā)出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
?
-
-
EchoInHandler2
-
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.Date; import cn.itcast_03_netty.sendobject.bean.Person; public class EchoInHandler2 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("in2"); ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("接收客戶端數(shù)據(jù):" + body); //向客戶端寫數(shù)據(jù) System.out.println("server向client發(fā)送數(shù)據(jù)"); String currentTime = new Date(System.currentTimeMillis()).toString(); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush();//刷新后才將數(shù)據(jù)發(fā)出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
?
-
-
EchoOutHandler1
-
import java.util.Date; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; public class EchoOutHandler1 extends ChannelOutboundHandlerAdapter { @Override // 向client發(fā)送消息 public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("out1"); /*System.out.println(msg);*/ String currentTime = new Date(System.currentTimeMillis()).toString(); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.write(resp); ctx.flush(); } }
?
-
-
EchoOutHandler2
-
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPromise; public class EchoOutHandler2 extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { System.out.println("out2"); // 執(zhí)行下一個OutboundHandler /*System.out.println("at first..msg = "+msg); msg = "hi newed in out2";*/ super.write(ctx, msg, promise); } }
?
-
總結
- 在使用Handler的過程中,需要注意:
- ChannelInboundHandler之間的傳遞捏雌,通過調用
ctx.fireChannelRead(msg)
實現(xiàn)跃赚;調用ctx.write(msg)
將傳遞到ChannelOutboundHandler。 -
ctx.write()
方法執(zhí)行后性湿,需要調用flush()
方法才能令它立即執(zhí)行纬傲。 - 流水線pipeline中outhandler不能放在最后,否則不生效
- Handler的消費處理放在最后一個處理肤频。
- ChannelInboundHandler之間的傳遞捏雌,通過調用
netty發(fā)送對象
簡介
- Netty中叹括,通訊的雙方建立連接后,會把數(shù)據(jù)按照ByteBuf的方式進行傳輸宵荒,例如http協(xié)議中汁雷,就是通過HttpRequestDecoder對ByteBuf數(shù)據(jù)流進行處理净嘀,轉換成http的對象∠姥叮基于這個思路挖藏,我自定義一種通訊協(xié)議:Server和客戶端直接傳輸java對象。
- 實現(xiàn)的原理是通過Encoder把java對象轉換成ByteBuf流進行傳輸厢漩,通過Decoder把ByteBuf轉換成java對象進行處理膜眠,處理邏輯如下圖所示:
- mark
代碼
-
bean
import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private String sex; private int age; public String toString() { return "name:" + name + " sex:" + sex + " age:" + age; } get/set... }
-
序列化
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import cn.itcast_03_netty.sendobject.bean.Person; import cn.itcast_03_netty.sendobject.utils.ByteObjConverter; /** * 序列化 * 將object轉換成Byte[] * */ public class PersonEncoder extends MessageToByteEncoder<Person> { @Override protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception { //工具類:將object轉換為byte[] byte[] datas = ByteObjConverter.objectToByte(msg); out.writeBytes(datas); ctx.flush(); } }
-
反序列化
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; import cn.itcast_03_netty.sendobject.utils.ByteBufToBytes; import cn.itcast_03_netty.sendobject.utils.ByteObjConverter; /** * 反序列化 * 將Byte[]轉換為Object * */ public class PersonDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { //工具類:將ByteBuf轉換為byte[] ByteBufToBytes read = new ByteBufToBytes(); byte[] bytes = read.read(in); //工具類:將byte[]轉換為object Object obj = ByteObjConverter.byteToObject(bytes); out.add(obj); } }
-
轉換工具類
import io.netty.buffer.ByteBuf; public class ByteBufToBytes { /** * 將ByteBuf轉換為byte[] * @param datas * @return */ public byte[] read(ByteBuf datas) { byte[] bytes = new byte[datas.readableBytes()];// 創(chuàng)建byte[] datas.readBytes(bytes);// 將ByteBuf轉換為byte[] return bytes; } }
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class ByteObjConverter { /** * 使用IO的inputstream流將byte[]轉換為object * @param bytes * @return */ public static Object byteToObject(byte[] bytes) { Object obj = null; ByteArrayInputStream bi = new ByteArrayInputStream(bytes); ObjectInputStream oi = null; try { oi = new ObjectInputStream(bi); obj = oi.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { try { bi.close(); } catch (IOException e) { e.printStackTrace(); } try { oi.close(); } catch (IOException e) { e.printStackTrace(); } } return obj; } /** * 使用IO的outputstream流將object轉換為byte[] * @param bytes * @return */ public static byte[] objectToByte(Object obj) { byte[] bytes = null; ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream oo = null; try { oo = new ObjectOutputStream(bo); oo.writeObject(obj); bytes = bo.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { try { bo.close(); } catch (IOException e) { e.printStackTrace(); } try { oo.close(); } catch (IOException e) { e.printStackTrace(); } } return bytes; } }
-
ServerHandler
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import cn.itcast_03_netty.sendobject.bean.Person; public class EchoServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Person person = (Person) msg; System.out.println(person.getName()); System.out.println(person.getAge()); System.out.println(person.getSex()); } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { System.out.println("server 讀取數(shù)據(jù)完畢.."); ctx.flush();//刷新后才將數(shù)據(jù)發(fā)出到SocketChannel } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
server
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; 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.nio.NioServerSocketChannel; import cn.itcast_03_netty.sendobject.coder.PersonDecoder; /** * ? 配置服務器功能,如線程溜嗜、端口 ? 實現(xiàn)服務器處理程序宵膨,它包含業(yè)務邏輯,決定當有一個請求連接或接收數(shù)據(jù)時該做什么 * * */ public class EchoServer { private final int port; public EchoServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup eventLoopGroup = null; try { //創(chuàng)建ServerBootstrap實例來引導綁定和啟動服務器 ServerBootstrap serverBootstrap = new ServerBootstrap(); //創(chuàng)建NioEventLoopGroup對象來處理事件炸宵,如接受新連接柄驻、接收數(shù)據(jù)、寫數(shù)據(jù)等等 eventLoopGroup = new NioEventLoopGroup(); //指定通道類型為NioServerSocketChannel焙压,一種異步模式鸿脓,OIO阻塞模式為OioServerSocketChannel //設置InetSocketAddress讓服務器監(jiān)聽某個端口已等待客戶端連接。 serverBootstrap.group(eventLoopGroup).channel(NioServerSocketChannel.class).localAddress("localhost",port) .childHandler(new ChannelInitializer<Channel>() { //設置childHandler執(zhí)行所有的連接請求 @Override protected void initChannel(Channel ch) throws Exception { //注冊解碼的handler ch.pipeline().addLast(new PersonDecoder()); //IN1 反序列化 //添加一個入站的handler到ChannelPipeline ch.pipeline().addLast(new EchoServerHandler()); //IN2 } }); // 最后綁定服務器等待直到綁定完成涯曲,調用sync()方法會阻塞直到服務器完成綁定,然后服務器等待通道關閉野哭,因為使用sync(),所以關閉操作也會被阻塞幻件。 ChannelFuture channelFuture = serverBootstrap.bind().sync(); System.out.println("開始監(jiān)聽拨黔,端口為:" + channelFuture.channel().localAddress()); channelFuture.channel().closeFuture().sync(); } finally { eventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoServer(20000).start(); } }
-
clientHandler
import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import cn.itcast_03_netty.sendobject.bean.Person; public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> { // 客戶端連接服務器后被調用 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Person person = new Person(); person.setName("angelababy"); person.setSex("girl"); person.setAge(18); ctx.write(person); ctx.flush(); } // ? 從服務器接收到數(shù)據(jù)后調用 @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("client 讀取server數(shù)據(jù).."); // 服務端返回消息后 ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("服務端數(shù)據(jù)為 :" + body); } // ? 發(fā)生異常時被調用 @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("client exceptionCaught.."); // 釋放資源 ctx.close(); } }
-
client
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; import cn.itcast_03_netty.sendobject.coder.PersonEncoder; /** * ? 連接服務器 ? 寫數(shù)據(jù)到服務器 ? 等待接受服務器返回相同的數(shù)據(jù) ? 關閉連接 * */ 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 nioEventLoopGroup = null; try { // 創(chuàng)建Bootstrap對象用來引導啟動客戶端 Bootstrap bootstrap = new Bootstrap(); // 創(chuàng)建EventLoopGroup對象并設置到Bootstrap中,EventLoopGroup可以理解為是一個線程池绰沥,這個線程池用來處理連接篱蝇、接受數(shù)據(jù)、發(fā)送數(shù)據(jù) nioEventLoopGroup = new NioEventLoopGroup(); // 創(chuàng)建InetSocketAddress并設置到Bootstrap中徽曲,InetSocketAddress是指定連接的服務器地址 bootstrap.group(nioEventLoopGroup)// .channel(NioSocketChannel.class)// .remoteAddress(new InetSocketAddress(host, port))// .handler(new ChannelInitializer<SocketChannel>() {// // 添加一個ChannelHandler零截,客戶端成功連接服務器后就會被執(zhí)行 @Override protected void initChannel(SocketChannel ch) throws Exception { // 注冊編碼的handler ch.pipeline().addLast(new PersonEncoder()); //out //注冊處理消息的handler ch.pipeline().addLast(new EchoClientHandler()); //in } }); // ? 調用Bootstrap.connect()來連接服務器 ChannelFuture f = bootstrap.connect().sync(); // ? 最后關閉EventLoopGroup來釋放資源 f.channel().closeFuture().sync(); } finally { nioEventLoopGroup.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { new EchoClient("localhost", 20000).start(); } }