前言
MessagePack是一个高效的二进制序列化框架,它像JSON一样支持不同语言之间的数据交换,但是它的性能更快,序列化之后的码流也更小。
MessagePack的特点如下:
1、编解码高效,性能高
2、序列化之后的码流小
3、支持跨语言
演示案例
引入jar包
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
服务端
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
public class EchoServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
/* MessagePack编码器 继承ChannelOutboundHandlerAdapter */
ch.pipeline().addLast(new MsgPackEncoder());
/* MessagePack解码器 继承ChannelInboundHandlerAdapter */
ch.pipeline().addLast(new MsgPackDecoder());
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer().bind(8088);
}
}
EchoServerHandler
import com.wyl.netty.code6.UserInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/*
msg已经通过前一个MsgPackDecoder被反序列化为UserInfo对象,
所以EchoServerHandler直接强转为UserInfo即可
*/
UserInfo u = (UserInfo) msg;
System.out.println("server receive the msgPack message " + u);
//服务端也响应客户端,发送一个UserInfo对象
UserInfo userInfo = new UserInfo();
userInfo.setUserName("i`m server");
userInfo.setAge(20);
ctx.writeAndFlush(userInfo);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
客户端
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;
public class EchoClient {
public void client(int port, String host) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
/* MessagePack编码器 继承ChannelOutboundHandlerAdapter */
ch.pipeline().addLast(new MsgPackEncoder());
/* MessagePack解码器 继承ChannelInboundHandlerAdapter */
ch.pipeline().addLast(new MsgPackDecoder());
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoClient().client(8088, "localhost");
}
}
EchoClientHandler
import com.wyl.netty.code6.UserInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
/*
客户端发送UserInfo对象
*/
UserInfo userInfo = new UserInfo();
userInfo.setAge(18);
userInfo.setUserName("hello i`m client");
for (int i = 0; i < 1; i++) {
ctx.write(userInfo);
}
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/*
客户端接收服务端发来内容
*/
UserInfo u = (UserInfo) msg;
System.out.println("clint receive the msgPack message : " + u);
}
}
解码器
import com.wyl.netty.code6.UserInfo;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;
import java.util.List;
public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
//从msg中获取可读的字节内容,并读取到字节数组中
int len = msg.readableBytes();
byte[] array = new byte[len];
msg.getBytes(msg.readerIndex(), array, 0, len);
/*
通过MessagePack的read方法,将字节数组反序列化为UserInfo对象。
然后将UserInfo对象添加到解码列表中。
*/
MessagePack messagePack = new MessagePack();
out.add(messagePack.read(array, UserInfo.class));
}
}
编码器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;
public class MsgPackEncoder extends MessageToByteEncoder<Object> {
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
/* 通过MessagePack将UserInfo对象序列化为字节数组并写入ByteBuf中 */
MessagePack messagePack = new MessagePack();
byte[] raw = messagePack.write(msg);
out.writeBytes(raw);
}
}
UserInfo对象
需要添加@Message,表示支持MessagePack序列化
import org.msgpack.annotation.Message;
@Message
public class UserInfo {
private String userName;
private int age;
public void setUserName(String userName) {
this.userName = userName;
}
public void setAge(int age) {
this.age = age;
}
public String getUserName() {
return userName;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "UserInfo{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
运行结果
服务端
客户端
粘包、半包问题
修改客户端发送次数
因为产生粘包、半包问题,导致序列化报错
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: org.msgpack.MessageTypeException: Expected array, but got integer value
支持粘包、半包处理
修改EchoClient ,添加LengthFieldBasedFrameDecoder、LengthFieldPrepender
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 io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
public class EchoClient {
public void client(int port, String host) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
/*
自定义长度解码器,需要放在入栈消息链的头部
65535, 0, 2, 0, 2含义
int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip
maxFrameLength 65535:数据包最大长度
lengthFieldOffset 0:长度域的偏移量
lengthFieldLength 2:长度域本身的长度
lengthAdjustment 0:修正值,公式:(数据包的长度-lengthFieldOffset-lengthFieldLength-长度域的值)
initialBytesToStrip 2:从数据包中跳过的字节数
*/
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
/* 在ByteBuf之前增加2个字节的消息长度字段 */
ch.pipeline().addLast(new LengthFieldPrepender(2));
/* MessagePack编码器 */
ch.pipeline().addLast(new MsgPackEncoder());
/* MessagePack解码器 */
ch.pipeline().addLast(new MsgPackDecoder());
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoClient().client(8088, "localhost");
}
}
同样修改EchoServer
import io.netty.bootstrap.ServerBootstrap;
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.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
public class EchoServer {
public void bind(int port) throws InterruptedException {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
/*
参数含义同客户端
*/
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
ch.pipeline().addLast(new LengthFieldPrepender(2));
ch.pipeline().addLast(new MsgPackEncoder());
ch.pipeline().addLast(new MsgPackDecoder());
ch.pipeline().addLast(new EchoServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new EchoServer().bind(8088);
}
}
运行结果
客户端、服务端恢复正常通信
入栈、出站过程梳理
服务端入栈处理过程:
LengthFieldBasedFrameDecoder、MsgPackDecoder、EchoServerHandler
先处理粘包、半包问题,然后进行对象反序列化处理,最后业务EchoServerHandler读取
服务端出站处理过程:
MsgPackEncoder、LengthFieldPrepender
先进行对象序列化处理,然后指定长度域的长度
客户端入栈、出站过程与服务端类似。