目录
Netty内置的序列化器
- JBoss Marshalling 和 Protocol Buffers
集成第三方 MessagePack
- LengthFieldBasedFrame 详解
- maxFrameLength:表示的是包的最大长度
- lengthFieldOffset:指的是长度域的偏移量,表示跳过指定个数字节之后的才是长度域;
- lengthFieldLength:记录该帧数据长度的字段,也就是长度域本身的长度;
- lengthAdjustment:长度的一个修正值,可正可负,Netty 在读取到数据包的长度值 N 后, 认为接下来的 N 个字节都是需要读取的,但是根据实际情况,有可能需要增加 N 的值,也有可能需要减少 N 的值,具体增加多少,减少多少,写在这个参数里;
- initialBytesToStrip:从数据帧中跳过的字节数,表示得到一个完整的数据包之后,扔掉这个数据包中多少字节数,才是后续业务实际需要的业务数据。
- failFast:如果为 true,则表示读取到长度域,TA 的值的超过 maxFrameLength,就抛出一个 TooLongFrameException,而为 false 表示只有当真正读取完长度域的值表示的字节之后, 才会抛出 TooLongFrameException,默认情况下设置为 true,建议不要修改,否则可能会造成内存溢出。
pom
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
创建实体类
- 注意要添加@Message注解
package org.example.msgpack;
import org.msgpack.annotation.Message;
@Message
public class User {
private String userName;
private int age;
public User() {
}
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
创建解码器
package org.example.msgpack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;
import java.util.List;
//基于MessagePack的解码器,反序列化
public class MsgPackDecoder extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
//获取消息长度
int length = byteBuf.readableBytes();
//创建字节数据,并定义长度
byte[] bytes = new byte[length];
//将byteBuf转换为字节数组
byteBuf.getBytes(byteBuf.readerIndex(), bytes);
MessagePack messagePack = new MessagePack();
//将字节数组解码成User对象,并添加到解码器集合中
list.add(messagePack.read(bytes, User.class));
}
}
创建编码器
package org.example.msgpack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;
//基于MessagePack的编码器,序列化
public class MsgPackEncoder extends MessageToByteEncoder<User> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, User user, ByteBuf byteBuf) throws Exception {
MessagePack messagePack = new MessagePack();
//将User对象编码成字节数组
byte[] write = messagePack.write(user);
//将字节数组写入ByteBuf
byteBuf.writeBytes(write);
}
}
创建客户端handler
package org.example.msgpack;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
import java.util.concurrent.atomic.AtomicInteger;
public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private AtomicInteger counter = new AtomicInteger(0);
//客户端读取到网络数据后的处理
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
System.out.println("客户端接收的消息:" + byteBuf.toString(CharsetUtil.UTF_8) + ", count = " + counter.incrementAndGet());
}
//channel活跃时,做业务处理
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//分5次写入,一起刷新
for (int i = 0; i < 5; i++) {
User user = new User();
user.setUserName("user" + i);
user.setAge(i);
System.out.println("发送:" + user);
ctx.write(user);
}
ctx.flush();
}
}
创建客户端
package org.example.msgpack;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
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.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.LineBasedFrameDecoder;
import java.net.InetSocketAddress;
public class Client {
public static void main(String[] args) throws InterruptedException {
//线程组
EventLoopGroup group = new NioEventLoopGroup();
//客户端必备
Bootstrap b = new Bootstrap();
try {
b.group(group)//将线程组传入
.channel(NioSocketChannel.class)//指定使用NIO进行网络传输
.remoteAddress(new InetSocketAddress("127.0.0.1", 9001))//配置要连接服务器的ip地址和端口
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
//通过LengthFieldPrepender计算报文长度,把这个值存入报文头,并指定大小2字节,2的16次方,即长度不能超过65536
channel.pipeline().addLast(new LengthFieldPrepender(2));
//对服务器应答报文解码,解决粘包半包,服务端的应答报文这里使用回车换行符进行分隔
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
//对发送的数据做编码-序列化
channel.pipeline().addLast(new MsgPackEncoder());
channel.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture future = b.connect().sync();
System.out.println("已连接到服务器。。。");
future.channel().closeFuture().sync();
} finally {
//优雅关闭线程组
group.shutdownGracefully().sync();
}
}
}
创建服务端handler
package org.example.msgpack;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.concurrent.atomic.AtomicInteger;
public class ServerHandler extends ChannelInboundHandlerAdapter {
private AtomicInteger counter = new AtomicInteger(0);
//服务端读取到网络数据后的处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//将上一个handler解码后的数据强转
User user = (User) msg;
System.out.println("服务端接收的数据:" + user + ", count = " + counter.incrementAndGet());
//服务器应答消息,使用回车换行符分隔
String resp = "user :" + user.getUserName() + System.getProperty("line.separator");
ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
//传递消息至下一个处理器
ctx.fireChannelRead(user);
}
}
创建服务端
package org.example.msgpack;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
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.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.net.InetSocketAddress;
public class Server {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
//服务端启动必须
ServerBootstrap b = new ServerBootstrap();
try {
b.group(group).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(9001))
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
//最大长度65536,偏移量0,长度值长度2字节,长度修正值0,丢弃2字节(把存放长度值的2字节消息头丢弃)
channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(65536,
0, 2, 0, 2));
//解码
channel.pipeline().addLast(new MsgPackDecoder());
channel.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture f = b.bind().sync();
System.out.println("服务器启动完成,等待客户端的连接和数据.....");
f.channel().closeFuture().sync();
} finally {
//优雅关闭线程组
group.shutdownGracefully().sync();
}
}
}
启动服务端,再启动客户端
- 服务端启动成功
- 客户端打印
- 服务端打印