一 .解决黏包TCP(一)
1.解决方案一
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,
如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
存在的问题:
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
刚刚的方法,问题在于我们我们在发送
我们可以借助一个模块,这个模块可以把要发送的数据长度转换成固定长度的字节。
这样客户端每次接收消息之前只要先接受这个固定长度字节的内容看一看接下来要接收的信息大小,
那么最终接受的数据只要达到这个值就停止,就能刚好不多不少的接收完整的数据了
server import socket server=socket.socket() server.bind(("192.168.59.1",8100)) server.listen(5) conn,addr=server.accept() while True: aa = input("请输入命令:").strip() conn.send(aa.encode("gbk")) num = conn.recv(1024).decode("utf-8") conn.send(b"ok") res = conn.recv(int(num)).decode("gbk") print(res,"1111111111111111111111111111111111111111111111111") conn.close() server.close()
client import socket import subprocess client=socket.socket() while True: client.connect(("192.168.59.1", 8100)) cmd = client.recv(1024).decode("utf-8") res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) stdout = res.stdout.read() stderr = res.stderr.read() client.send(str(len(stderr) + len(stdout)).encode("utf-8"))
client.recv(1024) client.send(stdout) client.send(stderr) client.close()
上面代码 好处: 确定了我们到底要接收多大的数据 要在文件中配置一个配置项 就是每一次 recv 的大小 bufer=4096 最大 当我们要发生大数据时候 要明确知道发送的数据大小 告诉对方方便准确的接收所以数据 也就是这里的 recv 应用场景: 多用于文件传输过程中 大文件传输 一定是按照字节读取 每一次读固定字节 传输的过程中 一边读 一边传 接收端一边收 一边写 send 这个文件之前 35000字节 send(4096) 35000-4069-4039----->0 recv 这个文件 recv 35000字节 recv(2048) 35000-2048 ---->0 不好: 的地方 多了一次交互 send sendto 交互过多会导致 程序内存管理 占用过多
二 .解决黏包TCP struct模块(二)
1.该模块可以把一个类型,如数字,转成固定长度的bytes
# struct 该模块可以把一个类型,如数字,转成固定长度的bytes import struct # 借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。 # 为什么 要转换成固定长度的bytes # aa = struct.pack('i') i 代表int 就是即将要把一个数字转换成固定长度bytes类型 #一次性发送4096 # 发送时 接收时 # 先发送struct转换好的数据长度4字节 先接受4个字节使用struct转换成数字来获取要接收的数据长度 # 再发送数据 再按照长度接收数据 ret=struct.pack('i',4096) # i 代表int 就是即将要把一个数字转换成固定长度bytes类型 打包 print(ret) # b'x00x10x00x00' num=struct.unpack('i',ret) # struct.unpack 解包 的结果一定是元组 print(num) # (4096,) print(num[0]) # 4096 # 如果发送数据时候 # 先发送长度 先接收长度
SERVER # TCP 黏包 # 连续 send 两个小数据 # 两个 recv 第一个特别小 # 本质: 你不知道到底接收多大的数据 # 解决问题 例如 # tcp 完美解决了黏包问题 import socket import struct server=socket.socket() server.bind(('127.0.0.1',8700)) server.listen(5) conn, addr =server.accept() while True: cmd=input("》》》") if cmd=="q": conn.send(b'q') break conn.send(cmd.encode("gbk")) num=conn.recv(4) # 4 nn=struct.unpack('i',num)[0] # 2048 res=conn.recv(int(nn)).decode("gbk") # 2048 print(res) conn.close() server.close()
CLIENT import socket import subprocess import struct s=socket.socket() s.connect(('127.0.0.1',8700)) while True: cmd=s.recv(1024).decode('gbk') if cmd=="q": break res = subprocess.Popen(cmd, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) std_err = res.stderr.read() std_out = res.stdout.read() len_a=len(std_out)+len(std_err) num_by=struct.pack('i',len_a) ret=s.send( num_by) # 4 2048 s.send(std_err)# 1024 s.send(std_out) #1024 4+2048 print(ret) s.close()
2. 自定制报头(使用struct)
协议 报文 报头 文件传输 文件名字 文件大小 文件类型 存储路径 例如: head={'filemane':'tex.txt','filesize':60000,'filetype':'txt','filepath':r'userin'} 报头 长度 接收4个字节 send(head) 报头 根据这4个字节获取报头 send(file)报文 从报头中获取filesize 然后根据filesize接收文件 协议就是一堆报文和报头 ------字节
SERVER 视频上传 import socket import struct import json buffer=1024 # 4096 server=socket.socket() server.bind(('127.0.0.1',8600)) server.listen() conn,addr=server.accept() # 接收 head_len=conn.recv(4) 注意接收到的数据是元祖 datel=struct.unpack('i',head_len)[0] json_head=conn.recv(datel).decode("utf-8") head=json.loads(json_head) filesize=head['filesize'] with open(head['filename'],'wb')as f: while filesize: print(filesize) if filesize>=buffer: content=conn.recv(buffer) f.write(content) filesize-=buffer else: content=conn.recv(filesize) f.write(content) break conn.close() server.close()
CLIENT 视频上传 # 发送端 import os import socket import json import struct sk=socket.socket() sk.connect(('127.0.0.1',8600)) buffer=1024 # 4096 # 发送文件 head={'filepath':r'D:Vidoevide', 'filename':r'aa.mp4', 'filesize':None } file_path=os.path.join(head['filepath'],head['filename']) filesize=os.path.getsize(file_path) head['filesize']=filesize json_head=json.dumps(head) # 把字典转换成了str # print(json_head) # print(type(json_head)) bytes_head=json_head.encode("utf-8") # 字符串转换成bytes # print(bytes_head) # print(type(bytes_head)) head_len=len(bytes_head) # 计算hend长度 # print(head_len) pack_len=struct.pack('i',head_len) # print(pack_len) sk.send(pack_len) # 先发送报头的长度 注意发送过去是元祖类型数据 sk.send(bytes_head) # 再发送bytes类型的报头 with open(file_path,'rb')as f: while filesize: print(filesize) if filesize>=buffer: content = f.read(buffer) # 每次读出的内容 sk.send(content) filesize-=buffer else: content=f.read(filesize) sk.send(content) break sk.close() # 5.31 MB (5,575,110 字节) 原视频 # 5.16 MB (5,415,366 字节) 上传的视频
文件上传案例
server1 不黏包 多发送一次send 方法一 import socket import json import os server=socket.socket() server.bind(("192.168.59.1",8600)) server.listen(5) conn,addr=server.accept() ret_str=conn.recv(1024).decode("utf-8") ret=json.loads(ret_str) file_name=ret.get("filename") action=ret.get("action") file_size=ret.get("file_size") conn.sendall(b"200") with open(file_name,"wb") as f1: conn_len = 0 while file_size>conn_len: msg=conn.recv(1024) conn_len+=len(msg) f1.write(msg) print(f'文件大写{file_size}---文件传输大小{conn_len}') print("读取失败了")
client1 不黏包 多发送接收次recv' 方法一 import socket import json import os client=socket.socket() client.connect(("192.168.59.1",8600)) while True: cmd=input("请输入命令:") action,filename=cmd.strip().split(" ") flie_size=os.path.getsize(filename) ret={"action":action,"filename":filename,"file_size":flie_size} ret_str=json.dumps(ret).encode("utf-8") client.sendall(ret_str) code=client.recv(1024).decode("utf-8") if code=="200": with open(filename,"rb")as f1: for line in f1: client.sendall(line) else: print("接收失败啦啦啦啦")
server使用 struct import json,socket,struct ser=socket.socket() ser.bind(("192.168.59.1",8600)) ser.listen(5) conn,addr=ser.accept() ret=conn.recv(4) pik=struct.unpack('i',ret)[0] ret_b=conn.recv(pik).decode("utf-8") ret_len=json.loads(ret_b) filename=ret_len.get("filename") action=ret_len.get("action") file_size=ret_len.get("file_size") buff=0 with open("tup/"+filename,"wb") as f1: while file_size>buff: cont=conn.recv(1024) buff+=len(cont) f1.write(cont)
client使用 struct
import socket cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("输入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line)
文件上传(使用hashlib 验证文件一次性)
server aa 11.txt import json,socket,struct,hashlib ser=socket.socket() ser.bind(("192.168.59.1",8600)) ser.listen(5) conn,addr=ser.accept() ret=conn.recv(4) pik=struct.unpack('i',ret)[0] ret_b=conn.recv(pik).decode("utf-8") ret_len=json.loads(ret_b) filename=ret_len.get("filename") action=ret_len.get("action") file_size=ret_len.get("file_size") buff=0 md=hashlib.md5() with open("tup/"+filename,"wb") as f1: while file_size>buff: cont=conn.recv(1024) buff+=len(cont) f1.write(cont) md.update(cont) print("接收成功") conn.sendall(b"ok") print(md.hexdigest()) val_md=md.hexdigest() cli_md5=conn.recv(1024).decode("utf-8") if cli_md5==val_md: conn.sendall(b"203") else: conn.sendall(b"208")
client aa 11.txt import hashlib import socket,json,os cli=socket.socket() import os import json,struct cli.connect(("192.168.59.1",8600)) cmd=input("输入指令和文件:") # act aa.jpg action,filename=cmd.strip().split(" ") file_size=os.path.getsize(filename) ret={ "action":action, "filename":filename, "file_size":file_size } md5=hashlib.md5() ret_str=json.dumps(ret).encode("utf-8") ret_pik=struct.pack("i",len(ret_str)) cli.sendall(ret_pik) cli.sendall(ret_str) with open(filename,"rb")as f1: for line in f1: cli.sendall(line) md5.update(line) data=cli.recv(1024) print(data.decode("utf-8")) print(md5.hexdigest()) val_md=md5.hexdigest() cli.sendall(val_md.encode("utf-8")) is_val=cli.recv(1024).decode("utf-8") if is_val=="203": print("完整性") else: print("文件上次失败")
总结
可以通过struct模块来定制协议
解决黏包 问题
为什么会出现黏包现象
首页只有在tcp协议中才会黏包现象
只是因为tcp协议是面向流的协议
在发送的数据传输过程中还有缓存机制来避免数据丢包
因此 在连续发送小数据的时候 以及接收大小不符的时候容易黏包 现象
本质 还是因为我们在接收数据的时候不知道发送的数据的长短
解决黏包问题
在传输大量数据之前先告诉接收端要发送的数据长短
可以通过struct模块来定制协议
struct 模块
pack unpack
模式 i
pack之后的长度 4个字节
unpack 之后娜到的数据是一个元组 元组的第一个值是pack的值