千家信息网

TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,这篇文章主要讲解了"TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"TCP粘包拆包的概念以及Ne
千家信息网最后更新 2024年11月19日TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例

这篇文章主要讲解了"TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例"吧!

1、TCP粘包拆包

操作系统

我们都知道,操作系统的核心是内核,独立于普通的应用程序之外,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。
为了保护用户进程不能直接访问操作内核,保证内核的安全,操作系统划分为两部分,一部分为内核空间,另一部分为用户空间。

I/O模型

为了操作系统的安全考虑,进程是无法直接访问I/O设备的,必须通过系统调用请求内核来完成I/O操作,而内核会为每个I/O设备维护一个缓冲区(buffer)

整个请求过程为:

1、用户进程发起请求,内核接受到请求之后,从I/O设备获取数据到buffer中
2、再将buffer中的数据copy到用户进程的地址空间
3、该用户进程获取到数据之后再响应客户端

粘包和拆包

同样的,操作系统通过TCP协议发送数据的时候,也会先将数据存放在缓冲区中,假设缓冲区的大小为1024个字节

粘包

如果发送的数据包比较小,远小于缓冲区的大小,TCP会将多个数据包合并为一个数据包发送,这就发生了粘包

拆包

如果发送的数据包比较大,远大于缓冲区的大小,TCP会将数据包拆分为多个数据包发送,这就发生了拆包

  • 服务端分两次读到了两个独立的数据包,分别是D1和D2,没有粘包和拆包

  • 服务端一次接收到了两个数据包,D1和D2粘在一起,发生了粘包

  • 服务端分两次读到了两个数据包,一次读到了完整的D1包和D2的部分包D2_1,第二次读到了D2剩下的包D2_2

  • 服务端分两次读到了两个数据包,一次读到了D1的部分分D1_1,第二次读到了D1的剩下的包D1_2和完整的D2包

2、TCP粘包拆包解决

对于TCP粘包拆包问题,有以下4种解决办法

(1) 消息定长,例如每个数据包的大小都为128字节,如果不够,空位补空格

(2) 客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包

(3) 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息

(4) 通过自定义协议进行粘包和拆包的处理

3、Netty粘包拆包示例

客户端向服务端发送3条消息,服务端在收到消息之后也回复客户端3条消息

客户端

public class HelloWorldClient {    private int port;    private String address;    public HelloWorldClient(int port, String address) {        this.port = port;        this.address = address;    }    public void start() {        EventLoopGroup group = new NioEventLoopGroup();        Bootstrap bootstrap = new Bootstrap();        bootstrap.group(group).channel(NioSocketChannel.class)                .handler(new ChannelInitializer(){                    @Override                    protected void initChannel(SocketChannel socketChannel) throws Exception {                        ChannelPipeline pipeline = socketChannel.pipeline();                                                pipeline.addLast("decoder", new StringDecoder());// 字符串解码和编码                        pipeline.addLast("encoder", new StringEncoder());                        pipeline.addLast("handler", new ChannelInboundHandlerAdapter(){                                                        @Override                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {                                System.out.println("客户端收到消息:[">

服务端

public class HelloWorldServer {    private int port;        public HelloWorldServer(int port) {        this.port = port;    }        public void start(){        EventLoopGroup bossGroup = new NioEventLoopGroup();//创建父子线程组        EventLoopGroup workGroup = new NioEventLoopGroup();                ServerBootstrap server = new ServerBootstrap();        server.group(bossGroup, workGroup)              .channel(NioServerSocketChannel.class)//指定处理客户端的通道              .childHandler(new ChannelInitializer(){                @Override                protected void initChannel(SocketChannel socketChannel) throws Exception {                    ChannelPipeline pipeline = socketChannel.pipeline();                                        pipeline.addLast("decoder", new StringDecoder());// 字符串解码和编码                    pipeline.addLast("encoder", new StringEncoder());                    pipeline.addLast("handler", new ChannelInboundHandlerAdapter(){                                                @Override                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {                            System.out.println("服务端收到消息:[" + msg + "]");                            ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端".getBytes()));                            ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端".getBytes()));                            ctx.writeAndFlush(Unpooled.copiedBuffer("我是服务端".getBytes()));                        }                    });//自定义handler                }              });//通道初始化        try {            ChannelFuture future = server.bind(port).sync();            future.channel().closeFuture().sync();        } catch (InterruptedException e) {            e.printStackTrace();        } finally{            bossGroup.shutdownGracefully();            workGroup.shutdownGracefully();        }    }    public static void main(String[] args) {        HelloWorldServer server = new HelloWorldServer(8888);        server.start();    }}

运行结果:
服务端

客户端

可以看到,客户端收到的3条消息都粘在一起了

4、Netty解决粘包拆包

4.1、采用定长解决

客户端添加FixedLengthFrameDecoder消息定长解码器,服务端不用处理

运行结果

可以看到,客户端收到的消息确实是按字节数来分割的,但是由于一条消息的长度超过10个字节,所以在遇到中文字符时,会发生乱码,这也是定长分隔符的不足之处

4.2、采用分隔符

客户端修改initChannel方法,自定义 $ 为分隔符

服务端在消息的末尾加上自定义分隔符 $

那么客户端收到的消息就是单独的消息了,没有粘包

感谢各位的阅读,以上就是"TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例"的内容了,经过本文的学习后,相信大家对TCP粘包拆包的概念以及Netty解决TCP粘包拆包实例这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0