TCP粘包问题
1.什么是粘包?
粘包指的是数据与数据之间没有明确的分界线,导致不能正确的读取数据。换句话说就是接收方不知道发送方一次到底发送了多少数据。只有TCP才会出现粘包现象,UDP不会出现粘包现象,因为TCP是流式协议,UDP是数据报协议。那么要理解粘包问题,就需要先了解TCP协议传输数据时的具体流程:
应用程序无法直接操作硬件,应用程序想要发送数据必须将数据交给操作系统,由操作系统来完成数据的传输,但是操作系统要为所有的应用程序提供数据传输的服务,也就意味着操作系统不可能立马将数据发送出去,就需要为应用程序提供一个缓冲区,用于临时存放数据。
UDP:
UDP在收发数据时时基于数据包的,即一个一个的包的发送,包与包之间有着明确的分界,到达对方操作系统缓冲区后也是一个个独立的数据包,接收方从操作系统缓冲区中,将数据包加载到应用程序。这种方式存在的问题有:
1.发送方发送的数据长度每个操作系统会有不同的限制,数据超过限制则无法发送
2.接收方接收数据时如果应用程序的提供的缓存容量小于数据包的长度,则会发生数据丢失,而且缓冲区不可能无限大
TCP:
当我们需要传输较大的数据或者需要保证数据的完整性时,就需要使用TCP协议。
与UDP不同的是,TCP增加了一套校验规则来保证数据的完整性,会将超过TCP包最大长度的数据拆分为多个TCP包 并在传输数据时为每一个TCP数据包指定一个顺序号,接收方在收到TCP数据包后按照顺序将数据包进行重组,重组后的数据全都是二进制数据,且每次收到的二进制数据之间没有明显的分界,这样就会容易产生粘包现象。
产生粘包问题的三种情况:
1.当单个数据包较小时,接收方可能一次性读取了多个包的数据
2.当整体数据较大时,接收方可能一次只读取了一个包的一部分内容
3.另外TCP协议为了提高效率,增加了一种优化机制,会将数据较小且发送时间间隔较短的数据和并发送,该机制也会导致发送方将两个数据包黏在一起发送。
2.粘包的解决方案
1.基础解决方案
在发送数据前先发送数据长度
server.py import socket
import subprocess server = socket.socket()
server.bind(('127.0.0.1', 3456))
server.listen(5)
while True:
client, addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8') # 接收命令
if not cmd:
client.close()
break
p = subprocess.Popen(cmd, # 接收命令
shell=True, # 判断参数是否是一个系统命令
stdout=subprocess.PIPE, # 指定结果的输出管道
stderr=subprocess.PIPE) # 指定错误结果的输出管道 std = p.stdout.read() # 获取结果
std_err = p.stderr.read() # 获取错误的结果 length = len(std) + len(std_err) # 统计结果的长度
len_size = str(length).encode('utf-8') # 将int类型转换为bytes类型 client.send(len_size) # 发送结果的长度的bytes数据
client.send(std) # 发送结果数据
client.send(std_err) # 发送错误结果数据
except ConnectionResetError as e:
print(e)
client.close()
break client.py import socket client = socket.socket()
client.connect(('127.0.0.1', 3456))
while True:
cmd = input('>>:').strip().encode('utf-8') # 用户输入命令,并且编码
if not cmd:continue
client.send(cmd) # 发送命令
length_size = client.recv(1024) # 接收数据长度的bytes数据
length = length_size.decode('utf-8') # 将bytes反解成int类型,得到数据长度
all_data = b''
recv_len = 0
while recv_len < int(length): # 条件为如果循环接收的数据长度小于得到的数据长度则执行
data = client.recv(1024) # 接收真实数据
recv_len += len(data) # 累加接收的数据长度
all_data += data # 累加真实数据
print(all_data.decode('gbk')) # 解码打印最终结果 client.close()
解决粘包基础版
但是由于negle优化机制的存在,长度信息和数据还是有可能会发生粘包现象,而对方并不知道长度信息具体几个字节,所以现在的问题是如何能够将长度信息做成一个固定长度的bytes数据。所以有个内置模块:struct模块为我们提供了一个功能,可以将int类型转换为固定长度的bytes。
2.解决粘包问题升级版
import struct # 整型转bytes
res1 = struct.pack('i', 1000) # 无论数字(int)有多大,最后结果的长度都是4
res2 = struct.pack('q', 1000) # 无论数字(long long)有多大,最后结果的长度都是8 print(res1, len(res1)) # b'\xe8\x03\x00\x00' 4
print(res2, len(res2)) # b'\xe8\x03\x00\x00\x00\x00\x00\x00' 8 # bytes转整型
data1 = struct.unpack('i', res1)
data2 = struct.unpack('q', res2)
# 结果是一个元组的形式
print(data1) # (1000,)
print(data2) # (1000,)
server.py import socket
import subprocess
import struct server = socket.socket()
server.bind(('127.0.0.1', 3456))
server.listen(5)
while True:
client, addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8') # 接收命令
if not cmd:
client.close()
break
p = subprocess.Popen(cmd, # 接收命令
shell=True, # 判断参数是否是一个系统命令
stdout=subprocess.PIPE, # 指定结果的输出管道
stderr=subprocess.PIPE) # 指定错误结果的输出管道 std = p.stdout.read() # 获取结果
std_err = p.stderr.read() # 获取错误的结果 length = len(std) + len(std_err) # 统计结果的长度
len_size = struct.pack('i', length) # 将int类型转换为bytes类型 client.send(len_size) # 发送结果的长度的bytes数据
client.send(std) # 发送结果数据
client.send(std_err) # 发送错误结果数据
except ConnectionResetError as e:
print(e)
client.close()
break client.py import socket
import struct client = socket.socket()
client.connect(('127.0.0.1', 3456))
while True:
cmd = input('>>:').strip().encode('utf-8') # 用户输入命令,并且编码
if not cmd:continue
client.send(cmd) # 发送命令
length_size = client.recv(4) # 接收数据长度的bytes数据
length = struct.unpack('i', length_size)[0] # 将bytes反解成int类型,得到数据长度
all_data = b''
recv_len = 0
while recv_len < length: # 条件为如果循环接收的数据长度小于得到的数据长度则执行
data = client.recv(1024) # 接收真实数据
recv_len += len(data) # 累加接收的数据长度
all_data += data # 累加真实数据
print(all_data.decode('gbk')) # 解码打印最终结果 client.close()
解决粘包升级版
3.自定义报头解决粘包
上述方案已经完美解决了粘包问题,但是扩展性不高,假如我们要实现文件的上传和下载,不光要传输文件数据,还需要传输文件名字,md5值等等,如何能实现?
解决步骤:
发送端:
1.先将所有的额外信息打包到一个头中
2.然后先发送头部数据
3.最后发送真实数据
接收端:
1.接收固定长度的头部长度数据
2.根据长度数据获取头部数据
3.根据头部数据获取真实数据
server.py import socket
import subprocess
import datetime
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 3456))
server.listen(5)
while True:
client, addr = server.accept()
while True:
try:
cmd = client.recv(1024).decode('utf-8')
if not cmd:
client.close()
break
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
std = p.stdout.read()
std_err = p.stderr.read() # 自定义报头
t = {}
t['time'] = str(datetime.datetime.now())
t['size'] = len(std) + len(std_err) json_data = json.dumps(t) # 序列化成json格式的数据
t_data = json_data.encode('utf-8') # 将json格式的数据转换成字节
json_len = struct.pack('i', len(t_data)) client.send(json_len)
client.send(t_data) client.send(std)
client.send(std_err)
except ConnectionResetError as e:
print(e)
client.close()
break client.py import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 3456))
while True:
cmd = input('>>:').strip().encode('utf-8')
if not cmd:continue
client.send(cmd) json_len = client.recv(4)
json_size = struct.unpack('i', json_len)[0] json_data = client.recv(json_size)
json_dic = json.loads(json_data.decode('utf-8')) print('执行时间:%s' % json_dic['time'])
all_data = b''
recv_len = 0
while recv_len < json_dic['size']:
data = client.recv(1024)
recv_len += len(data)
all_data += data
print(all_data.decode('gbk')) client.close()
解决粘包问题终极版
FTP文件上传下载程序:
import socket
import struct
import json
import os server = socket.socket() server.bind(("127.0.0.1", 9090))
server.listen() def run():
while True:
client, addr = server.accept()
while True:
try:
# 接收报头
len_data = client.recv(4)
if not len_data:
print("客户端已断开....")
client.close()
break
head_len = struct.unpack("i", len_data)[0]
head = json.loads(client.recv(head_len).decode("utf-8")) # 从报头中获取用户要执行的操作
if head["opt"] == "login":
res = login(head) # 调用登录 无论成功失败 都会得到一个结果
send_response(res, client) # 将结果发送给客户端
elif head["opt"] == "register":
res = register(head) # 调用注册 无论成功失败 都会得到一个结果
send_response(res, client) # 将结果发送给客户端
else:
print("请求错误!") except ConnectionResetError:
print("客户端异常断开...")
client.close()
break def login(head):
dir_path = r"D:\脱产5期内容\选课系统\黏包作业\登录注册\user_data"
path = os.path.join(dir_path, head["name"]) if not os.path.exists(path):
response = {"status": "", "msg": "用户名不存在!"}
return response
with open(path, "rt", encoding="utf-8") as f:
user_dic = json.load(f)
if user_dic["pwd"] == head["pwd"]:
response = {"status": "", "msg": "登录成功!"}
return response
else:
response = {"status": "", "msg": "密码错误!"}
return response def register(head):
user_dic = {"name": head["name"], "pwd": head["pwd"]}
dir_path = r"D:\脱产5期内容\选课系统\黏包作业\登录注册\user_data"
path = os.path.join(dir_path, head["name"]) if os.path.exists(path):
print("用户名已存在!")
response = {"status": "", "msg": "用户名已存在!"}
return response with open(path, "wt", encoding="utf-8") as f:
json.dump(user_dic, f)
response = {"status": "", "msg": "注册成功"}
return response def send_response(resp, client):
response_data = json.dumps(resp).encode("utf-8")
client.send(struct.pack("i", len(response_data)))
client.send(response_data) run()
服务器
import socket
import struct
import json c = socket.socket()
c.connect(("127.0.0.1", 9090)) def run():
print("""
1.登录
2.注册
3.退出
""") res = input("请选择功能:")
if res == "":
return
elif res == "":
login()
elif res == "":
register()
else:
print("输入错误!") def login():
name = input("name:")
pwd = input("password:")
dic = {"name": name, "pwd": pwd, "opt": "login"}
res = send_request(dic)
print(res) def register():
name = input("name:")
pwd = input("password:")
dic = {"name": name, "pwd": pwd, "opt": "register"}
res = send_request(dic)
print(res) def send_request(dic):
dic_data = json.dumps(dic).encode("utf-8")
c.send(struct.pack("i", len(dic_data)))
c.send(dic_data) # 接收返回结果
res_len = struct.unpack("i", c.recv(4))[0]
res = json.loads(c.recv(res_len).decode("utf-8")) return res run()
客户端
TCP粘包问题的更多相关文章
- Socket编程(4)TCP粘包问题及解决方案
① TCP是个流协议,它存在粘包问题 TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的.这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的.T ...
- Netty(三)TCP粘包拆包处理
tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...
- netty 解决TCP粘包与拆包问题(二)
TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...
- Netty的TCP粘包/拆包(源码二)
假设客户端分别发送了两个数据包D1和D2给服务器,由于服务器端一次读取到的字节数是不确定的,所以可能发生四种情况: 1.服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包. 2.服 ...
- netty 解决TCP粘包与拆包问题(一)
1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...
- TCP粘包/拆包问题
无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河 ...
- tcp粘包问题(封包)
tcp粘包分析 http://blog.csdn.net/zhangxinrun/article/details/6721495 解决TCP网络传输“粘包”问题(经典) http: ...
- 1. Netty解决Tcp粘包拆包
一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...
- TCP 粘包/拆包问题
简介 TCP 是一个’流’协议,所谓流,就是没有界限的一串数据. 大家可以想想河里的流水,是连成一片的.期间并没有分界线, TCP 底层并不了解上层业务数据的具体含义 ,它会根据 TCP 缓冲区 ...
- TCP粘包/拆包问题的解决
TCP粘包拆包问题 一个完整的包可能被TCP拆分成多个包,或多个小包封装成一个大的数据包发送. 解决策略 消息定长,如果不够,空位补空格 在包尾增加回车换行符进行分割,例如FTP协议 将消息分为消息头 ...
随机推荐
- linux用户
hen we are travelling, we find ourselves in new places and new spaces, physically and internally; it ...
- laravel 框架后台主菜单接口
后台菜单调用接口:/admin/manages ManageRepository类: 每个路由中注册: 等等: 最后后台菜单返回:
- php 统计某个目录中所有文件的大小
/** * @Purpose : 利用递归的方式统计目录的大小 * @Author : chrdai * @Method Name : dirSize() * @parameter : string ...
- 如何在PDF中添加水印,PDF添加水印技巧
PDF文件现在的使用很是普遍,不管是工作中还是学习中都会使用到PDF文件,制作一个PDF文件就很辛苦的,我们要是想把PDF文件中添加水印防止抄袭的时候应该要怎么做呢,其实吧PDF文件添加水印还挺简单的 ...
- Software tips
1.Microsoft office professional plus 2013秘钥 PD3W7-NVGG7-YKGQX-7CRDG-J2MG7(test success)MTGRC-8BM6H-W ...
- bzoj 1076
发现自己已经把期望dp忘光了... 其实本质上非常简单,就是利用状压的思想跑期望 首先很容易设计出状态:记状态f[s][i]表示到了第i个点,之前已选过的点的状态为s时所能获得的最大期望得分 但是会发 ...
- Centos6.8部署jumpserver(完整版)
环境: 系统 Centos6.8 IP:192.168.66.131 关闭selinux和防火墙 # 修改字符集,否则可能报 input/output error的问题,因为日志里打印了中文 # lo ...
- C. cltt的幸运数LCAdfs
/*C: cltt的幸运数 Time Limit: 1 s Memory Limit: 128 MB Submit Problem Description 一棵树有n个节点,共m次查询,查询 ...
- 史上最简单的SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)
在上一篇文章,讲了服务的注册和发现.在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的.Spring cloud有两种服务调用方式,一种是ribbon+r ...
- .Net页面缓存OutPutCache详解
一 它在Web.Config中的位置 <system.web> <!--页面缓存--> <caching> <outputCacheSettings> ...