在平时客户端socket开发中,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题。

我们都知道TCP属于传输层的协议,传输层除了有TCP协议外还有UDP协议。那么UDP是否会发生粘包或拆包的现象呢?答案是不会。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。而TCP是基于字节流的,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界;另外从TCP的帧结构也可以看出,在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
TCP为提高性能,发送端会将需要发送的数据发送到缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制,来接收数据。

粘包/拆包表现形式

现在假设客户端向服务端连续发送了两个数据包,用packet1和packet2来表示,那么服务端收到的数据可以分为三种,现列举如下:

第一种情况,接收端正常收到两个数据包,即没有发生拆包和粘包的现象,此种情况不在本文的讨论范围内。

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

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

粘包/拆包发生原因

发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充

  1. 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
  2. 待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
  3. 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
  4. 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会当做一个包发出去,产生粘包)

########################
#服务端
from socket import *
phone = socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
print('start running...')coon,addr = phone.accept() #等待连接data1 = coon.recv(10)
data2 = coon.recv(10)print('------------>',data1.decode('utf-8'))
print('------------>',data2.decode('utf-8'))
coon.close()
phone.close()###############################
#客户端
from socket import *
import time
phone = socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8080))phone.send('hello'.encode('utf-8'))
phone.send('helloworld'.encode('utf-8'))
phone.close()

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

########################
#服务端
from socket import *
phone = socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SOCK_STREAM,1)
phone.bind(('127.0.0.1',8080))
phone.listen(5)
print('start running...')coon,addr = phone.accept() #等待连接data1 = coon.recv(2) #一次没有接收完整
data2 = coon.recv(10)  #下一次接收的时候会先取旧的数据,然后取新的
# data3 = coon.recv(1024)  #接收等5秒后的信息
print('------------>',data1.decode('utf-8'))
print('------------>',data2.decode('utf-8'))
# print('------------>',data3.decode('utf-8'))
coon.close()
phone.close()###############################
#客户端
from socket import *
import time
phone = socket(AF_INET,SOCK_STREAM)
phone.connect(('127.0.0.1',8080))phone.send('hello'.encode('utf-8'))
time.sleep(5)
phone.send('haiyan'.encode('utf-8'))
phone.close()

粘包/拆包解决办法

通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:

  1. 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
  2. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
  3. 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据

########################
#服务端
import socket
import subprocess
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.bind(('127.0.0.1',8080)) #绑定手机卡
phone.listen(5) #阻塞的最大数
print('start runing.....')
while True: #链接循环coon,addr = phone.accept()# 等待接电话print(coon,addr)while True: #通信循环# 收发消息cmd = coon.recv(1024) #接收的最大数print('接收的是:%s'%cmd.decode('utf-8'))#处理过程res = subprocess.Popen(cmd.decode('utf-8'),shell = True,stdout=subprocess.PIPE, #标准输出stderr=subprocess.PIPE #标准错误)stdout = res.stdout.read()stderr = res.stderr.read()#先发报头(转成固定长度的bytes类型,那么怎么转呢?就用到了struct模块)#len(stdout) + len(stderr)#统计数据的长度header = struct.pack('i',len(stdout)+len(stderr))#制作报头coon.send(header)#再发命令的结果coon.send(stdout)coon.send(stderr)coon.close()
phone.close()###############################
#客户端import socket
import struct
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) #连接服
while True:# 发收消息cmd = input('请你输入命令>>:').strip()if not cmd:continuephone.send(cmd.encode('utf-8')) #发送#先收报头header_struct = phone.recv(4) #收四个unpack_res = struct.unpack('i',header_struct)total_size = unpack_res[0]  #总长度#后收数据recv_size = 0total_data=b''while recv_size<total_size: #循环的收recv_data = phone.recv(1024) #1024只是一个最大的限制recv_size+=len(recv_data) #total_data+=recv_data #print('返回的消息:%s'%total_data.decode('gbk'))
phone.close()

解决粘包问题升级版

########################
#服务端
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(('127.0.0.1',8080)) #绑定手机卡
phone.listen(5) #阻塞的最大数
print('start runing.....')
while True: #链接循环coon,addr = phone.accept()# 等待接电话print(coon,addr)while True: #通信循环# 收发消息cmd = coon.recv(1024) #接收的最大数print('接收的是:%s'%cmd.decode('utf-8'))#处理过程res = subprocess.Popen(cmd.decode('utf-8'),shell = True,stdout=subprocess.PIPE, #标准输出stderr=subprocess.PIPE #标准错误)stdout = res.stdout.read()stderr = res.stderr.read()# 制作报头header_dic = {'total_size': len(stdout)+len(stderr),  # 总共的大小'filename': None,'md5': None}header_json = json.dumps(header_dic) #字符串类型header_bytes = header_json.encode('utf-8')  #转成bytes类型(但是长度是可变的)#先发报头的长度coon.send(struct.pack('i',len(header_bytes))) #发送固定长度的报头#再发报头coon.send(header_bytes)#最后发命令的结果coon.send(stdout)coon.send(stderr)coon.close()
phone.close()###############################
#客户端
import socket
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) #连接服务器
while True:# 发收消息cmd = input('请你输入命令>>:').strip()if not cmd:continuephone.send(cmd.encode('utf-8')) #发送#先收报头的长度header_len = struct.unpack('i',phone.recv(4))[0]  #吧bytes类型的反解#在收报头header_bytes = phone.recv(header_len) #收过来的也是bytes类型header_json = header_bytes.decode('utf-8')   #拿到json格式的字典header_dic = json.loads(header_json)  #反序列化拿到字典了total_size = header_dic['total_size']  #就拿到数据的总长度了#最后收数据recv_size = 0total_data=b''while recv_size<total_size: #循环的收recv_data = phone.recv(1024) #1024只是一个最大的限制recv_size+=len(recv_data) #有可能接收的不是1024个字节,或许比1024多呢,# 那么接收的时候就接收不全,所以还要加上接收的那个长度total_data+=recv_data #最终的结果print('返回的消息:%s'%total_data.decode('gbk'))
phone.close()

struct模块

#该模块可以把一个类型,如数字,转成固定长度的bytes类型
import struct
res = struct.pack('i',12345)
print(res,len(res),type(res))  #长度是4res2 = struct.pack('i',12345111)
print(res,len(res),type(res2))  #长度也是4unpack_res =struct.unpack('i',res2)
print(unpack_res)  #(12345111,)
print(unpack_res[0]) #12345111

[网络知识]TCP协议中的粘包与拆包相关推荐

  1. socket 网络通信(基于tcp协议)以及粘包解决方案

    socket 网络通信(基于tcp协议)以及粘包解决方案 参考文章: (1)socket 网络通信(基于tcp协议)以及粘包解决方案 (2)https://www.cnblogs.com/amiee- ...

  2. 面试官问:你来讲下Netty通信中的粘包、拆包?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Java技术剑 来源:https://urlify.cn/I ...

  3. TCP中的粘包、拆包问题产生原因及解决方法

    目录 粘包/拆包 问题产生原因: 解决 粘包/拆包 问题: 为什么TCP有粘包? 为什么UDP没有粘包? 发生在网络的哪些层上? 粘包/拆包 问题产生原因: 发生TCP粘包或拆包有很多原因,现列出常见 ...

  4. Netty 中的粘包和拆包详解

    Netty 底层是基于 TCP 协议来处理网络数据传输.我们知道 TCP 协议是面向字节流的协议,数据像流水一样在网络中传输那何来 "包" 的概念呢? TCP是四层协议不负责数据逻 ...

  5. TCP 协议如何解决粘包、半包问题

    TCP 协议是流式协议 很多读者从接触网络知识以来,应该听说过这句话:TCP 协议是流式协议.那么这句话到底是什么意思呢?所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标 ...

  6. 面试题:聊聊TCP的粘包、拆包以及解决方案

    TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架.Netty等.如果你的简历中写了类似的技术或者你所面试的公司使用了相关的技术,被问到该面试的几率会非常高. 今天这篇文章就带大家 ...

  7. Netty 解决粘包和拆包问题的四种方案

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://my.oschina.net/ ...

  8. 什么是粘包和拆包,Netty如何解决粘包拆包?

    Netty粘包拆包 TCP 粘包拆包是指发送方发送的若干包数据到接收方接收时粘成一包或某个数据包被拆开接收. 如下图所示,client 发送了两个数据包 D1 和 D2,但是 server 端可能会收 ...

  9. 【Netty】Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

最新文章

  1. 每日一皮:程序员和黑客的区别
  2. 无法读取源文件或磁盘_禁用格式化磁盘操作 保护电脑文件安全防止删除
  3. Network下方什么请求也没有_HTTP的响应与请求
  4. sh(Spring+Spring mvc+hibernate)——IEmpDao.java
  5. 【渝粤题库】陕西师范大学210014幼儿园科学教育作业(高起专)
  6. CSDN使用富文本编辑器为所发布的文章生成右侧目录
  7. [转]Pytest 基础教程
  8. spring boot 源码_springboot源码架构解析listener
  9. mapreduce数据压缩
  10. JAVA面试总结(初版)
  11. 【工具】pt-online-schema-change
  12. 在word中快速得到数学公式
  13. 美国NSA泄露文件中提到的神秘APT组织被曝光
  14. 分布式任务调度平台如何选型?
  15. vba 汉字转拼音 -- wps office
  16. 星际争霸II sc2 台湾版试玩
  17. 时间焦虑:为时已晚?
  18. 双相机 单目三维重建
  19. 这款免费开源的数据库工具,支持所有主流数据库!
  20. 开关电源雷击浪涌整改_浪涌整改措施.doc

热门文章

  1. 苹果证实收购 InVisage,量子点材料会如何变革智能手机?
  2. termux 玩法(一)
  3. 劲牌持正堂药业与湖北中医药大学共建中药产业研究院
  4. CAD 二次开发之创建包含文字的线型
  5. 家庭如何实现外网映射?--最全的保姆式教学
  6. 类的数据成员——C#【类与对象】。
  7. 前端Vue中query传参
  8. 百格活动:会议界端午大PK,“粽”有一款满足你
  9. oracle键盘不出来,告诉你笔记本键盘没反应怎么解决
  10. C++学习 2019-1-9