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

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));
1
案例

其他代码不变,在此处加入这行代码! 
服务端: 

这里写图片描述

客户端: 

这里写图片描述


运行结果

服务端: 

客户端: 


总结

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

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

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


案例

其他代码相似,注意红圈! 
服务端: 

这里写图片描述

客户端: 

这里写图片描述
运行结果

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

这里写图片描述
总结

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

将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段。
--------------------- 
作者:詠聖wK 
来源:CSDN 
原文:https://blog.csdn.net/yswKnight/article/details/79363668 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值