Netty高级(Netty5.0的用法,TCP粘包、拆包问题解决方案)

本文介绍Netty 5.0的重要更新和新特性,提供升级指南并展示具体应用案例,包括服务器端与客户端的搭建流程及解决TCP粘包、拆包问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Netty5.0的用法


《Netty官方文档》5.0中的变化和注意点

这篇文档将引领你了解netty在4.1 release版本之后所做的一系列显著升级和新特性,以便让你能把应用升级到新版本。

不像netty在3.X和4.0之间的升级变化,5.0版本虽然在设计上做出了重大突破和简化,但(在调用层面)并没有改变很多。我们尽可能让4.X版本可以平滑地升级到5.0版本,但是如果你在升级过程中遇到任何问题,请告知我们。
文章详情>>>

Netty5案例展示

Maven坐标
    <dependencies>
        <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>5.0.0.Alpha2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling -->
        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling</artifactId>
            <version>1.3.19.GA</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.jboss.marshalling/jboss-marshalling-serial -->
        <dependency>
            <groupId>org.jboss.marshalling</groupId>
            <artifactId>jboss-marshalling-serial</artifactId>
            <version>1.3.18.GA</version>
            <scope>test</scope>
        </dependency>

服务器端

public class Netty4Server {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Netty4服务器端已经启动....");
        //创建两个线程,一个负责接受客户端连接 一个负责传输数据
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        NioEventLoopGroup cGroup = new NioEventLoopGroup();
        //2.创建服务器辅助类
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(pGroup,cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024)
                //设置缓冲区与发送区大小
                .option(ChannelOption.SO_SNDBUF,32*1024).option(ChannelOption.SO_RCVBUF,32*1024)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new StringDecoder());
                        socketChannel.pipeline().addLast(new Server4Handler());

                    }
                });
        ChannelFuture cf = serverBootstrap.bind(8080).sync();
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();
        cGroup.shutdownGracefully();
    }
}

class Server4Handler extends ChannelHandlerAdapter{
    /**
     * 当通道被调用时,执行该方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接受客户端数据
        String value=(String) msg;
        System.out.println("Client Msg:"+value);
        //回复给客户端
        ctx.channel().writeAndFlush(Unpooled.wrappedBuffer("您好,主人暂时不在,有事请您留言。".getBytes()));

    }
}

客户端
public class Netty4Client {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Netty4客户端已经启动...");
        NioEventLoopGroup pGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                socketChannel.pipeline().addLast(new StringDecoder());
                socketChannel.pipeline().addLast(new Client4Handler());
            }
        });
        ChannelFuture cf = bootstrap.connect("127.0.0.1", 8080).sync();
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("yswKnight".getBytes()));
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("wangyongsheng".getBytes()));
        Thread.sleep(500);//如果在这不停一下,则会让两个消息连在一起发送出去!
        cf.channel().writeAndFlush(Unpooled.wrappedBuffer("LebronJames".getBytes()));
        //等待客户端端口号被关闭
        cf.channel().closeFuture().sync();
        pGroup.shutdownGracefully();

    }
}

class Client4Handler extends ChannelHandlerAdapter{
    /**
     * 当通道被调用时,执行该方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //接受消息
        String value=(String)msg;
        System.out.println("Server Msg:"+value);

    }
}
运行结果

服务端:
这里写图片描述
客户端:
这里写图片描述


TCP粘包、拆包问题解决方案

什么是粘包/拆包

一个完整的业务可能会被TCP拆分成多个包进行发送(拆包),
也有可能把多个小的包封装成一个大的数据包发送(粘包),
这个就是TCP的拆包和封包问题。

下面可以看一张图,是客户端向服务端发送包:
这里写图片描述
解析上图:
1. 第一种情况,Data1和Data2都分开发送到了Server端,没有产生粘包和拆包的情况。
2. 第二种情况,Data1和Data2数据粘在了一起,打成了一个大的包发送到Server端,这个情况就是粘包。
3. 第三种情况,Data2被分离成Data2_1和Data2_2,并且Data2_1在Data1之前到达了服务端,这种情况就产生了拆包。

由于网络的复杂性,可能数据会被分离成N多个复杂的拆包/粘包的情况,所以在做TCP服务器的时候就需要首先解决拆包/粘包的问题。

解决办法
方法一:

消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,
这样即使粘包了通过接收方编程实现获取定长报文也能区分。

socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(10));
案例

其他代码不变,在此处加入这行代码!
服务端:
这里写图片描述
客户端:
这里写图片描述

运行结果

服务端:
这里写图片描述
客户端:
这里写图片描述

总结

从结果可以看出,TCP将消息报文 以 每10个字节 拆开发送,但这种方法不推荐使用,因为消息长度都是固定的,读取消息时的效率特别低!

方法二:

报文添加特殊分隔符,
例如每条报文结束都添加回车换行符(例如FTP协议)
或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。

ByteBuf buf = Unpooled.copiedBuffer("_split".getBytes());
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
案例

其他代码相似,注意红圈!
服务端:
这里写图片描述
客户端:
这里写图片描述

运行结果

服务端:
这里写图片描述
客户端:
这里写图片描述

总结

从结果可以看出,按照我在消息中自定义的分隔符“_split“,将消息报文分别拆开发送;
注意:如果不写分隔符,消息将不会被发送出去!

方法三:

将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值