粘包和拆包是什么?

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(); }
}

分别运行服务器端和客户端,我们看到服务器端的输出:

相应的,服务器端也只给客户端了两个返回,而不是我们期待的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(); }
}

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

服务器端输出:

客户端输出:

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

tcp的粘包和拆包示例以及使用LengthFieldFrameDecoder来解决的方法的更多相关文章

  1. 关于TCP的粘包和拆包

    问题产生 一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题. 下面可以看一张图,是客户端向服务端发送包: 1. 第一种情况 ...

  2. Netty—TCP的粘包和拆包问题

    一.前言 虽然TCP协议是可靠性传输协议,但是对于TCP长连接而言,对于消息发送仍然可能会发生粘贴的情形.主要是因为TCP是一种二进制流的传输协议,它会根据TCP缓冲对包进行划分.有可能将一个大数据包 ...

  3. TCP的粘包和拆包问题及解决

    前言 TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议.那么UDP是否会发生粘包或拆包的现象呢?答案是不会.UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit ...

  4. TCP的粘包和拆包问题及解决办法(C#)

    本文参考:https://blog.csdn.net/wxy941011/article/details/80428470 原因 如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数 ...

  5. TCP的粘包、拆包及解决方法

    TCP粘包,拆包及解决方法 粘包拆包问题是处于网络比较底层的问题,在数据链路层.网络层以及传输层都有可能发生.我们日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因 ...

  6. 【Netty】TCP粘包和拆包

    一.前言 前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包. 二.粘包和拆包 对于TCP协议而言,当底层发送消息和接受消息时,都需要考虑TCP ...

  7. netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...

  8. C#网络编程学习(5)---Tcp连接中出现的粘包、拆包问题

    本文参考于CSDN博客wxy941011 1.疑问 我们使用第四个博客中的项目. 修改客户端为:连接成功后循环向服务器发送从1-100的数字.看看服务器会不会正常的接收100次数据. 可是我们发现服务 ...

  9. 【游戏开发】网络编程之浅谈TCP粘包、拆包问题及其解决方案

    引子 现如今手游开发中网络编程是必不可少的重要一环,如果使用的是TCP协议的话,那么不可避免的就会遇见TCP粘包和拆包的问题,马三觉得haifeiWu博主的 TCP 粘包问题浅析及其解决方案 这篇博客 ...

随机推荐

  1. spring中的多线程aop方法拦截

    日常开发中,常用spring的aop机制来拦截方法,记点日志.执行结果.方法执行时间啥的,很是方便,比如下面这样:(以spring-boot项目为例) 一.先定义一个Aspect import org ...

  2. .ipynb格式文件

    ipynb,即ipython notebook,需要用ipython notebook打开,IPython Notebook是web based IPython封装,但是可以展现富文本,使得整个工作可 ...

  3. log4j2自定义Appender(输出到文件/RPC服务中)

    1.背景 虽然log4j很强大,可以将日志输出到文件.DB.ES等.但是有时候确难免完全适合自己,此时我们就需要自定义Appender,使日志输出到指定的位置上. 本文,将通过两个例子说明自定义APP ...

  4. windows环境下面批量修改文件夹名称

    ren 1 A ren 2 B ren 3 C ren 4 D 电脑桌面新建文档 ---> 批量修改文件夹名称.txt 修改文件名称为:--->批量修改文件夹名称.bat 内容如上: 双击 ...

  5. Spring Boot 之httpClient使用

    版权声明:本文为博主原创文章,转载时请在文章最前方附上本文地址. https://blog.csdn.net/qq_35033270/article/details/80112085 超文本传输协议( ...

  6. javap 指令集

    栈和局部变量操作将常量压入栈的指令aconst_null 将null对象引用压入栈iconst_m1 将int类型常量-1压入栈iconst_0 将int类型常量0压入栈iconst_1 将int类型 ...

  7. Spark(四十五):Schema Registry

    很多时候在流数据处理时,我们会将avro格式的数据写入到kafka的topic,但是avro写入到kafka的时候,数据有可能会与版本升级,也就是schema发生变化,此时如果消费端,不知道哪些数据的 ...

  8. ubuntu 配置 apt 使用代理

    ubuntu 配置 apt 使用代理 仅配置系统代理是无法使 apt 也使用代理的,我们需要给 apt 独立配置代理. 方法 ubuntu 官方说明 :https://help.ubuntu.com/ ...

  9. git变慢的原因

    最近使用git更新代码变慢,进一步试了一下提交代码.执行git命令都很慢,换了idea的工作目录.更换git版本,所有操作都是徒劳. 最后关了火绒杀毒软件,才快了起来. 坑坑坑坑坑的火绒杀毒!浪费我至 ...

  10. Mathematica查看内部定义

    << GeneralUtilities`; PrintDefinitions[IntegerReverse]