首页 > 代码库 > Socket函数编程(二)
Socket函数编程(二)
粘包问题
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:continue12 phone.send(bytes(cmd,encoding=‘utf-8‘))13 #收消息14 data = http://www.mamicode.com/phone.recv(1024) #819215 print(data.decode(‘gbk‘))16 phone.close()
#输出 长于1024字节的不行
server端
import socketimport subprocessphone=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 socket2 import subprocess3 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:continue12 phone.send(bytes(cmd,encoding=‘utf-8‘))13 #收报头14 baotou = phone.recv(4) #819215 data_size=struct.unpack(‘i‘,baotou)[0]16 17 #收数据18 recv_size=019 recv_data=http://www.mamicode.com/b‘‘20 while recv_size < data_size:21 data=http://www.mamicode.com/phone.recv(1024)22 recv_size+=len(data)23 recv_data+=data24 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:break20 res = subprocess.Popen(cmd.decode(‘utf-8‘),21 shell=True,22 stderr=subprocess.PIPE,23 stdout=subprocess.PIPE24 )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 break35 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:break21 res = subprocess.Popen(cmd.decode(‘utf-8‘),22 shell=True,23 stderr=subprocess.PIPE,24 stdout=subprocess.PIPE25 )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 break42 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:continue13 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=030 recv_data=http://www.mamicode.com/b‘‘31 while recv_size < data_size:32 data=http://www.mamicode.com/phone.recv(1024)33 recv_size+=len(data)34 recv_data+=data35 print(recv_data.decode(‘utf-8‘))36 phone.close()
报头类似于字典,先做字典,字典转成json格式,保证发过去还有结构的。把json字符串转成bytes格式,有了它可以发送报头的。
发送时:服务器端struct模块把报头长度打成bytes,先发报头长度,在发报头,再发真实数据。
接收时:客户端通过struct模块拿到报头长度,再recv收到报头长度,解码,反序列化得到字典,再从字典里拿到真实数据
FTP上传下载
服务端
import socketimport structimport jsonimport subprocessimport osclass 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 socketimport structimport jsonimport osclass 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()
Socket函数编程(二)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。