二、解决黏包问题
2.1 解决黏包方法1
- 计算消息实体的大小
- 服务端接受两次,一次时消息大小,二次是消息实体,解决消息实体黏包
- 客户端发送两次,一次是消息大小,一次是消息实体
- 在两次收发之间加入一次多余通信,以防止消息大小和消息实体黏包
server端
import socket sk = socket.socket() sk.bind(('127.0.0.1',9000)) sk.listen() conn,addr = sk.accept() print(conn,addr) while True: cmd = input('请输入你的命令: ') if cmd == 'q': break conn.send(cmd.encode('gbk')) data_length = conn.recv(1024).decode('gbk') conn.send(b'ok') # 接受到消息大小后马上send,这样可以隔开两次recv,并且第二次接受指定长度的消息 data = conn.recv(int(data_length)).decode('gbk') print(data) conn.close() sk.close()
client
import socket import subprocess sk = socket.socket() sk.connect(('127.0.0.1',9000)) while True: cmd = sk.recv(1024).decode('gbk') ret = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) std_out = ret.stdout.read() std_err = ret.stderr.read() data_length = str(len(std_out)+len(std_err)).encode('gbk') sk.send(data_length) sk.recv(1024) # send消息大小后马上转入recv,隔开两次send。 sk.send(std_out) sk.send(std_err) sk.close()
2.2 借助于struct模块
server端
import socket import struct sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() while True: cmd = input('请输入命令: ') conn.send(cmd.encode('utf-8')) data_length_struct = conn.recv(4) data_length = struct.unpack('i',data_length_struct)[0] res = conn.recv(data_length).decode('gbk') print(res) conn.close() sk.close()
client端
import socket import subprocess import struct sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: cmd = sk.recv(1024).decode('utf-8') res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = res.stdout.read() std_err = res.stderr.read() data_length = len(stdout) + len(std_err) data_length_struct = struct.pack('i',data_length) sk.send(data_length_struct) sk.send(stdout) sk.send(std_err) sk.close()
2.3 通过struct定制报头传输大文件
server端
import socket import struct import json sk = socket.socket() sk.bind(('127.0.0.1',8090)) sk.listen() buffer = 2048 conn,addr = sk.accept() # 先接受报头 # 根据报头接受消息实体 struct_length = conn.recv(4) # 接受struct长度的包 bytes_head_length = struct.unpack('i',struct_length)[0] # 计算报头的长度 bytes_head = conn.recv(bytes_head_length) # 接受报头 json_head = bytes_head.decode('utf-8') # 将报头转换为str head = json.loads(json_head) # 将报头转换为字典 fileSize = head['fileSize'] with open(head['fileName'],'wb') as f: while fileSize: if fileSize >= buffer: content = conn.recv(buffer) f.write(content) fileSize -= buffer else: content = conn.recv(fileSize) f.write(content) break conn.close() sk.close()
client端
import socket import os import json import struct sk = socket.socket() sk.connect(('127.0.0.1',8090)) buffer = 2048 # 定制报头 # 先发送报头大小 # 发送报头 # 发送消息实体 head = {'fileSize':None, 'fileName':r'a.tar.gz', 'filePath':r'C:\Users\王诚\Desktop'} file = os.path.join(head['filePath'],head['fileName']) fileSize = os.path.getsize(file) head['fileSize'] = fileSize json_head = json.dumps(head) # 字典转换成了字符串 bytes_head = json_head.encode('utf-8') # 将字符串的head转换为bytes类型 bytes_head_length = len(bytes_head) # 计算bytes类型的head的大小 struct_length = struct.pack('i',bytes_head_length) # 通过struct转换为固定长度的bytes sk.send(struct_length) # 先发报头的长度 sk.send(bytes_head) # 发报头 with open(file,'rb') as f: while fileSize: if fileSize >= buffer: content= f.read(buffer) sk.send(content) fileSize -= buffer else: content = f.read(fileSize) sk.send(content) break sk.close()
三、检查客户端的合法性
使用hmac
server端
import socket import os import hmac secret_key = b'wangys' sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() def check_conn(conn): msg = os.urandom(32) conn.send(msg) h = hmac.new(secret_key,msg) server_msg = h.digest() client_msg = conn.recv(1024) res = hmac.compare_digest(server_msg,client_msg) return res conn,addr = sk.accept() res = check_conn(conn) if res: print('合法的客户端') conn.close() else: print('不合法的客户端') conn.close() sk.close()
client端
import socket import hmac secret_key = b'wangys' sk = socket.socket() sk.connect(('127.0.0.1',8080)) msg = sk.recv(1024) h = hmac.new(secret_key,msg) client_msg = h.digest() sk.send(client_msg) sk.close()