网络编程的粘包问题
定义:粘包是存在于tcp协议中的,因为tcp是传输是需要三次握手和四次挥手的,这两种模式分别是确立通道和确保数据输送完整的. 在这个过程中因为传输和接收的不一致性就会到时粘包的出现. 而在udp模式中则不存在这种现象,因为udp是只管传输,不管接收的
二 粘包出现在tcp模式中的三种情况
粘包会出现在发送端 和 接收端
1: 发送端输入的内容过小且时间间隔很短 会粘包
2:接收端一次性接受了多次传输的内容时 会粘包
3;接收端一次接收小于一次传输的内容量 下次继续接收时 会把剩下没有接收的和下次要接收的内容粘在一起
三 解决粘包的方案
粘包问题没出现之前 是按照指定的长度来进行传输数据 而粘包的问题主要是接收信息差造成的 那么让双方明确传输的长度就能能解决这问题 那么解决方案就是先传递长度 在传输内容
解决步骤:
发送端
1:利用struct将目标数据转化为固定的字节
2:发送数据长度给接收duan
3;发送数据
接收端
1;先接收长度数据 此时字节数是固定的
2:接收数据 (当发送端储传输的数据过大时,需要利用循环来进行分批次接收)
首先介绍struct 模块
是内置函数 主要功能是将数据转化为固定字节模式
struct应用
import struct # 整形转化为字节数 struct.pack() q代表的是八个字节 res = struct.pack('q',100) print(res) # print(len(res)) # 8 # 字节数转化为整形 struct.unpack() 返回的 是一个元祖 取值则取0号位就是目标整数 res2 = struct.unpack('q',res) print(res2) # (100,) print(res2[0]) # 100
四 解决粘包案例
案例一 不完善
客户端版本 import socket client= socket.socket() # 创建socket对象 client.connect(("192.168.13.80",1933)) # 连接服务器 print("链接成功!") while True: msg = input(":") if msg == "q": break if not msg: continue client.send(msg.encode("utf-8")) # 发送信息 data = client.recv(1024) # 接收数据 length= int(data.decode('utf-8')) print(length) size = 0 res = b'' while size < length: temp= client.recv(1024) size += len(temp) res += temp print(res.decode("gbk")) 注意:send 发 只能发二进制数据 recv 收 收多少字节 收发数据 需要循环 服务器版本 import socket import subprocess server = socket.socket() # 创建socket对象 server.bind(('192.168.13.80',1933)) # 绑定固定的ip和端口号 必须是元祖 server.listen() # 开始监听客户端的信息 while True: client_socket,client_addr = server.accept() # 接收客户端的连接请求 print('接到对方信息 开始工作了') while True: data = client_socket.recv(1024).decode('utf-8') # 收数据 print(data) if not data: break p = subprocess.Popen(data,shell= True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) data = p.stdout.read()+p.stderr.read() length= str(len(data)) client_socket.send(length.encode('utf-8')) # 发长度数据 print(length) # 发长度 client_socket.send(data) # 发具体数据
案例一 不完善的地方 : 但是由于negle优化机制的存在,长度信息和数据还是有可能会粘包,而接受方并不知道长度信息具体几个字节,所以现在的问题是如何能够长度信息做成一个固定长度的bytes数据
案例二 引用struct模块
客户端 import socket import struct c = socket.socket() c.connect(("127.0.0.1",8888)) while True: cmd = input(">>>:").strip() c.send(cmd.encode("utf-8")) data = c.recv(4) length = struct.unpack("i",data)[0] print(length) size = 0 res = b"" while size < length: temp = c.recv(1024) size += len(temp) res += temp print(res.decode("gbk"))
服务器 import socket import subprocess import struct server = socket.socket() server.bind(("127.0.0.1",8888)) server.listen() while True: client, addr = server.accept() while True: cmd = client.recv(1024).decode("utf-8") p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1) data = p.stdout.read()+p.stderr.read() length = len(data) len_data = struct.pack("i",length) client.send(len_data) print(length) client.send(data)
老师版本 """ 客户端输入指令 服务器接收指令并执行 最后返回执行结果 """ 1 导入的模块 from 二_CMD程序 import smallTool import struct def recv_data(client): # 接收长度数据 固定位8个字节 len_bytes = client.recv(8) # 转换为整型 len_size = struct.unpack("q", len_bytes)[0] print("服务器返回了%s长度的数据" % len_size) # 再接收真实数据 # 问题 如果数据量太大 则不能 一次收完 必须循环一次收一部分 # 缓冲区大小 buffer_size = 1024 # 已接受大小 recv_size = 0 # 最终的数据 data = b"" while True: # 如果剩余数据长度 大于缓存区大小 则缓冲区有多大就读多大 if len_size - recv_size >= buffer_size: temp = client.recv(buffer_size) else: # 剩余数据长度 小于缓冲区大小 剩多少就收多少 temp = client.recv(len_size - recv_size) recv_size += len(temp) data += temp # 当已接受大小等于数据总大小则跳出循环 if recv_size == len_size: break # print(data.decode("gbk")) # windows执行指令返回的结果默认为GBK return data 2 客户端 import socket from 二_CMD程序 import smallTool import struct client = socket.socket() try: client.connect(("127.0.0.1",1688)) print("链接成功!") while True: msg = input("请输入要执行指令:").strip() if msg == "q": break if not msg: continue # 发送指令 # 先发长度 len_bytes = struct.pack("q",len(msg.encode("utf-8"))) client.send(len_bytes) # 在发指令 client.send(msg.encode("utf-8")) data = smallTool.recv_data(client) print(data.decode("GBK")) client.close() except ConnectionRefusedError as e: print("链接服务器失败了!",e) except ConnectionResetError as e: print("服务器挂了!", e) client.close() 3 服务器 import socket import subprocess import struct from 二_CMD程序 import smallTool server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(("127.0.0.1",1688)) server.listen() # back while True: # socket,addr一个元组 客户端的ip和port client,addr = server.accept() print("客户端链接成功!") # 循环收发数据 while True: try: cmd = smallTool.recv_data(client) if not cmd: break print(cmd) p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # 不要先读err错误信息 它会卡主 原因不详 linux不会有问题 tasklist netstat - ano啥的 data = p.stdout.read() err_data = p.stderr.read() len_size = len(data) + len(err_data) print("服务器返回了: %s " % len_size) len_bytes = struct.pack("q",len_size) # 在发送真实数据前先发送 长度 client.send(len_bytes) # 返回的结果刚好就是二进制 # 发送真实数据 client.send(data + err_data) except ConnectionResetError as e: print("客户端了挂了!",e) break client.close() #server.close()
五 自定义报头
粘包问题解决了 就可以按照思路去添加其他内容 其实就是粘包的延伸
什么时候用报头
当需要在传输数据时 传呼一些额外参数时就需要自定义报头
报头本质是一个json 数据
具体过程如下:
发送端
1 发送报头长度
2 发送报头数据 其中包含了文件长度 和其他任意的额外信息
3 发送文件内容
接收端
1.接收报头长度
2.接收报头信息
3.接收文件内容
服务器 import socket import os import struct import json """ 客户端接链成功我就给你发个文件过去 固定的文件下载 """ server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(("127.0.0.1",1688)) server.listen() # back while True: # socket,addr一个元组 客户端的ip和port client,addr = server.accept() print("客户端链接成功!") f = None try: path = r"F:\2.半链接数.mp4" file_size = os.path.getsize(path) # 我想把文件名发过去 file_info = {"file_name":"半链接数.mp4","file_size":file_size,"md5":"xxxxxxxxx"} json_str = json.dumps(file_info).encode("utf-8") # 发送报头长度 client.send(struct.pack("q",len(json_str))) # 发报头 client.send(json_str) # 发文件了 # 发送文件数据 f = open(path,"rb") # 循环发送文件内容 每次发2048 while True: temp = f.read(2048) if not temp: break client.send(temp) print("文件发送完毕!") except Exception as e: print("出问题了",e) finally: if f:f.close() client.close() # 无论是否抛出异常 文件都要关闭 #server.close() # 用户可以指定要下载什么文件 FTP
""" 客户端输入指令 服务器接收指令并执行 最后返回执行结果 """ import socket import struct import json client = socket.socket() try: client.connect(("127.0.0.1",1688)) print("链接成功!") # 1.先收报头长度 head_size = struct.unpack("q",client.recv(8))[0] # 2.收报头数据 head_str = client.recv(head_size).decode("utf-8") file_info = json.loads(head_str) print("报头数据:",file_info) file_size = file_info.get("file_size") file_name = file_info.get("file_name") # 3.再收文件内容 # 已接收大小 recv_size = 0 buffer_size = 2048 f = open(file_name,"wb") while True: if file_size - recv_size >= buffer_size: temp = client.recv(buffer_size) else: temp = client.recv(file_size - recv_size) f.write(temp) recv_size += len(temp) print("已下载:%s%%" % (recv_size / file_size * 100)) if recv_size == file_size: break f.close() except ConnectionRefusedError as e: print("链接服务器失败了!",e)