网络编程-socket黏包现象
黏包现象:多个包粘黏到一起,即这次收到的结果还是上一次的结果
bug1:服务端在回复数据时采用了“+”号
bug2:客户端指定接收1024字节
黏包产生原理:
- 不管是recv还是send都不是直接接收对方数据,而是操作系统内存,不是一个send对应一个recv
- recv:wait data耗时非常长,send:copy data
黏包问题只出现在TCP中,UDP中没有此现象
系统会将数据量小的且间隔时间断的通过算法合并到一起发送
简单方法解决黏包问题:
发送问题之前,先将数据信息发送给对方,让对方知道如何接收数据。
自定义定长的数据报头,对方再接收定长数据
补充知识:数据打包
import struct #打包数据
'l'模式对打包数据无规定 res=struct.pack('i',11111) print(res,type(res),len(res)) #解包数据 obj=struct.unpack('i',res) print(obj)
服务端:
import socket import subprocess import struct 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) while True: conn,client_addr=phone.accept() print(client_addr) while True: try: #1、接收命令 cmd=conn.recv(1024) if not cmd: break #2、执行命令并拿到结果 obj=subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout=obj.stdout.read() stderr=obj.stderr.read() #第一步:制作固定长度报头 total_size=len(stdout)+len(stderr) header=struct.pack('i',total_size) #第二步,发送报头 conn.send(header) #b、再发送真实数据 conn.send(stdout+stderr)#“+”号需要优化 except ConnectionResetError: break conn.close()#连接关闭 phone.close()#套接字关闭
- 制作固定长度报头
- 将报头发送给客户端
- 再发送真实数据
客户端:
import socket import struct client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8080)) #通信循环 while True: cmd=input('>> ').strip() if not cmd:continue#避免用户输入空字符串导致客户端卡死 client.send(cmd.encode('utf-8')) #1、先拿到数据长度(包头),取得有效数据 header=client.recv(4) total_size=struct.unpack('i',header)[0] recv_size=0 recv_data=b'' #直到在循环中接收完才退出循环,再次接收数据 while recv_size < total_size: res=client.recv(1024) recv_data+=res recv_size+=len(res) print(recv_data.decode('gbk')) #关闭连接 client.close()
- 先收报头
- 从报头中解析出对真实数据的藐视信息(数据长度)
- 接收真实数据
终极方法解决黏包问题:
server:
- 自定义一个字典形式的报头,包含文件所有信息
- 用struct模块将文件信息打包成固定长度的报头
- 发送固定长度的文件信息
- 发送真实数据
import socket,os 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) while True: conn,client_addr=phone.accept() print(client_addr) while True: try: #1、接收命令 cmd=conn.recv(1024) if not cmd: break #2、执行命令并拿到结果 obj=subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout=obj.stdout.read() stderr=obj.stderr.read() #第一步:制作固定长度报头 total_size=len(stdout)+len(stderr) header_dic={'filename':"a.txt", 'md5':'xxxxxxx', 'total_size':len(stdout)+len(stderr)} header_json=json.dumps(header_dic) header_bytes=header_json.encode('utf-8') header=struct.pack('i',len(header_bytes)) #第二步,先发送报头的长度 conn.send(header) #第三步,发送报头 conn.send(header_bytes) #第四步,发送真实数据 conn.send(stdout+stderr)#“+”号需要优化 except ConnectionResetError: break conn.close()#连接关闭 phone.close()#套接字关闭
client:
- 接收固定长度的报头
- 从报头中得知文件信息报文大小
- 获取文件信息数据
- 从文件信息数据中获取真实数据大小
- 接收真实数据
import socket # client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # client.connect(('127.0.0.1',8080)) # client.send('hello'.encode('gbk')) # client.send('world'.encode('gbk')) # # while True: # # #1、发送命令 # # cmd=input('>> ').strip() # # if not cmd:continue # # client.send(cmd.encode('gbk')) # # #2、拿到结果并打印 # # data=client.recv(1024).decode('gbk')#1024是坑 # # print(data) # client.close() import socket import struct import json client=socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8080)) #通信循环 while True: cmd=input('>> ').strip() if not cmd:continue#避免用户输入空字符串导致客户端卡死 client.send(cmd.encode('utf-8')) #1、先拿到数据长度(包头),取得有效数据 header=client.recv(4) header_size=struct.unpack('i',header)[0] #2、接收报头 header_bytes=client.recv(header_size) #3、从报头中获取有用数据 header_json=header_bytes.decode('utf-8') header_dic=json.loads(header_json) print(header_json) total_size=header_dic['total_size'] recv_size=0 recv_data=b'' #直到在循环中接收完才退出循环,再次接收数据 while recv_size < total_size: res=client.recv(1024) recv_data+=res recv_size+=len(res) print(recv_data.decode('gbk')) #关闭连接 client.close()