tcp的粘包和拆包示例以及使用LengthFieldFrameDecoder来解决的方法

简介: 粘包和拆包是什么?TCP协议是一种字节流协议,没有记录边界,我们在接收消息的时候,不能人为接收到的数据包就是一个整包消息当客户端向服务器端发送多个消息数据的时候,TCP协议可能将多个消息数据合并成一个数据包进行发送,这就是粘包当客户端向服务器端发送的消息过大的时候,tcp协议可能将一个数据包拆...

粘包和拆包是什么?
TCP协议是一种字节流协议,没有记录边界,我们在接收消息的时候,不能人为接收到的数据包就是一个整包消息

当客户端向服务器端发送多个消息数据的时候,TCP协议可能将多个消息数据合并成一个数据包进行发送,这就是粘包

当客户端向服务器端发送的消息过大的时候,tcp协议可能将一个数据包拆成多个数据包来进行发送,这就是拆包

以下一netty为例,展示一下tcp粘包和拆包的例子:
ServerBusinessHanler:

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

import java.nio.charset.Charset;
import java.util.UUID;

public class ServerBusinessHanler extends SimpleChannelInboundHandler<ByteBuf> {

    int count = 0;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        String message = new String(bytes, Charset.forName("UTF-8"));
        System.out.println("从客户端收到的字符串:" + message);
        System.out.println("服务端接收到的请求数:" + (++count));
        ctx.writeAndFlush(Unpooled.copiedBuffer(UUID.randomUUID().toString(),Charset.forName("UTF-8")) );

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println(cause.getStackTrace());
        ctx.channel().close();
    }

}

Server:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;


public class Server {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                        
                            ch.pipeline().addLast(new ServerBusinessHanler());
                        }
                    });
            serverBootstrap.bind(new InetSocketAddress(8899)).sync().channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}    

ClientBusinessHandler:


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;    
import java.nio.charset.Charset;

public class ClientBusinessHandler extends SimpleChannelInboundHandler<ByteBuf> {
    int count = 1;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        byte[] bytes = new byte[msg.readableBytes()];
        msg.readBytes(bytes);
        System.out.println("从服务器端接收到的信息: " + new String(bytes, Charset.forName("UTF-8")));
        System.out.println("从服务器端读到的请求的个数: " + count++);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            ctx.writeAndFlush(Unpooled.copiedBuffer("中国移动".getBytes()));
        }
        ctx.writeAndFlush(Unpooled.copiedBuffer("jiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyipingjiaoyiping".getBytes()));
    }
}

Client:


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;


public class Client {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ClientBusinessHandler());
                    }
                });
        Channel channel = bootstrap.connect("127.0.0.1", 8899).channel();
        channel.closeFuture().sync();

    }
}    

分别运行服务器端和客户端,我们看到服务器端的输出:
image
相应的,服务器端也只给客户端了两个返回,而不是我们期待的11个

我们从客户端传入的十个 "中国移动"和第二次发送的长的字符串的前一部分发生了粘包,而第二个长字符串的后一部分则被拆分到第二个数据包里了

粘包和拆包的情况都出现了

那怎么解决粘包和拆包的问题呢? 一种方法是在字符串中使用特定的分隔符来分隔,另外一种方法是,我们在发送数据包的时候在前边附加上数据包的长度

netty为我们提供了多种的解码器,比如定长解码器和基于分隔符的解码器等,这里,我么使用 LengthFieldBasedFrameDecoder这个解码器来解决粘包和拆包的问题,关于这个解码器的详细信息,可以参考这个类的java doc文档,上边讲述的很详细,简单来说,就是我们需要在请求中添加一些关于数据字段长度的信息(以下简称length),LengthFieldBasedFrameDecoder的构造方法中,设置一下关于length所占的字节数和要跳过的字节数等信息(我们只需要读取数据内容的话,可以跳过length的信息) 这样的话,我们从客户端发送的每个数据包,都可以正确地解析

首先,我们增加一个编码器,为数据包附件上lenght:


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;


//本类作用,加上四个字节的消息头(int类型),表示数据包长度
public class LengthEncoder extends MessageToByteEncoder<ByteBuf> {
    @Override
    protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
        int length = msg.readableBytes();
        byte[] bytes = new byte[length];
        msg.readBytes(bytes);
        out.writeInt(length);
        out.writeBytes(bytes);
    }
}

然后,将我们的编码器和对应的 LengthFieldBasedFrameDecoder添加到服务器端和客户端的ChannelInitializer里边去

修改之后的服务器端代码:


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import java.net.InetSocketAddress;


public class Server {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(4096,0,4,0,4));
                            ch.pipeline().addLast(new LengthEncoder());
                            ch.pipeline().addLast(new ServerBusinessHanler());
                        }
                    });
            serverBootstrap.bind(new InetSocketAddress(8899)).sync().channel().closeFuture().sync();

        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}   

new LengthFieldBasedFrameDecoder(4096,0,4,0,4));的意思是:使用LengthFieldBasedFrameDecoder的最大的数据包大小是4096个,其中length占四个字节,读取的时候跳过4个字节(也就是最终解码的时候,忽略掉length字段,只返回真正的数据内容)

修改之后的客户端代码:


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;


public class Client {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(4096,0,4,0,4));
                        ch.pipeline().addLast(new LengthEncoder());
                        ch.pipeline().addLast(new ClientBusinessHandler());
                    }
                });
        Channel channel = bootstrap.connect("127.0.0.1", 8899).channel();
        channel.closeFuture().sync();

    }
}  

分别运行服务器端和客户端之后我们得到的结果:
服务器端输出:

image
客户端输出:
image

这样就解决了粘包和拆包导致的问题

目录
相关文章
|
网络协议 算法 程序员
坦白局,TCP粘包:我只是犯了每个数据包都会犯的错
李东,自称亚健康终结者,尝试使用互联网+的模式拓展自己的业务。在某款新开发的聊天软件琛琛上发布广告。 键盘说来就来。疯狂发送"李东",回车发送!,"亚健康终结者",再回车发送!
|
移动开发 网络协议 Java
TCP 粘包/拆包问题
《基础系列》
128 0
TCP 粘包/拆包问题
|
网络协议 图形学
Socket TCP协议解决粘包、半包问题的三种解决方案
Socket TCP协议解决粘包、半包问题的三种解决方案
233 1
Socket TCP协议解决粘包、半包问题的三种解决方案
|
移动开发 网络协议
TCP的粘包拆包问题+解决方案
TCP的粘包拆包问题+解决方案
124 0
TCP的粘包拆包问题+解决方案
|
网络协议
TCP的粘包和拆包
TCP的粘包和拆包
140 0
|
移动开发 网络协议 IDE
Netty通信遇到了TCP拆包粘包问题?看这篇文章如何解决
在上一篇文章中主要是使用Springboot开发了一个Netty通信的基本案例,这篇文章是在上一篇文章的基础之上进行讲解的,主要是考虑传输数据如果遇到粘包问题该如何解决。 这篇文章会按照一下步骤进行讲解,希望对你有所收获: 1、什么是TCP粘包拆包 2、Netty中粘包问题的问题重现 3、Netty中粘包问题的解决方案 OK,在你心中有这么一个基本的脉络之后就可以开始今天的文章了。本系列所有的文章都会给出完整的代码,且在电脑上真实运行了一遍,确保无误。
228 0
Netty通信遇到了TCP拆包粘包问题?看这篇文章如何解决
|
移动开发 网络协议 Java
牛逼!TCP 粘拆包问题及 Netty 中的解决方案
本文选自 Doocs 开源社区旗下“源码猎人”项目,作者 AmyliaY。
222 0
牛逼!TCP 粘拆包问题及 Netty 中的解决方案
|
移动开发 网络协议
【Netty】TCP粘包和拆包
前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包。
121 0
【Netty】TCP粘包和拆包
|
编解码 网络协议 Java
Netty(三) 什么是 TCP 拆、粘包?如何解决?(下)
记得前段时间我们生产上的一个网关出现了故障。 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信。 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议。 有个前提是:网关是需要读取一段完整的报文才能进行后面的逻辑。
|
网络协议
TCP/IP协议的介绍
TCP/IP协议是众多协议的统称,通过分层结构来管理。可分为七层模型或四层结构