粘包和拆包是什么?

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. 小甲鱼Python第十讲课后题---

    0. 下边的列表分片操作会打印什么内容? >>> list1 = [1, 3, 2, 9, 7, 8]>>> list1[2:5] [2,9,7] 1.请问 lis ...

  2. Vue(二十一)使用express模拟接口数据

    1.下载express ... 2.使用vue-cli下载好项目文件 ... 3.找到文件 build - webpack.dev.conf.js 'use strict' const utils = ...

  3. html冲刺

    html知识点回顾与面试题<!--1.<DOCTYPE>告诉浏览器当前文档要以何种HTML或者XHTML规范解析2.语义标签strong 粗体em 斜体del 删除线ins 下划线 ...

  4. PMM 对MYSQL 的监控配制

      系统选择: centos 7.2     关闭防火墙:     systemctl stop firewalld.service     systemctl disable firewalld.s ...

  5. Vue Element使用icon图标(第三方)

    element-ui自带的图标库还是不够全,还是需要需要引入第三方icon,自己在用的时候一直有些问题,参考了些教程,详细地记录补充下 对于我们来说,首选的当然是阿里icon库 教程: 1.打开阿里i ...

  6. Android的Databinding-数据、Map绑定

    本节主要说Collection的字符串数组.List.SparseArray.Map的绑定.先看看xml的布局. <layout xmlns:android="http://schem ...

  7. [Web 前端] mobx教程(三)-在React中使用Mobx

    copy from : https://blog.csdn.net/smk108/article/details/85053903 Mobx提供了一个mobx-react包帮助开发者方便地在React ...

  8. MyBatis中使用实体中使用枚举,数据库中使用数值

    一.简介 本文主要讲MyBatis中使用实体中使用枚举,数据库中使用数值的解决方案.正常直接使用会报错,需要添加typeHandlers在mybatis-config.xml中. 二.解决方案 如下: ...

  9. Nginx负载均衡NFS配置

    Nginx配置 首先在两台服务器上部署同一个项目,例如下: 测试网站节点1: http://192.168.168.61/nfstest/ 测试网站节点2: http://192.168.64.145 ...

  10. 基于netty实现的长连接,心跳机制及重连机制

    技术:maven3.0.5 + netty4.1.33 + jdk1.8   概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...