MessagePack的特点如下:
- 编解码高效,性能高;
- 序列化之后的码流小;
- 支持跨语言
7.1 MessagePack Java Api 介绍
7.1.1 导入相关jar
<properties>
<msgpack.version>0.6.12</msgpack.version>
</properties>
<!-- MessagePack dependency -->
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>${msgpack.version}</version>
</dependency>
MsgPackEncoder.java
package com.viagra.chapter06.MessagePack;
import org.msgpack.MessagePack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* @Auther: viagra
* @Date: 2019/7/30 22:41
* @Description:
*/
public class MsgPackEncoder extends MessageToByteEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
// 创建MessagePack对象
MessagePack msgpack = new MessagePack();
// 将对象编码为MessagePack格式的字节数组
byte[] raw = msgpack.write(msg);
// 将字节数组写入到ByteBuf中
out.writeBytes(raw);
}
}
MsgPackDecoder.java
package com.viagra.chapter06.MessagePack;
import org.msgpack.MessagePack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
/**
* @Auther: viagra
* @Date: 2019/7/31 15:39
* @Description:
*/
public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx , ByteBuf msg, List<Object> out) throws Exception {
// // 从数据报msg中(这里的数据类型为ByteBuf,因为Netty的通信基于ByteBuf对象)
final byte[] array;
final int length = msg.readableBytes();
array = new byte[length];
// /**
// * 这里使用的是ByteBuf的getBytes方法来将ByteBuf对象转换为字节数组,前面是使用readBytes,直接传入一个接收的字节数组参数即可
// * 这里的参数比较多,第一个参数是index,关于readerIndex,说明如下:
// * ByteBuf是通过readerIndex跟writerIndex两个位置指针来协助缓冲区的读写操作的,具体原理等到Netty源码分析时再详细学习一下
// * 第二个参数是接收的字节数组
// * 第三个参数是dstIndex the first index of the destination
// * 第四个参数是length the number of bytes to transfer
// */
msg.getBytes(msg.readerIndex(), array, 0, length);
// // 创建一个MessagePack对象
MessagePack msgpack = new MessagePack();
// // 解码并添加到解码列表out中
out.add(msgpack.read(array));
}
}
EchoServer.java
package com.viagra.chapter06.MessagePack;
/**
* @Auther: viagra
* @Date: 2019/7/30 07:11
* @Description:
*/
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加MesspagePack×××
ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder());
// 添加MessagePack编码器
ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());
// 添加业务处理handler
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args != null && args.length > 0) {
try {
port = Integer.valueOf(port);
} catch (NumberFormatException e) {
// TODO: handle exception
}
}
new EchoServer().bind(port);
}
}
EchoServerHandler.java
package com.viagra.chapter06.MessagePack;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Auther: viagra
* @Date: 2019/7/30 07:17
* @Description:
*/
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("服务器接收msgpack消息 : "+msg+"");
// 原路返回给客户端
ctx.writeAndFlush(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
EchoClient.java
package com.viagra.chapter06.MessagePack;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
/**
* @Auther: viagra
* @Date: 2019/7/31 20:36
* @Description:
*/
public class EchoClient {
public void connect(String host, int port, int sendNumber) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
// 设置TCP连接超时时间
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加MesspagePack×××
ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder());
// 添加MessagePack编码器
ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());
// 添加业务处理handler
ch.pipeline().addLast(new EchoClientHandler(sendNumber));
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args != null && args.length > 0) {
try {
port = Integer.valueOf(port);
} catch (NumberFormatException e) {
// 采用默认值
}
}
int sendNumber = 1000;
new EchoClient().connect("localhost", port, sendNumber);
}
}
EchoClientHandler.java
package com.viagra.chapter06.MessagePack;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Auther: viagra
* @Date: 2019/7/31 20:50
* @Description:
*/
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
// sendNumber为写入发送缓冲区的对象数量
private int sendNumber;
public EchoClientHandler(int sendNumber) {
this.sendNumber = sendNumber;
}
/**
* 构建长度为userNum的User对象数组
* @param userNum
* @return
*/
private User[] getUserArray(int userNum) {
User[] users = new User[userNum];
User user = null;
for(int i = 0; i < userNum; i++) {
user = new User();
user.setName("ABCDEFG --->" + i);
user.setAge(i);
users[i] = user;
}
return users;
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
User[] users = getUserArray(sendNumber);
for (User user : users) {
ctx.writeAndFlush(user);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Client receive the msgpack message : " + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
User.java
package com.viagra.chapter06.MessagePack;
import org.msgpack.annotation.Message;
import java.io.Serializable;
import java.nio.ByteBuffer;
/**
* @Auther: viagra
* @Date: 2019/7/31 21:06
* @Description:
*/
@Message
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
7.1.2 测试
当EchoClient.java中的sendNumber为1时,服务端和客户端都是正常工作的,此时,服务端和客户端的输出分别如下:
服务端:
Server receive the msgpack message : ["ABCDEFG --->0",0]
客户端:
Client receive the msgpack message : ["ABCDEFG --->0",0]
但是当sendNumber数字很大时,就不能正常工作了,比如可以设置为1000,此时输出结果如下:
服务端:
Server receive the msgpack message : ["ABCDEFG --->0",0]
Server receive the msgpack message : ["ABCDEFG --->1",1]
Server receive the msgpack message : ["ABCDEFG --->3",3]
...省略输出...
Server receive the msgpack message : ["ABCDEFG --->146",146]
Server receive the msgpack message : 70
Server receive the msgpack message : ["ABCDEFG --->156",156]
Server receive the msgpack message : ["ABCDEFG --->157",157]
...省略输出...
客户端:
Client receive the msgpack message : ["ABCDEFG --->0",0]
Client receive the msgpack message : 62
Client receive the msgpack message : 68
显然运行结果跟预期的不太一样,这是因为出现了TCP粘包问题。
7.2 粘包问题解决方案
在前面代码的基础上,只需要对EchoServer.java和EchoClient.java中的代码进行修改即可。
EchoServer.java
package com.viagra.chapter06.updatedMessagePack;
/**
* @Auther: viagra
* @Date: 2019/7/30 07:11
* @Description:
*/
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
/**
* @author lilinfeng
* @date 2014年2月14日
* @version 1.0
*/
public class EchoServer {
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加长度字段×××
// 在MessagePack×××之前增加LengthFieldBasedFrameDecoder,用于处理半包消息
// 它会解析消息头部的长度字段信息,这样后面的MsgpackDecoder接收到的永远是整包消息
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
// 添加MesspagePack×××
ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder());
// 添加长度字段编码器
// 在MessagePack编码器之前增加LengthFieldPrepender,它将在ByteBuf之前增加2个字节的消息长度字段
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
// 添加MessagePack编码器
ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());
// 添加业务处理handler
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args != null && args.length > 0) {
try {
port = Integer.valueOf(port);
} catch (NumberFormatException e) {
// TODO: handle exception
}
}
new EchoServer().bind(port);
}
}
EchoClient.java
package com.viagra.chapter06.updatedMessagePack;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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;
/**
* @Auther: viagra
* @Date: 2019/7/31 20:36
* @Description:
*/
public class EchoClient {
public void connect(String host, int port, int sendNumber) throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
// 设置TCP连接超时时间
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加长度字段×××
// 在MessagePack×××之前增加LengthFieldBasedFrameDecoder,用于处理半包消息
// 它会解析消息头部的长度字段信息,这样后面的MsgpackDecoder接收到的永远是整包消息
ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
// 添加MesspagePack×××
ch.pipeline().addLast("msgpack decoder", new MsgPackDecoder());
// 添加长度字段编码器
// 在MessagePack编码器之前增加LengthFieldPrepender,它将在ByteBuf之前增加2个字节的消息长度字段
ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
// 添加MessagePack编码器
ch.pipeline().addLast("msgpack encoder", new MsgPackEncoder());
// 添加业务处理handler
ch.pipeline().addLast(new EchoClientHandler(sendNumber));
}
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端链路关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if(args != null && args.length > 0) {
try {
port = Integer.valueOf(port);
} catch (NumberFormatException e) {
// 采用默认值
}
}
int sendNumber = 1000;
new EchoClient().connect("localhost", port, sendNumber);
}
}
7.2.1 测试
可以将EchoClient.java中sendNumber设置为1000或更大,此时服务端和客户端的输出结果跟预期的都是一样的。
测试结果为,服务端和客户端都会打印1000行的信息(假设sendNumber为1000),这里不再给出运行结果。