Netty涉及到编解码的组件有Channel、ChannelHandler、ChannelPipe等,先大概了解下这几个组件的作用。
ChannelHandler
ChannelHandler充当了处理入站和出站数据的应用程序逻辑容器。例如,实现ChannelInboundHandler接口(或
ChannelInboundHandlerAdapter),你就可以接收入站事件和数据,这些数据随后会被你的应用程序的业务逻辑处理。当你要给连接的客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。你的业务逻辑通常写在一个或者多个ChannelInboundHandler中。ChannelOutboundHandler原理一样,只不过它是用来处理出站数据的。
ChannelPipeline
ChannelPipeline提供了ChannelHandler链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过pipeline中的一系列ChannelOutboundHandler(ChannelOutboundHandler调用是从tail到head方向逐个调用每个handler的逻辑),并被这些Handler处理,反之则称为入站的,入站只调用pipeline里的ChannelInboundHandler逻辑(ChannelInboundHandler调用是从head到tail方向逐个调用每个handler的逻辑)。
如下图
从客户端的角度触发出站是从客户端的handler的tail到head,刚好去到服务端,然后从服务端的角度来看就是入站从head到tail。
编解码实例
1、字符串编解码
在netty(三)、实现一个简单的在线聊天程序中我们如果不添加如下代码:
sc.pipeline().addLast(new StringEncoder());//对 String 对象自动编码,属于出站处理器
sc.pipeline().addLast(new StringDecoder());//把网络字节流自动解码为 String 对象,属于入站处理器
那么直接用
String msg = scanner.nextLine();
System.out.println("用户输入:"+msg);
//通过channel发送到服务器端
cf.channel().writeAndFlush(msg);
将不能发送消息,因为网络传输只能传输字节,但是加上字符串的编解码器就可以了,在发送消息的时候,内容会被StringEncoder编码为字节,在接收消息的时候,字节会被解码器StringDecoder解码为字符串。
为什么直接在pipeline中加上入站出站不会两个都经过呢?
因为我们的出站只会经过ChannelOutboundHandler,入站只会经过ChannelInboundHandler,而StringEncoder实现了ChannelOutboundHandler,StringDecoder实现了ChannelInboundHandler。所以不会两个同时经过。
2、实现自定义Long型编解码器
这里编码器继承MessageToByteEncoder,解码器继承ByteToMessageDecoder即可,代码如下:
编码器:
public class MyLongEncoder extends MessageToByteEncoder<Long>{
@Override
protected void encode(ChannelHandlerContext ctx, Long msg, ByteBuf out) throws Exception {
out.writeLong(msg);
}
}
解码器:
public class MyLongDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if(in.readableBytes()>=4) {
out.add(in.readLong());
}
}
}
在pipeline加上编解码器:
sc.pipeline().addLast(new MyLongEncoder());
sc.pipeline().addLast(new MyLongDecoder());
然后我们发送消息就可以执行如下语句:
ctx.writeAndFlush(1000l);
亲测OK!
3、实现自定义对象编解码器
有时候我们想要发送对象,如
Msg msg = new Msg();
ctx.writeAndFlush(msg);
此时我们可以用netty默认实现的对象编解码器ObjectEncoder和ObjectDecoder也可以自己实现一个,这里是作为学习了解netty的编解码器,所以就自己实现啦。其实原理也很简单,无非就是实现一个编码器把把对象转为字节数组出站,然后实现一个解码器把字节数组转为对象入站罢了。
注:对象序列化一定要实现Serializable接口。
编码器:
public class MyObjectEncoder extends MessageToByteEncoder<Object>{
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
System.out.println("对象转字节编码器开始执行");
//将对象转化成字节
ByteArrayOutputStream bops = new ByteArrayOutputStream();
ObjectOutputStream oops = new ObjectOutputStream(bops);
oops.writeObject(msg);
byte[] bytes = bops.toByteArray();
out.writeBytes(bytes);
}
}
解码器:
public class MyObjectDecoder extends ByteToMessageDecoder{
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("字节转对象解码器开始执行");
//将字节转成对象
byte[] body = new byte[in.readableBytes()];
in.readBytes(body);
//如果用in.array()获得数组可能会报如下错误:java.lang.UnsupportedOperationException: direct buffer
ByteArrayInputStream bis = new ByteArrayInputStream(body);
ObjectInputStream ois = new ObjectInputStream(bis);
Object obj = ois.readObject();
out.add(obj);
}
}
如果用in.array()获得数组可能会报如下错误:java.lang.UnsupportedOperationException: direct buffer:
- hasArray() ——- 如果ByteBuf由一个字节数组支撑,则返回true。通俗的讲:ByteBuf是堆缓冲区模式,则代表其内部存储是由字节数组支撑的。如果还没理解,可参考5.2.2章节
- array() ——- 如果ByteBuf是由一个字节数组支撑泽返回数组,否则抛出UnsupportedOperationException异常。也就是,ByteBuf是堆缓冲区模式
在pipeline
...
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new MyObjectEncoder());
sc.pipeline().addLast(new MyObjectDecoder());
sc.pipeline().addLast(new NettyServerHandler());
}
...
然后就可以直接发对象啦
4、测试对象编解码器
对象
public class Msg implements Serializable{
private static final long serialVersionUID = 7530436885643753365L;
private String username;
private String toUsername;
private String content;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getToUsername() {
return toUsername;
}
public void setToUsername(String toUsername) {
this.toUsername = toUsername;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Msg(String username, String toUsername, String content) {
super();
this.username = username;
this.toUsername = toUsername;
this.content = content;
}
@Override
public String toString() {
return "Msg [username=" + username + ", toUsername=" + toUsername + ", content=" + content + "]";
}
}
发消息
Msg msg = new Msg("客户端1", "服务器", "你是狗");
ctx.writeAndFlush(msg);
接收消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("接收到客户端的消息:"+msg);
}
测试结果:可以正常收发打印出结果。
好了,这篇文章只是编解码器的入门,以后有机会再继续深入研究下。