编码器:负责处理出站数据,将消息对象转换为字节数组。
解码器:负责处理入站数据,将字节数组转换为消息对象。
注:由于在网络通道中实际传输的都是二进制的字节序列,所以对于出站的数据,最后一个处理数据的OutboundHandler发送的数据格式必须是ByteBuf类型。
同理,对于入站消息,第一个接收消息的InboundHandler接收到的数据类型一定是ByteBuf类型。
注:Channel的Pipline中有两条Handler链,处理出站数据的OutboundHandler链和处理入站数据的InboundHandler链,出站消息经过的顺序为:tail -----> head, 入站消息经过的顺序为:head ----->tail
pipeline().addLast()在尾部添加, pipeline().addFirst()在头部添加。
Netty提供了很多开箱即用的编解码器:
StringEncoder字符串编码器StringDecoder字符串解码器ObjectEncoder对象编码器ObjectDecoder对象解码器FixedLengthFrameDecoder固定长度的解码器LineBasedFrameDecoder以换行符为结束标识的解码器DelimiterBasedFrameDecoder指定消息分隔符的解码器LengthFieldBasedFrameDecoder基于长度通用解码器
代码实战
我们用Netty来实现一个Echo服务器,其仅仅接收客户端的消息,并将收到的消息返回给客户端。
引入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
服务端 EhcoServer
public class Server {
public void start() {
NioEventLoopGroup boss = new NioEventLoopGroup(1);
NioEventLoopGroup worker = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker)
.localAddress(9999)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new EchoInboundHandler());
}
});
ChannelFuture future = null;
try {
future = bootstrap.bind().sync();
System.out.println("服务器启动成功, 监听端口:" + future.channel().localAddress());
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅关闭
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new Server().start();
}
}
public class EchoInboundHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
ctx.writeAndFlush(byteBuf);
}
}
客户端
public class Client {
Bootstrap bootstrap = new Bootstrap();
public Client(){
NioEventLoopGroup worker = new NioEventLoopGroup();
bootstrap.group(worker)
.channel(NioSocketChannel.class)
.remoteAddress("127.0.0.1", 9999);
}
public void send(String msg){
try {
Channel channel = bootstrap.connect().sync().channel();
System.out.println("客户端发送消息: " + msg);
channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
}
}
}
CASE1 不使用任何编码器发送消息
public void send(String msg){
try {
Channel channel = bootstrap.connect().sync().channel();
System.out.println("客户端发送消息: " + msg);
channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
}
}
不使用编码器,发送消息时需要手动地将消息转换成ByteBuf类型,如将字符串转换:Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)
CASE2 使用StringEncoder
public void send(String msg){
try {
Channel channel = bootstrap.connect().sync().channel();
channel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
System.out.println("客户端发送消息: " + msg);
channel.writeAndFlush(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
通过 channel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));添加String编码器后,就可以直接发送字符串类型的数据。
channel.writeAndFlush(msg);
CASE3 自定义编码器
将实体类Person转换为Json发送
@Data
public class Person {
private String name;
private int age;
}
继承MessageToMessageEncoder
public class JsonEncoder extends MessageToMessageEncoder<Person> {
@Override
protected void encode(ChannelHandlerContext ctx, Person person, List<Object> out) throws Exception {
byte[] bytes = JSON.toJSONBytes(person);
ByteBuf byteBuf = Unpooled.buffer(bytes.length);
byteBuf.writeBytes(bytes);
out.add(byteBuf);
}
}
发送实体类消息
public void send(Person person){
try {
Channel channel = bootstrap.connect().sync().channel();
channel.pipeline().addLast(new JsonEncoder());
channel.writeAndFlush(person);
} catch (InterruptedException e) {
e.printStackTrace();
}
}