粘包问题
1.修改数据长度:
client端
1 import socket 2 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 #拨通电话 5 ip_port = (‘127.0.0.1‘,8081) 6 phone.connect(ip_port) 7 8 while True: 9 #发消息 10 cmd = input(‘>>: ‘).strip() 11 if not cmd:continue 12 phone.send(bytes(cmd,encoding=‘utf-8‘)) 13 #收消息 14 data = phone.recv(1024) #8192 15 print(data.decode(‘gbk‘)) 16 phone.close() #输出 长于1024字节的不行
server端
import socket import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM tcp # ip_port=(‘127.0.0.1‘,808) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ip_port=(‘127.0.0.1‘,8081) phone.bind(ip_port) phone.listen(5) #开机 while True: conn,client_addr=phone.accept() while True: try: cmd = conn.recv(1024) if not cmd:break res = subprocess.Popen(cmd.decode(‘utf-8‘), shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE ) # err=res.stderr.read() # if err: # cmd_res=err # else: # cmd_res=res.stdout.read() conn.send(res.stdout.read()) conn.send(res.stderr.read()) except Exception: break conn.close() phone.close()
client端
1 import socket 2 import subprocess 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 #拨通电话 5 ip_port = (‘127.0.0.1‘,8080) 6 phone.connect(ip_port) 7 8 phone.send(‘helloworld‘.encode(‘utf-8‘)) 9 phone.send(‘SB‘.encode(‘utf-8‘)) #输出
第一个包 b‘helloworldSB‘
第二个包 b‘‘
服务器端
1 import socket 2 import subprocess 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM tcp 4 ip_port=(‘127.0.0.1‘,8080) 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 7 phone.bind(ip_port) 8 phone.listen(5) #开机 9 conn,client_addr=phone.accept() 10 11 data1=conn.recv(1024) 12 data2=conn.recv(1024) 13 14 print(‘第一个包‘,data1) 15 print(‘第二个包‘,data2)
改进
2.修改时间长度
client端
1 import socket,time 2 import subprocess 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 #拨通电话 5 ip_port = (‘127.0.0.1‘,8080) 6 phone.connect(ip_port) 7 8 phone.send(‘helloworld‘.encode(‘utf-8‘)) #客户端都是发到自己的缓存里了 9 time.sleep(3) #网络延迟没有三秒长 10 phone.send(‘SB‘.encode(‘utf-8‘))
server端
1 import socket 2 import subprocess,time 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM tcp 4 ip_port=(‘127.0.0.1‘,8080) 5 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 7 phone.bind(ip_port) 8 phone.listen(5) #开机 9 conn,client_addr=phone.accept() 10 11 data1=conn.recv(1) #conn.recv(1024) 12 time.sleep(5) 13 data2=conn.recv(1024) 14 15 print(‘第一个包‘,data1) 16 print(‘第二个包‘,data2)
TCP流式协议,
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。(TCP是流式协议)
此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。
- TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
- UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
- tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。
所以,发包时一定要给包定义一个头部。
3.stuct模块解决粘包
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
报头: 1.固定长度 2.包含对发送数据的描述信息
自定义报头(普通)
client端
1 import socket 2 import struct 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 #拨通电话 5 ip_port = (‘192.168.16.134‘,8080) 6 phone.connect(ip_port) 7 #通信循环 8 while True: 9 #发消息 10 cmd = input(‘>>: ‘).strip() 11 if not cmd:continue 12 phone.send(bytes(cmd,encoding=‘utf-8‘)) 13 #收报头 14 baotou = phone.recv(4) #8192 15 data_size=struct.unpack(‘i‘,baotou)[0] 16 17 #收数据 18 recv_size=0 19 recv_data=b‘‘ 20 while recv_size < data_size: 21 data=phone.recv(1024) 22 recv_size+=len(data) 23 recv_data+=data 24 print(recv_data.decode(‘utf-8‘)) 25 phone.close()
server端
1 #/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import socket 4 import struct 5 import subprocess 6 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 7 ip_port=(‘192.168.16.134‘,8080) 8 9 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 10 phone.bind(ip_port) 11 12 phone.listen(5) 13 while True: 14 conn,client_addr=phone.accept() 15 16 while True: 17 try: 18 cmd = conn.recv(1024) 19 if not cmd:break 20 res = subprocess.Popen(cmd.decode(‘utf-8‘), 21 shell=True, 22 stderr=subprocess.PIPE, 23 stdout=subprocess.PIPE 24 ) 25 out_res=res.stdout.read() 26 err_res=res.stderr.read() 27 data_size=str(len(out_res)+len(err_res)).encode(‘utf-8‘) 28 #发送报头 29 conn.send(struct.pack(‘i‘,data_size)) 30 #发送数据部分 31 conn.send(out_res) 32 conn.send(err_res) 33 except Exception: 34 break 35 36 conn.close() 37 phone.close()
struct.error: ‘i‘ format requires -2147483648 <= number <= 2147483647 #这个是范围
自定义报头(jason)
server端
1 #/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import socket 4 import struct 5 import subprocess 6 import json 7 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 8 ip_port=(‘192.168.16.134‘,8080) 9 10 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 11 phone.bind(ip_port) 12 13 phone.listen(5) 14 while True: 15 conn,client_addr=phone.accept() 16 17 while True: 18 try: 19 cmd = conn.recv(1024) 20 if not cmd:break 21 res = subprocess.Popen(cmd.decode(‘utf-8‘), 22 shell=True, 23 stderr=subprocess.PIPE, 24 stdout=subprocess.PIPE 25 ) 26 out_res=res.stdout.read() 27 err_res=res.stderr.read() 28 data_size=len(out_res)+len(err_res) 29 head_dic={‘data_size‘:data_size} 30 head_json=json.dumps(head_dic) 31 head_bytes=head_json.encode(‘utf-8‘) 32 #part1:先发报头的长度 33 head_len=len(head_bytes) 34 conn.send(struct.pack(‘i‘,head_len)) 35 #part2:再发送报头 36 conn.send(head_bytes) 37 #part3:最后发送数据部分 38 conn.send(out_res) 39 conn.send(err_res) 40 except Exception: 41 break 42 43 conn.close() 44 phone.close()
client端
1 import socket 2 import struct 3 import json 4 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 5 #拨通电话 6 ip_port = (‘192.168.16.134‘,8080) 7 phone.connect(ip_port) 8 #通信循环 9 while True: 10 #发消息 11 cmd = input(‘>>: ‘).strip() 12 if not cmd:continue 13 phone.send(bytes(cmd,encoding=‘utf-8‘)) 14 15 #part1:先收报头的长度 16 head_struct=phone.recv(4) 17 print(struct.unpack(‘i‘,head_struct)) 18 head_len=struct.unpack(‘i‘,head_struct)[0] 19 20 #part2:再收报头 21 head_bytes=phone.recv(head_len) 22 head_json=head_bytes.decode(‘utf-8‘) 23 24 head_dic=json.loads(head_json) 25 print(head_dic) 26 data_size=head_dic[‘data_size‘] 27 28 #part3收数据 29 recv_size=0 30 recv_data=b‘‘ 31 while recv_size < data_size: 32 data=phone.recv(1024) 33 recv_size+=len(data) 34 recv_data+=data 35 print(recv_data.decode(‘utf-8‘)) 36 phone.close()
报头类似于字典,先做字典,字典转成json格式,保证发过去还有结构的。把json字符串转成bytes格式,有了它可以发送报头的。 发送时:服务器端struct模块把报头长度打成bytes,先发报头长度,在发报头,再发真实数据。接收时:客户端通过struct模块拿到报头长度,再recv收到报头长度,解码,反序列化得到字典,再从字典里拿到真实数据
FTP上传下载
服务端
import socket import struct import json import subprocess import os class MYTCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding=‘utf-8‘ request_queue_size = 5 server_dir=‘file_upload‘ def __init__(self, server_address, bind_and_activate=True): """Constructor. May be extended, do not override.""" self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise def server_bind(self): """Called by constructor to bind the socket. """ if self.allow_reuse_address: self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.socket.bind(self.server_address) self.server_address = self.socket.getsockname() def server_activate(self): """Called by constructor to activate the server. """ self.socket.listen(self.request_queue_size) def server_close(self): """Called to clean-up the server. """ self.socket.close() def get_request(self): """Get the request and client address from the socket. """ return self.socket.accept() def close_request(self, request): """Called to clean up an individual request.""" request.close() def run(self): while True: self.conn,self.client_addr=self.get_request() print(‘from client ‘,self.client_addr) while True: try: head_struct = self.conn.recv(4) if not head_struct:break head_len = struct.unpack(‘i‘, head_struct)[0] head_json = self.conn.recv(head_len).decode(self.coding) head_dic = json.loads(head_json) print(head_dic) #head_dic={‘cmd‘:‘put‘,‘filename‘:‘a.txt‘,‘filesize‘:123123} cmd=head_dic[‘cmd‘] if hasattr(self,cmd): func=getattr(self,cmd) func(head_dic) except Exception: break def put(self,args): file_path=os.path.normpath(os.path.join( self.server_dir, args[‘filename‘] )) filesize=args[‘filesize‘] recv_size=0 print(‘----->‘,file_path) with open(file_path,‘wb‘) as f: while recv_size < filesize: recv_data=self.conn.recv(self.max_packet_size) f.write(recv_data) recv_size+=len(recv_data) print(‘recvsize:%s filesize:%s‘ %(recv_size,filesize)) tcpserver1=MYTCPServer((‘127.0.0.1‘,8080)) tcpserver1.run() #下列代码与本题无关 class MYUDPServer: """UDP server class.""" address_family = socket.AF_INET socket_type = socket.SOCK_DGRAM allow_reuse_address = False max_packet_size = 8192 coding=‘utf-8‘ def get_request(self): data, client_addr = self.socket.recvfrom(self.max_packet_size) return (data, self.socket), client_addr def server_activate(self): # No need to call listen() for UDP. pass def shutdown_request(self, request): # No need to shutdown anything. self.close_request(request) def close_request(self, request): # No need to close anything. pass 服务端
import socket import struct import json import os class MYTCPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM allow_reuse_address = False max_packet_size = 8192 coding=‘utf-8‘ request_queue_size = 5 def __init__(self, server_address, connect=True): self.server_address=server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except: self.client_close() raise def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: inp=input(">>: ").strip() if not inp:continue l=inp.split() cmd=l[0] if hasattr(self,cmd): func=getattr(self,cmd) func(l) def put(self,args): cmd=args[0] filename=args[1] if not os.path.isfile(filename): print(‘file:%s is not exists‘ %filename) return else: filesize=os.path.getsize(filename) head_dic={‘cmd‘:cmd,‘filename‘:os.path.basename(filename),‘filesize‘:filesize} print(head_dic) head_json=json.dumps(head_dic) head_json_bytes=bytes(head_json,encoding=self.coding) head_struct=struct.pack(‘i‘,len(head_json_bytes)) self.socket.send(head_struct) self.socket.send(head_json_bytes) send_size=0 with open(filename,‘rb‘) as f: for line in f: self.socket.send(line) send_size+=len(line) print(send_size) else: print(‘upload successful‘) client=MYTCPClient((‘192.168.16.134‘,8080)) client.run()
客户端
时间: 2024-10-10 06:41:14