Netty是一款用于创建高性能网络应用程序的高级框架。
在第1章中,我们将首先小结Java网络编程的演化过程。在我们回顾了异步通信和事件驱动的处理的基本概念之后,我们将首先看一看Netty的核心组件。在第2章中,你将能够构建自己的第一款基于Netty的应用程序!在第3章中,你将开启对于Netty的细致探究之旅,从它的核心网络协议(第4章)以及数据处理层(第5章和第6章)到它的并发模型(第7章)。我们将把所有的这些细节组合在一起,对第一部分进行总结。你将看到:如何在运行时配置基于Netty的应用程序的各个组件,以使它们协同工作(第8章),Netty是如何帮助你测试你的应用程序的(第9章)。
第1章 Netty——异步和事件驱动
一个既是异步的又是事件驱动的系统会表现出一种特殊的、对我们来说极具价值的行为:它可以以任意的顺序响应在任意的时间点产生的事件。
异步和可伸缩性之间的联系又是什么呢?
- 非阻塞网络调用使得我们可以不必等待一个操作的完成。完全异步的I/O正是基于这个特性构建的,并且更进一步:异步方法会立即返回,并且在它完成时,会直接或者在稍后的某个时间点通知用户。
- 选择器使得我们能够通过较少的线程便可监视许多连接上的事件。
1.1 Netty的核心组件
在本节中我将要讨论Netty的主要构件块:
Channel
;- 回调;
Future
;- 事件和
ChannelHandler
。
1.1.1 Channel
Channel是Java NIO的一个基本构造。
它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的I/O操作的程序组件)的开放连接,如读操作和写操作.可以把Channel
看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。
1.1.2 回调
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用,即指向一个方法用来处理某个事件,一旦触动便执行。这使得后者可以在适当的时候调用前者。回调在广泛的编程场景中都有应用,而且也是在操作完成后通知相关方最常见的方式之一。
Netty在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个interface-ChannelHandler
的实现处理。
public class ConnectHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx)
throws Exception { ⇽ -- 当一个新的连接已经被建立时,channelActive(ChannelHandlerContext)将会被调用
System.out.println(
"Client " + ctx.channel().remoteAddress() + " connected");
}
}
1.1.3 Future
Future
提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。JDK预置了interface java.util.concurrent.Future
,但是其所提供的实现,只允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以Netty提供了它自己的实现——ChannelFuture
,用于在执行异步操作的时候使用。
ChannelFuture
提供了几种额外的方法,这些方法使得我们能够注册一个或者多个ChannelFutureListener
实例。监听器的回调方法operationComplete()
,将会在对应的操作完成时被调用。简而言之,由ChannelFutureListener
提供的通知机制消除了手动检查对应的操作是否完成的必要。每个Netty的出站I/O操作都将返回一个ChannelFuture
;也就是说,它们都不会阻塞。正如我们前面所提到过的一样,Netty完全是异步和事件驱动的。
ChannelFuture future = channel.connect( ⇽ -- 异步地连接到远程节点
new InetSocketAddress("192.168.0.1", 25));
这里,connect()
方法将会直接返回,而不会阻塞,该调用将会在后台完成。这究竟什么时候会发生则取决于若干的因素,但这个关注点已经从代码中抽象出来了。因为线程不用阻塞以等待对应的操作完成,所以它可以同时做其他的工作,从而更加有效地利用资源。
Channel channel = ...;
// Does not block
ChannelFuture future = channel.connect( ⇽ -- 异步地连接到远程节点
new InetSocketAddress("192.168.0.1", 25));
future.addListener(new ChannelFutureListener() { ⇽ -- 注册一个ChannelFutureListener,以便在操作完成时获得通知
@Override
public void operationComplete(ChannelFuture future) { ⇽ -- ❶ 检查操作
的状态
if (future.isSuccess()){
ByteBuf buffer = Unpooled.copiedBuffer( ⇽ -- 如果操作是成功的,则创建一个ByteBuf以持有数据
"Hello",Charset.defaultCharset());
ChannelFuture wf = future.channel()
.writeAndFlush(buffer); ⇽ -- 将数据异步地发送到远程节点。
返回一个ChannelFuture
....
} else {
Throwable cause = future.cause(); ⇽ -- 如果发生错误,则访问描述原因的Throwable
cause.printStackTrace();
}
}
});
首先,要连接到远程节点上。然后,要注册一个新的ChannelFutureListener
到对connect()
方法的调用所返回的ChannelFuture
上。当该监听器被通知连接已经建立的时候,要检查对应的状态❶。如果该操作是成功的,那么将数据写到该Channel
。否则,要从ChannelFuture
中检索对应的Throwable
。事实上,回调和Future
是相互补充的机制;它们相互结合,构成了Netty本身的关键构件块之一。
1.1.4 事件和ChannelHandler
可以认为每个Channel-Handler
的实例都类似于一种为了响应特定事件而被执行的回调。Netty使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。这些动作可能是:
- 记录日志;
- 数据转换;
- 流控制;
- 应用程序逻辑。
Netty是一个网络编程框架,所以事件是按照它们与入站或出站数据流的相关性进行分类的。可能由入站数据或者相关的状态更改而触发的事件包括:
- 连接已被激活或者连接失活;
- 数据读取;
- 用户事件;
- 错误事件。
出站事件是未来将会触发的某个动作的操作结果,这些动作包括:
- 打开或者关闭到远程节点的连接;
- 将数据写到或者冲刷到套接字。
ChannelHandler
类中的某个用户实现的方法。
1.1.5 汇总
1.Future、回调和ChannelHandler
Netty的异步编程模型是建立在Future
和回调的概念之上的, 而将事件派发到ChannelHandler
的方法则发生在更深的层次上。拦截操作以及高速地转换入站数据和出站数据,都只需要你提供回调或者利用操作所返回的Future
。这使得链接操作变得既简单又高效,并且促进了可重用的通用代码的编写。
2.选择器、事件和EventLoop
Netty通过触发事件将Selector
从应用程序中抽象出来,消除了所有本来将需要手动编写的派发代码。在内部,将会为每个Channel
分配一个EventLoop
,用以处理所有事件,包括:
- 注册感兴趣的事件;
- 将事件派发给
ChannelHandler
; - 安排进一步的动作。
EventLoop
本身只由一个线程驱动,其处理了一个Channel
的所有I/O事件,并且在该EventLoop
的整个生命周期内都不会改变。这个简单而强大的设计消除了你可能有的在你的ChannelHandler
中需要进行同步的任何顾虑,因此,你可以专注于提供正确的逻辑,用来在有感兴趣的数据要处理的时候执行。
2.1 编写Echo服务器
所有的Netty服务器都需要以下两部分。
- 至少一个ChannelHandler——该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
- 引导——这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。
- 下面我们将描述Echo服务器的业务逻辑以及引导代码。
2.1.1 ChannelHandler和业务逻辑
因为你的Echo服务器会响应传入的消息,所以它需要实现ChannelInboundHandler
接口,用来定义响应入站事件的方法。这个简单的应用程序只需要用到少量的这些方法,所以继承Channel-InboundHandlerAdapter
类也就足够了,它提供了ChannelInboundHandler
的默认实现。
我们感兴趣的方法是:
channelRead()
——对于每个传入的消息都要调用;channelReadComplete()
——通知ChannelInboundHandler
最后一次对channel-Read()
的调用是当前批量读取中的最后一条消息;exceptionCaught()
——在读取操作期间,有异常抛出时会调用。
@Sharable ⇽--- 标示一个Channel- Handler可以被多个Channel安全地共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(
"Server received: " + in.toString(CharsetUtil.UTF_8)); ⇽--- 将消息记录到控制台
ctx.write(in); ⇽--- 将接收到的消息写给发送者,而不冲刷出站消息
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE); ⇽--- 将未决消息冲刷到远程节点,并且关闭该Channel
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace(); ⇽--- 打印异常栈跟踪
ctx.close(); ⇽--- 关闭该Channel
}
}
目前,请记住下面这些关键点:
- 针对不同类型的事件来调用
ChannelHandler
; - 应用程序通过实现或者扩展
ChannelHandler
来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑; - 在架构上,
ChannelHandler
有助于保持业务逻辑与网络处理代码的分离。
2.1.2 引导服务器
在讨论过由EchoServerHandler
实现的核心业务逻辑之后,我们现在可以探讨引导服务器本身的过程了,具体涉及以下内容:
- 绑定到服务器将在其上监听并接受传入连接请求的端口;
- 配置
Channel
,以将有关的入站消息通知给EchoServerHandler
实例。
注意:NIO传输大多数时候指的就是TCP传输。
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) {
System.err.println(
"Usage: " + EchoServer.class.getSimpleName() +
" ");
}
int port = Integer.parseInt(args[0]); ⇽--- 设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
new EchoServer(port).start(); ⇽--- 调用服务器的start()方法
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup(); ⇽--- ➊ 创建Event-LoopGroup
try {
ServerBootstrap b = new ServerBootstrap(); ⇽--- ❷ 创建Server-Bootstrap
b.group(group)
.channel(NioServerSocketChannel.class) ⇽--- ❸ 指定所使用的NIO传输Channel
.localAddress(new InetSocketAddress(port)) ⇽--- ❹ 使用指定的端口设置套接字地址
.childHandler(new ChannelInitializer(){ ⇽--- ❺添加一个EchoServer-
Handler到子Channel的ChannelPipeline
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(serverHandler);[4] ⇽--- EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
}
});
ChannelFuture f = b.bind().sync(); ⇽--- ❻ 异步地绑定服务器;调用sync()方法阻塞等待直到绑定完成
f.channel().closeFuture().sync(); ⇽--- ❼ 获取Channel的CloseFuture,并且阻塞当前线程直到它完成
} finally {
group.shutdownGracefully().sync(); ⇽--- ❽ 关闭EventLoopGroup,释放所有的资源
}
}
}
在➋处,你创建了一个ServerBootstrap
实例。因为你正在使用的是NIO传输,所以你指定了NioEventLoopGroup
➊来接受和处理新的连接,并且将Channel
的类型指定为NioServer-SocketChannel
➌。在此之后,你将本地地址设置为一个具有选定端口的InetSocket-Address
➍。服务器将绑定到这个地址以监听新的连接请求。
在➎处,你使用了一个特殊的类——ChannelInitializer
。这是关键。当一个新的连接被接受时,一个新的子Channel
将会被创建,而ChannelInitializer
将会把一个你的EchoServerHandler
的实例添加到该Channel
的ChannelPipeline
中。正如我们之前所解释的,这个ChannelHandler
将会收到有关入站消息的通知。
虽然NIO是可伸缩的,但是其适当的尤其是关于多线程处理的配置并不简单。Netty的设计封装了大部分的复杂性,而且我们将在第3章中对相关的抽象(EventLoopGroup
、Socket-Channel
和ChannelInitializer
)进行详细的讨论。
接下来你绑定了服务器➏,并等待绑定完成。(对sync()
方法的调用将导致当前Thread
阻塞,一直到绑定操作完成为止)。在➐处,该应用程序将会阻塞等待直到服务器的Channel
关闭(因为你在Channel
的Close Future
上调用了sync()
方法)。然后,你将可以关闭EventLoopGroup
,并释放所有的资源,包括所有被创建的线程➑。
与此同时,让我们回顾一下你刚完成的服务器实现中的重要步骤。下面这些是服务器的主要代码组件:
EchoServerHandler
实现了业务逻辑;main()
方法引导了服务器;
引导过程中所需要的步骤如下:
- 创建一个
ServerBootstrap
的实例以引导和绑定服务器; - 创建并分配一个
NioEventLoopGroup
实例以进行事件的处理,如接受新连接以及读/写数据; - 指定服务器绑定的本地的
InetSocketAddress
; - 使用一个
EchoServerHandler
的实例初始化每一个新的Channel
; - 调用
ServerBootstrap.bind()
方法以绑定服务器。
2.2 编写Echo客户端
Echo客户端将会:
(1)连接到服务器;
(2)发送一个或者多个消息;
(3)对于每个消息,等待并接收从服务器发回的相同的消息;
(4)关闭连接。
编写客户端所涉及的两个主要代码部分也是业务逻辑和引导,和你在服务器中看到的一样。
@Sharable ⇽--- 标记该类的实例可以被多个Channel共享
public class EchoClientHandler extends
SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", ⇽--- 当被通知Channel是活跃的时候,发送一条消息
CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println( ⇽--- 记录已接收消息的转储
"Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ⇽--- 在发生异常时,记录错误并关闭Channel
Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
首先,你重写了channelActive()
方法,其将在一个连接建立时被调用。这确保了数据将会被尽可能快地写入服务器,其在这个场景下是一个编码了字符串"Netty rocks!"
的字节缓冲区。
接下来,你重写了channelRead0()
方法。每当接收数据时,都会调用这个方法。需要注意的是,由服务器发送的消息可能会被分块接收。也就是说,如果服务器发送了5字节,那么不能保证这5字节会被一次性接收。即使是对于这么少量的数据,channelRead0()
方法也可能会被调用两次,第一次使用一个持有3字节的ByteBuf
(Netty的字节容器),第二次使用一个持有2字节的ByteBuf
。作为一个面向流的协议,TCP保证了字节数组将会按照服务器发送它们的顺序被接收。
SimpleChannelInboundHandler与ChannelInboundHandler的区别?
你可能会想:为什么我们在客户端使用的是SimpleChannelInboundHandler
,而不是在Echo- ServerHandler
中所使用的ChannelInboundHandlerAdapter
呢?这和两个因素的相互作用有关:业务逻辑如何处理消息以及Netty如何管理资源。
在客户端,当channelRead0()
方法完成时,你已经有了传入消息,并且已经处理完它了。当该方法返回时,SimpleChannelInboundHandler
负责释放指向保存该消息的ByteBuf
的内存引用。
在EchoServerHandler
中,你仍然需要将传入消息回送给发送者,而write()
操作是异步的,直到channelRead()
方法返回后可能仍然没有完成。为此,EchoServerHandler
扩展了ChannelInboundHandlerAdapter
,其在这个时间点上不会释放消息。消息在EchoServerHandler
的channelReadComplete()
方法中,当writeAndFlush()
方法被调用时被释放
引导客户端
引导客户端类似于引导服务器,不同的是,客户端是使用主机和端口参数来连接远程地址,也就是这里的Echo服务器的地址,而不是绑定到一个一直被监听的端口。
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
Bootstrap b = new Bootstrap(); ⇽--- 指定EventLoopGroup以处理客户端事件;需要适用于NIO的实现
b.group(group)
.channel(NioSocketChannel.class) ⇽--- 适用于NIO传输的Channel类型
.remoteAddress(new InetSocketAddress(host, port)) ⇽--- 设置服务器的InetSocketAddr-ess
![](/api/storage/getbykey/screenshow?key=17043add7e9c14a5d3f7) .handler(new ChannelInitializer<SocketChannel>() { ⇽--- 在创建Channel时,向ChannelPipeline中添加一个Echo-ClientHandler实例
@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();
}
}
让我们回顾一下这一节中所介绍的要点:
- 为初始化客户端,创建了一个
Bootstrap
实例; - 为进行事件处理分配了一个
NioEventLoopGroup
实例,其中事件处理包括创建新的连接以及处理入站和出站数据; - 为服务器连接创建了一个
InetSocketAddress
实例; - 当连接被建立时,一个
EchoClientHandler
实例会被安装到(该Channel
的)ChannelPipeline
中; - 在一切都设置完成后,调用
Bootstrap.connect()
方法连接到远程节点;
其他例子:
- public class Server {
- private int port;
- public Server(int port) {
- this.port = port;
- }
- public void run() {
- EventLoopGroup bossGroup = new NioEventLoopGroup(); //用于处理服务器端接收客户端连接
- EventLoopGroup workerGroup = new NioEventLoopGroup(); //进行网络通信(读写)
- try {
- ServerBootstrap bootstrap = new ServerBootstrap(); //辅助工具类,用于服务器通道的一系列配置
- bootstrap.group(bossGroup, workerGroup) //绑定两个线程组
- .channel(NioServerSocketChannel.class) //指定NIO的模式
- .childHandler(new ChannelInitializer<SocketChannel>() { //配置具体的数据处理方式
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new ServerHandler());
- }
- })
- /**
- * 对于ChannelOption.SO_BACKLOG的解释:
- * 服务器端TCP内核维护有两个队列,我们称之为A、B队列。客户端向服务器端connect时,会发送带有SYN标志的包(第一次握手),服务器端
- * 接收到客户端发送的SYN时,向客户端发送SYN ACK确认(第二次握手),此时TCP内核模块把客户端连接加入到A队列中,然后服务器接收到
- * 客户端发送的ACK时(第三次握手),TCP内核模块把客户端连接从A队列移动到B队列,连接完成,应用程序的accept会返回。也就是说accept
- * 从B队列中取出完成了三次握手的连接。
- * A队列和B队列的长度之和就是backlog。当A、B队列的长度之和大于ChannelOption.SO_BACKLOG时,新的连接将会被TCP内核拒绝。
- * 所以,如果backlog过小,可能会出现accept速度跟不上,A、B队列满了,导致新的客户端无法连接。要注意的是,backlog对程序支持的
- * 连接数并无影响,backlog影响的只是还没有被accept取出的连接
- */
- .option(ChannelOption.SO_BACKLOG, 128) //设置TCP缓冲区
- .option(ChannelOption.SO_SNDBUF, 32 * 1024) //设置发送数据缓冲大小
- .option(ChannelOption.SO_RCVBUF, 32 * 1024) //设置接受数据缓冲大小
- .childOption(ChannelOption.SO_KEEPALIVE, true); //保持连接
- ChannelFuture future = bootstrap.bind(port).sync();
- future.channel().closeFuture().sync();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- public static void main(String[] args) {
- new Server(8379).run();
- }
- }
- public class ServerHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- //do something msg
- ByteBuf buf = (ByteBuf)msg;
- byte[] data = new byte[buf.readableBytes()];
- buf.readBytes(data);
- String request = new String(data, "utf-8");
- System.out.println("Server: " + request);
- //写给客户端
- String response = "我是反馈的信息";
- ctx.writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
- //.addListener(ChannelFutureListener.CLOSE);
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
客户端:
- public class Client {
- public static void main(String[] args) throws InterruptedException {
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- Bootstrap bootstrap = new Bootstrap();
- bootstrap.group(workerGroup)
- .channel(NioSocketChannel.class)
- .handler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel socketChannel) throws Exception {
- socketChannel.pipeline().addLast(new ClientHandler());
- }
- });
- ChannelFuture future = bootstrap.connect("127.0.0.1", 8379).sync();
- future.channel().writeAndFlush(Unpooled.copiedBuffer("777".getBytes()));
- future.channel().closeFuture().sync();
- workerGroup.shutdownGracefully();
- }
- }
- public class ClientHandler extends ChannelHandlerAdapter {
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
- try {
- ByteBuf buf = (ByteBuf) msg;
- byte[] data = new byte[buf.readableBytes()];
- buf.readBytes(data);
- System.out.println("Client:" + new String(data).trim());
- } finally {
- ReferenceCountUtil.release(msg);
- }
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
TCP粘包、拆包问题
熟悉TCP编程的可能都知道,无论是服务器端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包/拆包机制。
TCP是一个“流”协议,所谓流就是没有界限的遗传数据。大家可以想象一下,如果河水就好比数据,他们是连成一片的,没有分界线,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的具体情况进行包的划分,也就是说,在业务上一个完整的包可能会被TCP分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送出去,这就是所谓的粘包/拆包问题。
解决方案:
①消息定长,例如每个报文的大小固定为200个字节,如果不够,空位补空格。
②在包尾部增加特殊字符进行分割,例如加回车等。
③将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务逻辑的处理。
Netty中解决TCP粘包/拆包的方法:
①分隔符类:DelimiterBasedFrameDecoder(自定义分隔符)
②定长:FixedLengthFrameDecoder
![](https://i-blog.csdnimg.cn/blog_migrate/c15e21795938ac46ca15e6f50d678f88.png)
Netty编解码技术
通常我们也习惯将编码(Encode)成为序列化,它将数据序列化为字节数组,用于网络传输、数据持久化或者其他用途。反之,解码(Decode)/反序列化(deserialization)
把从网络、磁盘等读取的字节数组还原成原始对象(通常是原始对象的拷贝),以方便后续的业务逻辑操作。进行远程跨进程服务调用时(例如RPC调用),需要使用特定的编解码技术,对需要进行网络传输的对象做编码或者解码,以便完成远程调用。
主流的编解码框架:
①JBoss的Marshalling包
②google的Protobuf
③基于Protobuf的Kyro
④MessagePack框架
在initChannle方法中加入socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
socketChannel.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
socketChannel.pipeline().addLast(new ServerHandler());