前言
上篇 Netty-搭建一个简单的消息接收发送服务 文章简单快速的搭建了一个Netty服务,但是一次发送消息内容很多的情况下就会出现半包和粘包问题,下面图片展示了半包问题,这篇我用Json协议来搭建,并解决半包粘包问题。
一、服务搭建
废话不多说直接上代码,因为是Json协议所以先创建Json协议的编码器和解码器;
@Data
public class JsonMsgDto {
private int id;
private String content;
public JsonMsgDto() {
this.id = RandomUtil.randomInt(100);
}
public static JsonMsgDto parse(String jsonStr) {
return JSONUtil.toBean(jsonStr, JsonMsgDto.class);
}
public static String format(JsonMsgDto jsonMsgDto) {
return JSONUtil.toJsonStr(jsonMsgDto);
}
public static String format(Object obj) {
return JSONUtil.toJsonStr(obj);
}
}
编码器
public class JsonMsgEncoder extends MessageToMessageEncoder<Object> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object obj, List<Object> list) throws Exception {
String json = JsonMsgDto.format(obj);
System.out.println("发送报文:" + json);
list.add(json);
}
}
解码器
public class JsonMsgDecoder extends MessageToMessageDecoder<String> {
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, String str, List<Object> list) throws Exception {
JsonMsgDto json = JsonMsgDto.parse(str);
System.out.println("收到报文:"+JsonMsgDto.format(str));
list.add(json);
}
}
服务端
public class JsonServer {
private final static Logger LOGGER = LoggerFactory.getLogger(JsonServer.class);
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 54123;
public static void main(String[] args) {
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
b.group(bossGroup, workGroup);
b.channel(NioServerSocketChannel.class);
b.localAddress(SERVER_PORT);
b.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
/**
* maxFrameLength: 解码的帧的最大长度,超过此长度的帧将被丢弃。
* lengthFieldOffset: 长度字段在帧中的偏移量,表示长度字段的起始位置。
* lengthFieldLength: 长度字段的长度,通常为 2、4、8 等字节。
* lengthAdjustment: 长度字段的值与帧的实际长度之间的偏差值。
* initialBytesToStrip: 解码后,从帧中跳过的字节数。
*/
socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));
socketChannel.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new JsonMsgDecoder());
}
});
ChannelFuture channelFuture = b.bind();
channelFuture.addListener((future) -> {
if (future.isSuccess()) {
LOGGER.info(" ========》反应器线程 回调 Json服务器启动成功,监听端口: " +
channelFuture.channel().localAddress());
}
});
channelFuture.sync();
LOGGER.info(" 调用线程执行的,Json服务器启动成功,监听端口: " +
channelFuture.channel().localAddress());
ChannelFuture closeFuture = channelFuture.channel().closeFuture();
closeFuture.sync();
} catch (Exception ex) {
ExceptionUtil.stacktraceToString(ex);
} finally {
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
客户端
public class JsonClient {
private final static Logger LOGGER = LoggerFactory.getLogger(JsonClient.class);
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 54123;
public static void main(String[] args) {
Bootstrap b = new Bootstrap();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
b.group(workGroup);
b.channel(NioSocketChannel.class);
b.remoteAddress(SERVER_IP, SERVER_PORT);
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,10000);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new LengthFieldPrepender(4));
socketChannel.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
socketChannel.pipeline().addLast(new JsonMsgEncoder());
}
});
ChannelFuture channelFuture = b.connect();
channelFuture.addListener((ChannelFuture futureListener) -> {
if (futureListener.isSuccess()) {
LOGGER.info("JsonClient客户端连接成功!");
} else {
LOGGER.info("JsonClient客户端连接失败!");
}
});
channelFuture.sync();
Channel channel = channelFuture.channel();
Scanner scanner = new Scanner(System.in);
LOGGER.info("请输入发送内容:");
// 发送回调监听
GenericFutureListener sendCallBack = future -> {
if (future.isSuccess()) {
LOGGER.info("发送成功!");
} else {
LOGGER.info("发送失败!");
}
};
while (scanner.hasNext()) {
//获取输入的内容
String next = scanner.next();
JsonMsgDto jsonMsgDto = new JsonMsgDto();
jsonMsgDto.setContent(next);
ChannelFuture writeAndFlushFuture = channel.writeAndFlush(jsonMsgDto);
writeAndFlushFuture.addListener(sendCallBack);
LOGGER.info("请输入发送内容:");
}
} catch (Exception ex) {
LOGGER.error(ExceptionUtil.stacktraceToString(ex));
}finally {
workGroup.shutdownGracefully();
}
}
}
以上的示例中,使用 LengthFieldBasedFrameDecoder 和 LengthFieldPrepender 解决半包和粘包问题,详细介绍可以看这篇 [netty]--最通用TCP黏包解决方案;
在Netty中选择的是使用ByteBuf作为底层的消息传递载体,所以我使用 StringEncoder 和 StringDecoder 字符串编解码器把ByteBuf转成字符串格式,最后再使用我们自定义的 JsonMsgDecoder 和 JsonMsgEncoder Json编解码器转成JsonDto(或者其他Java对象)。其他代码和前篇文章基本一样就不赘述了。