tcp粘包,拆包

10 篇文章 0 订阅

tcp 在发消息的时候,把多个数据量小的数据,合并成一个大的数据块,这个叫封包;
例如:客户端发送了2个数据包,一个a,一个b给到服务端,由于服务端一次读取到字节数是不确定的,可能有以下4种情况;
1、服务端分别读取到了a和b,这样是正常的情况;
2.服务端一次读取到了a和b,那么这样就是粘包;
3.服务第一次读取到了a和b的一部分数据,第二次读取到了b的剩余部分,这就叫拆包;
4.服务端第一次读取到了a的一部分,第二次读取到了a的剩余部分和b的完整数据,这也叫拆包;

我们来看下案例
 



            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.20.Final</version>
            </dependency>

package com.example.demo.controller;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

//服务端
public class MyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup boss=new NioEventLoopGroup();
        EventLoopGroup worker=new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new MyServerInitializer());
            //绑定端口
            ChannelFuture sync = bootstrap.bind(7777).sync();
            //监听关闭
            sync.channel().closeFuture().sync();
        }finally {
            //优雅关闭
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;

import java.time.LocalDateTime;
import java.util.UUID;

//服务端业务处理程序
public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {


    private int count;
    //读取客户端传过来的数据
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        //可读字节
        byte[]bytes=new byte[byteBuf.readableBytes()];
        //读取到字节数据中
        byteBuf.readBytes(bytes);
        System.out.println("服务器接收数据:"+new String(bytes, CharsetUtil.UTF_8));
        this.count++;
        System.out.println("服务器接收数据量:"+this.count);
        //给客户端回写数据
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer(UUID.randomUUID().toString()+"  ",CharsetUtil.UTF_8));

    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

package com.example.demo.controller;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

//服务端初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new MyServerHandler());

    }
}
package com.example.demo.controller;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

//客户端
public class MyClient {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup boss=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(boss)
                    .channel(NioSocketChannel.class)
                    .handler(new MyClientInitializer());
            //绑定端口
            ChannelFuture sync = bootstrap.connect("127.0.0.1",7777);
            //监听关闭
            sync.channel().closeFuture().sync();
        }finally {
            //优雅关闭
            boss.shutdownGracefully();
        }
    }
}
package com.example.demo.controller;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

//客户端初始化器
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new MyClientHandler());

    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.nio.charset.Charset;

//客户端处理程序
public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private int count;
    //给服务端写数据
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //发送10次数据
        for (int i = 0; i < 10; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("你好"+i+" ",CharsetUtil.UTF_8));
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    //读取服务器传过来的数据
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        byte[]bytes=new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        System.out.println("客户端接收消息是:"+new String(bytes,CharsetUtil.UTF_8));
        this.count++;
        System.out.println("客户端接收数据量是:"+this.count);
    }
}

在这里可以看到,服务器一次性接收了所有,客户端只接收了一次

 我们在启动一个客户端

可以看到,12粘包,345粘包,67粘包,89粘包

 

接下来我们通过协议包和自定义编码解码器来解决,粘包,拆包问题

package com.example.demo.controller;

//协议包
public class MessageProtocol {

    //长度
    private int len;

    //内容
    private byte[]content;

    public void setLen(int len) {
        this.len = len;
    }

    public void setContent(byte[] content) {
        this.content = content;
    }

    public int getLen() {
        return len;
    }

    public byte[] getContent() {
        return content;
    }
}
package com.example.demo.controller;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

//服务端
public class MyServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup boss=new NioEventLoopGroup();
        EventLoopGroup worker=new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();
            bootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new MyServerInitializer());
            //绑定端口
            ChannelFuture sync = bootstrap.bind(7777).sync();
            //监听关闭
            sync.channel().closeFuture().sync();
        }finally {
            //优雅关闭
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;

import java.time.LocalDateTime;
import java.util.UUID;

//服务端业务处理程序
public class MyServerHandler extends SimpleChannelInboundHandler<MessageProtocol> {


    private int count;



    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    //读取客户端传过来的数据
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
        //当我们读取数据的时候 需要先在初始化器类加入 解码器
        int len=messageProtocol.getLen();
        byte[]content=messageProtocol.getContent();
        System.out.println();
        System.out.println("服务端接收内容如下");
        System.out.println("长度:"+len);
        System.out.println("内容:"+new String(content,CharsetUtil.UTF_8));
        this.count++;
        System.out.println("消息包数量:"+count);

        //给客户端回写数据 当我们写入数据的时候 就需要在初始化器那个类 加入编码器
        String str=UUID.randomUUID().toString();
        int length=str.length();
        byte[]bytes=str.getBytes();
        MessageProtocol m=new MessageProtocol();
        m.setLen(length);
        m.setContent(bytes);
        channelHandlerContext.writeAndFlush(m);
        System.out.println();
    }
}
package com.example.demo.controller;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

//服务端初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //加入解码器
        pipeline.addLast(new MyMessageDecoder());
        //加入编码器
        pipeline.addLast(new MyMessageEncoder());
        //加入业务处理程序
        pipeline.addLast(new MyServerHandler());

    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.ReplayingDecoder;

import java.util.List;
//解码器
public class MyMessageDecoder extends ReplayingDecoder<Void> {


    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        System.out.println("解码器被调用");
        //解析字节缓冲区
        int len=byteBuf.readInt();
        byte[]bytes=new byte[len];
        byteBuf.readBytes(bytes);
        //封装协议包对象 传递给下一个handler处理
        MessageProtocol messageProtocol=new MessageProtocol();
        messageProtocol.setLen(len);
        messageProtocol.setContent(bytes);
        list.add(messageProtocol);
    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
//编码器
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol> {



    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol, ByteBuf byteBuf) throws Exception {
        System.out.println("编码器被调用");
        //把协议包写入 字节缓冲区
        byteBuf.writeInt(messageProtocol.getLen());
        byteBuf.writeBytes(messageProtocol.getContent());
    }
}
package com.example.demo.controller;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

//客户端
public class MyClient {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup boss=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(boss)
                    .channel(NioSocketChannel.class)
                    .handler(new MyClientInitializer());
            //绑定端口
            ChannelFuture sync = bootstrap.connect("127.0.0.1",7777);
            //监听关闭
            sync.channel().closeFuture().sync();
        }finally {
            //优雅关闭
            boss.shutdownGracefully();
        }
    }
}
package com.example.demo.controller;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.nio.charset.Charset;

//客户端处理程序
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol> {

    private int count;
    //给服务端写数据
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //当我们写入数据的时候 就需要先在初始化器类 加入编码器
        for (int i = 0; i <5 ; i++) {
            String str="你好啊";
            byte[]content=str.getBytes();
            int len=content.length;
            MessageProtocol messageProtocol=new MessageProtocol();
            messageProtocol.setContent(content);
            messageProtocol.setLen(len);
            ctx.writeAndFlush(messageProtocol);
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageProtocol messageProtocol) throws Exception {
        System.out.println();
        //当我们读取数据的时候 就需要在初始化器类 先加入解码器
        System.out.println("客户端接收到内容如下");
        System.out.println("长度:"+messageProtocol.getLen());
        System.out.println("内容:"+new String(messageProtocol.getContent(),CharsetUtil.UTF_8));
        this.count++;
        System.out.println("消息包数量:"+this.count);
        System.out.println();
    }
}
package com.example.demo.controller;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

//客户端初始化器
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //加入编码器
        pipeline.addLast(new MyMessageEncoder());
        //加入解码器
        pipeline.addLast(new MyMessageDecoder());
        //业务处理程序
        pipeline.addLast(new MyClientHandler());

    }
}

可以看到客户端给服务器发送了5次,服务器接收了5次

 服务器给客户端回写了5次,客户端接收了5次

 这样就解决了粘包,拆包问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值