《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
一、粘包/拆包概念
TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。
现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:
第一种情况:
接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

第二种情况:
接收端只收到一个数据包,由于TCP是不会出现丢包的,所以这一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。这种情况由于接收端不知道这两个数据包的界限,所以对于接收端来说很难处理。

第三种情况:
这种情况有两种表现形式,如下图。接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。这两种情况如果不加特殊处理,对于接收端同样是不好处理的。


二、粘包问题的解决策略
- 消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
- 包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
- 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
三、Netty粘包和拆包解决方案
Netty提供了多个解码器,可以进行分包的操作,分别是:
LineBasedFrameDecoder
DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)
FixedLengthFrameDecoder(使用定长的报文来分包)
LengthFieldBasedFrameDecoder
四、TCP粘包和拆包实例演示
首先编写服务端
package com.spring.netty.handler; 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 Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).
childHandler(new MyServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.spring.netty.handler; 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 ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyServerHandler());
}
}
package com.spring.netty.handler; 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 MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> { private int count; @Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer); String message = new String(buffer, Charset.forName("utf-8"));
System.out.println("服务端接收到的消息内容:"+message);
System.out.println("服务端接收的消息数量:"+(++this.count)); ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(),Charset.forName("utf-8"));
ctx.writeAndFlush(responseByteBuf);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
然后编写客户端
package com.spring.netty.handler; 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 Exception {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new MyClientInitializer());
ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
package com.spring.netty.handler; 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 ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyClientHandler());
}
}
package com.spring.netty.handler; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup; import java.nio.charset.Charset; public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
private int count;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for(int i=0;i<10;i++){
ByteBuf buffer = Unpooled.copiedBuffer("send from client ", Charset.forName("utf-8"));
ctx.writeAndFlush(buffer);
}
} @Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
byte[] buffer = new byte[msg.readableBytes()];
msg.readBytes(buffer); String message = new String(buffer,Charset.forName("utf-8"));
System.out.println("客户端接收到的消息内容:"+message);
System.out.println("客户端接收到的消息数量:"+(++this.count));
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
分别运行服务端和客户端查看运行效果
服务端效果:

客户端效果:

本节我们介绍了TCP粘包拆包的现象及做了个实例演示,下节我们来介绍在Netty中如何解决粘包拆包问题。
《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)的更多相关文章
- 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?
前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
- 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)
一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行) LineBasedFrameDecoder是回 ...
- 1. Netty解决Tcp粘包拆包
一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...
- 【转】Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
- Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
- Netty解决TCP粘包/拆包问题 - 按行分隔字符串解码器
服务端 package org.zln.netty.five.timer; import io.netty.bootstrap.ServerBootstrap; import io.netty.cha ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- Netty TCP粘包/拆包问题《一》
1.使用LineBasedFrameDecoder,StringDecoder解析器进行解决TCP粘包/拆包问题 2.代码搞起: TimeClient:客户端 /* * Copyright 2013- ...
随机推荐
- ActiveMQ之JMS及保证消息的可靠性<持久化、事务、签收>(三)
1.JAVAEE 是一套使用Java 进行企业级开发的13 个核心规范工业标准 , 包括: JDBC 数据库连接 JNDI Java的命名和目录接口 EJB Enterprise java b ...
- 二分法:从一个只包含数字的list中查找某个数
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2017/4/10 19:03 # @Author : MnCu # @Site : # ...
- 2020即将到来!DevExpress Winforms开发有哪些新功能值得期待?
下载DevExpress v19.2完整版 DevExpress Winforms Controls 内置140多个UI控件和库,完美构建流畅.美观且易于使用的应用程序.DevExpress Winf ...
- 「数据结构与算法(Python)」(一)
算法的提出 算法的概念 算法是计算机处理信息的本质,因为计算机程序本质上是一个算法来告诉计算机确切的步骤来执行一个指定的任务.一般地,当算法在处理信息时,会从输入设备或数据的存储地址读取数据,把结果写 ...
- Django ManyToManyField.through_fields 和
示例: from django.db import models class Person(models.Model): name = models.CharField(max_length=50) ...
- 企业级分布式 HTAP 数据库管理系统 TBase
TBase 是腾讯数据平台团队在开源的 PostgreSQL 基础上研发的企业级分布式 HTAP 数据库管理系统: 具备高性能可扩展的分布式事务能力,支持 RC 和 RR 两种隔离级别: 通过安全.管 ...
- 关键字local、global和内置函数【locals、globals】
每个函数都有着自已的命名空间,叫做局部名字空间,它记录了函数的变量,包括函数的参数和局部定义的变量.每个模块拥有它自已的命名空间,叫做全局命名空间,它记录了模块的变量,包括函数.类.其它导入的模块.模 ...
- pycharm分辨率&清晰度
三个月前换了新电脑,安装pycharm.又换了新的大屏幕. pycharm界面拖到大屏上时,字大小和字体都没有变化,清晰度低了很多,就像打了一层薄薄的马赛克一样.拖回到原本的屏幕又清晰了. 一直以为是 ...
- Codeforces Round #598 (Div. 3) A,B,C,D{E,F待补}
A. Payment Without Change #include<bits/stdc++.h> using namespace std; #define int long long ...
- ES6-3 变量的解构赋值
1.数组的解构赋值 数组的解构赋值其实是=左右进行“模式匹配”. ❗️❗️❗️=右侧是具体的数值,不是变量!,=左侧的是变量!如果右侧是变量形式,需要先计算出具体的数值!! let [a,[b],c] ...