python之socket编程3
1 什么是粘包
只有TCP有粘包现象,UDP永远不会粘包
应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向连接的,面向流的,收发两端都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法,将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这也是容易出现粘包问题的原因;
而UDP面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据。
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
两种情况下会发生粘包
(1.)发送端需要等缓冲区满才发送出去,造成粘包。发送数据时间间隔很短,数据很小会合到一起,产生粘包
(2)接收方不及时收取缓冲区的包,造成多个包接收,客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包
#服务端 import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.bind(ip_port) phone.listen(5) conn,addr=phone.accept() data1=conn.recv(1024) data2=conn.recv(1024) print("第1个包",data1) print("第2个包",data2) #客户端 import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.connect(ip_port) phone.send("helloworld".encode("utf8")) phone.send("SB".encode("utf8")) >> 第1个包 b'helloworldSB'#产生粘包现象 第2个包 b''
使用时间模块,并不能够彻底解决问题
#服务端 import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.bind(ip_port) phone.listen(5) conn,addr=phone.accept() data1=conn.recv(1024) data2=conn.recv(1024) print("第1个包",data1) print("第2个包",data2) #客户端 import socket,time phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.connect(ip_port) phone.send("helloworld".encode("utf8")) time.sleep(3) phone.send("SB".encode("utf8")) >> 第1个包 b'helloworld'#send先执行一次后,发送至客户端内存 第2个包 b'SB'#延迟三秒后,,再执行发送
#服务端 import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.bind(ip_port) phone.listen(5) conn,addr=phone.accept() data1=conn.recv(1) data2=conn.recv(1024) print("第1个包",data1) print("第2个包",data2) #客户端 import socket,time phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.0.0.1",8080) phone.connect(ip_port) phone.send("helloworld".encode("utf8")) time.sleep(3) phone.send("SB".encode("utf8")) >> 第1个包 b'h' #recv第一次只取一个字节 第2个包 b'elloworld' #第二次再次执行 ,”SB”还在服务器的内存中
2 自定制报头解决粘包问题
数据封装报头:
固定长度
包含对将要发送数据的描述信息
struct模块 import struct print(struct.pack("i",111)) >> b'o\x00\x00\x00' #转成字节模式 import struct res=struct.pack("i",111)#struct.pack 打包 print(len(res)) >> 4 #转成字节长度为固定4 import struct res=struct.pack("i",111) # print(len(res)) print(struct.unpack("i",res))#解包 (111,)#获得以元组形式的结果
简单实现:
#服务端 import socket import struct import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.168.0.1",8080) phone.bind(ip_port) phone.listen(5) #连接循环 while True: conn,addr=phone.accept() print("cline addr",addr) #通讯循环 while True: try: cmd=conn.recv(1024) res=subprocess.Popen(cmd.decode("utf8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out_res=res.stdout.read() err_res=res.stderr.read() data_size=len(out_res)+len(err_res) #发送报头 conn.send(struct.pack("i",data_size)) #发送数据部分 conn.send(out_res) conn.send(err_res) except Exception: break conn.close() phone.close() #客户端 import socket import struct phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.168.0.1",8080) phone.connect(ip_port) #通信循环 while True: #发消息 cmd=input(">>").strip() if not cmd:continue phone.send(bytes(cmd,encoding="utf8")) #收报头 baotou=phone.recv(4) data_size=struct.unpack("i",baotou)[0] #收数据 recv_size=0 recv_data=b"" while recv_size<data_size: data=phone.recv(1024) recv_size+=len(data) recv_data+=data print(recv_data.decode("gbk")) phone.close()
完全解决:
#服务端 import socket import struct import subprocess import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.168.0.1",8080) phone.bind(ip_port) phone.listen(5) #连接循环 while True: conn,addr=phone.accept() print("cline addr",addr) #通讯循环 while True: try: cmd=conn.recv(1024) res=subprocess.Popen(cmd.decode("utf8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out_res=res.stdout.read() err_res=res.stderr.read() data_size=len(out_res)+len(err_res) head_dic={"data_size":data_size} head_json=json.dumps(head_dic) head_bytes=head_json.encode("utf8") #发送报头 head_len=len(head_bytes) conn.send(struct.pack("i",data_size)) conn.send(head_bytes) #发送数据部分 conn.send(out_res) conn.send(err_res) except Exception: break conn.close() phone.close() #客户端 import socket import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=("127.168.0.1",8080) phone.connect(ip_port) #通信循环 while True: #发消息 cmd=input(">>").strip() if not cmd:continue phone.send(bytes(cmd,encoding="utf8")) #收报头的长度 head_struct=phone.recv(4) head_len=struct.unpack("i",head_len)[0] #再收报头 head_bytes=phone.recv(head_len) head_json=head_bytes.decode("utf8") head_dic=json.loads(head_json) print(head_dic) data_size=head_dic["data_size"] #收数据 recv_size=0 recv_data=b"" while recv_size<data_size: data=phone.recv(1024) recv_size+=len(data) recv_data+=data print(recv_data.decode("gbk")) phone.close()