一、struct模块
python中struct 模块用于python数据结构与C结构之间的相互转换,其中C结构是用一种格式化字符串表示的,学习struct 模块的难点就在这个格式化字符串上,强烈建议最好了解下C语言结构体的相关知识点,如果比较熟悉C语言结构体及对齐,学习struct 模块轻而易举。
官方英文文档:struct — Interpret strings as packed binary data
引用来源:https://blog.csdn.net/guoyajie1990/article/details/81044929
二、自制报文格式
headers = {‘file_name’: filename, ‘file_path’: filepath, ‘file_size’: None}
类型 | 方法 | 描述 |
---|---|---|
报头 | head=struct.pack(‘i’, ‘序列化之后报文的长度’) | 长度4个,服务端接受时候可以根据这个设置接受长度,避免沾包 |
发送报头 | send(head) | 服务端可以根据4个字节获取报头 |
发送报文 | send(‘序列化之后的headers’) | 服务端根据报文内文件大小,设置接受长度,避免沾包 |
网络传输过程中处处有协议,协议就是一堆报头和报文,协议的解析过程不需要关心,协议本质上就是一种约定
FTP上传功能实例
1、服务端代码
代码均有注释,方便理解,如有不懂,评论或者私信,一起学习一起进步
import socket
import struct,pickle
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 获取tcp/ip套接字
tcpSock.bind(('127.0.0.1', 8898)) # 绑定 (主机,端口号)到套接字
tcpSock.listen(1) # 开始监听,设置半进程池为1
print('====服务端等待请求中====')
while True:
conn, addr = tcpSock.accept() # 4、被动(阻塞式)接受TCP客户的连接
print('新用户:', addr)
# 上传功能
while True:
try:
head_len = conn.recv(4) # 接受报头长度,报头均为4个字节
if not head_len:break # 如果传入的数据为空,容易造成卡顿。
head_len = struct.unpack('i', head_len)[0] #报文的长度
bytes_head = conn.recv(head_len)
if not bytes_head: break
headers = pickle.loads(bytes_head) # 将字典反序化为字符串
file_size = int(headers['file_size']) # 获取上传文件大小
with open(headers['file_name'], 'wb')as f:
while file_size:
if file_size >= 1024:
f.write(conn.recv(1024))
file_size -= 1024
else:
f.write(conn.recv(file_size))
break
print('收到用户:%s 上传的:%s 文件' %(addr, headers['file_name']))
except ConnectionResetError:
print('===开始连接新用户===')
break
conn.close()
tcpSock.close() # 回收系统资源
2、客户端代码
from socket import *
import os, pickle, struct
Client = socket(AF_INET, SOCK_STREAM) # 获取tcp/ip套接字
Client.connect(('127.0.0.1', 8898)) # 请求服务器 (主机,端口号)到套接字
filepath = input('请输入文件路径:')
filename = input('请输入文件名')
# filepath = r'C:\Users\Administrator\Desktop\Day\Jasn--70--Days\leetcode刷题'
# filename = r'01两数之和.py'
# 设置报头格式(报文)
headers = {'file_name': filename, 'file_path': filepath, 'file_size': None}
file_path = os.path.join(headers['file_path'], headers['file_name']) # 拼接绝对路径,获取上传文件路径
file_size = os.path.getsize(file_path) # 获取上传文件大小
headers['file_size'] = file_size
bytes_head = pickle.dumps(headers) # 字典转bytes
head_len = len(bytes_head)
pack_len = struct.pack('i', head_len) # 制作报头
Client.send(pack_len) # 先传报头长度 ,长度一般为4个字节
Client.send(bytes_head) # 再传报文信息
# 上传文件
with open(file_path, 'rb')as f:
while file_size:
if file_size >= 1024: # 一次限制上传内容为1024字节
cont = f.read(1024)
Client.send(cont)
file_size -= 1024
else:
cont = f.read(file_size)
Client.send(cont)
break
Client.close() # 回收套接字资源
知识点:
read([size])方法:从文件当前位置起读取size个字节,若无参数size,则表示读取至文件结束为止,它范围为字符串对象。