为什么会出现黏包问题?
首先只有在TCP协议中才会出现黏包现象
是因为TCP协议是面向流的协议
在发送的数据 传输过程中 有缓存机制 来避免数据丢失
因此 在连续发送小数据的时候 以及接收大小不符的时候都容易出现黏包现象
本质还是因为我们在接受数据的时候不知道发送的数据的长短
怎么解决黏包问题?
在接收端发送要发送的数据的大小
一种是不带struct 一种是带struct 定制协议
黏包
http://www.cnblogs.com/Eva-J/articles/8244551.html#_label5
注意:只有TCP有粘包现象,UDP永远不会粘包
黏包成因:
多个send可能会发生黏包现象
优化算法不优化
发生黏包两种现象:
情况一 发送方的缓存机制
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
如何解决黏包?
存在的问题:
多了一次交互。程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
这个模块可以把要发送的数据长度转换成固定长度的字节。这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了。
import structret = struct.pack('i', 2049) # pack方法,将对象转换成固定字节长度bytes类型 num = struct.unpack('i', ret) # 解包 print(num) # 元组 print(num[0]) # 数字
连续send 连续receive
我们在网络上传输的所有数据,都叫数据包
数据包中的数据,都叫报文
报文里不只有你的数据 ip地址 mac地址 端口号
所有的报文都有报头 相当于协议 接收多少字节 什么顺序 等等
报头可以自己定制
根据报头来解包接收的数据
复杂的应用上就会用到定制报头
比如:传输文件的时候
文件名、大小、类型、路径
网络传输中,处处有有协议,协议就是一堆报文和报头 ———字节
协议的解析过程我们不需要关心
我们也可以自定制协议
实现一个大文件的上传或下载:
客户端作发送端:
import socket import os import json import structsk = socket.socket() sk.connect(('127.0.0.1',8090))# 发文件 # 定制报头 head = {'filepath':r'H:\python\day32','filename':r'05 python fullstack s9day32 strcuct模块定制报头的理论.mp4','filesize':None} file_path = os.path.join(head['filepath'],head['filename']) file_size = os.path.getsize(filepath) head['filesize'] = file_sizejson_head = json.dumps(head) # 字典转成字符串 bytes_head = json_head.encode('utf-8') # 字符串转bytes类型 head_len = len(bytes_head) # 报头的长度 pack_len = struct.pack('i', head_len) # 报头长度转成固定的4字节长度 sk.send(pack_len) # 先发报头长度 sk.send(bytes_head) # 再发报头内容 # 然后再发文件内容: buffer = 1024 with open(filepath, 'rb') as f:while file_size:if file_size >= buffer:content = f.read(buffer)sk.send(content)file_size -= bufferelse:content = f.read(file_size)sk.send(content)break sk.close
服务端:
import socket import os import json import structsk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen()conn, addr = sk.accept() # 接收 head_len = conn.recv(4) # 接收报头长度 head_len = struct.unpack('i', head_len)[0] # 解包成元组 第一个 json_head = conn.recv(head_len).decode('utf-8') head = json.loads(json_head) # 报头 filesize = head['filesize'] buffer = 1024 # 写入文件 with open(head['filename'], 'wb') as f:while filesize:if filesize >= buffer:content = conn.recv(buffer)f.write(content)filesize -= bufferelse:f.write(conn.recv(filesize))breakconn.close sk.close