Netty-搭建一个简单的Json协议消息接收发送服务

前言

上篇 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对象)。其他代码和前篇文章基本一样就不赘述了。

二、效果展示

服务端

客户端

发送消息


项目地址:https://github.com/YIminta/Netty-learning

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值