一:背景
在之前已经实现了文件的下载,现在再来完善上传功能,并且使用面向对象来封装,让代码看起来更加清楚明了。
二: 使用规则和运行结果
-
下载文件,下载格式 get 文件名
get空格后面直接接文件名称,在服务端存放的文件名 -
上传文件,上传格式 put 文件路径+文件名
因为是上传,上传的时候需要加上文件的路径和文件的名字,客户端程序可以直接根据路径去读取文件内容发送给服务端 -
以下是本地运行的结果
上传
下载:
三:部分函数说明
1、客户端
我们把套接字家族、协议类型、最大传输字节数、编码格式,和下面目录都设置为类变量
class MyTcpClient:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding = 'gbk'
downloads_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\client\downloads'
还把每一个连接步骤也封装成为一个方法,在初始化实例的时候,要传入服务端地址
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
run函数就是处理键盘输入的命令,先解析数是put 还是get,把他存入到cmd中。
hasattr() 函数用于判断对象是否包含对应的属性。
getattr其实是使用的反射,获取到cmd 是put ,func(l) 就表示是调用put(l)方法
在run 方法中只去分析命令,并调用对应的方法。
def run(self):
while True:
inp = input(">>: ").strip() # 要求下载文件的格式get a.txt, 上传的时候带文件路径如 put e:\selenium.png
if not inp: continue # 如果发的是空就进入下一次循环
l = inp.split()
cmd = l[0]
if hasattr(self,cmd):
func = getattr(self,cmd)
func(l)
get方法,传入的参数是[‘get’,‘a.txt’], 不管是get 还是put都把命令都加上一个报头,如果{‘cmd’:cmd,‘filename’:filename,‘file_size’:10},cmd和文件名称都可以从参数中获取到,file_size在这里是没有用的,所以随便写一个都无所谓。先把报头发过去,在接收服务端返回的数据。
服务端也是先返回一个报头,告诉你文件的大小,获取到文件大小之后,再把文件写入到本地目录。
def get(self,args):
cmd = args[0]
filename = args[1]
header_dic = {'cmd':cmd,'filename':filename,'file_size':10}
#print(header_dic)
header_json = json.dumps(header_dic)
header_bytes = header_json.encode(self.coding)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes) #发送命令
#接收报头
head_struct = self.socket.recv(4)
if not head_struct: return # 适用于linux操作系统,如果客户端断开了连接
header_len = struct.unpack('i', head_struct)[0]
header_json = self.socket.recv(header_len).decode(self.coding)
header_dic = json.loads(header_json)
print(header_dic)
filename = header_dic['filename']
file_size = header_dic['file_size']
with open('%s/%s' % (self.downloads_dir, filename), 'wb') as fd:
recv_size = 0 # 接收的数据大小
while recv_size < file_size:
data = self.socket.recv(self.max_packet_size)
fd.write(data)
recv_size = recv_size + len(data)
print('总大小:%s 已下载大小:%s' % (file_size, recv_size))
put方法,这次参数是 [‘put’,‘e:\a.txt’] 这样的,filepath就表示带路径的文件,先获取到文件大小
其实在服务端是不需要你客户端的文件路径的,只需要文件名,服务端会在指定目录下创建新的文件,所以我通过\ 来切出文件的名称,传给服务端的报头就只有文件名
把包头发送过去后,再发送文件内容。
def put(self,args):
cmd = args[0]
filepath = args[1]
if not os.path.isfile(filepath):
print('file: %s is not exists' %filepath)
return
else:
file_size = os.path.getsize(filepath)
filename = str(filepath).split('\\')[-1]
header_dic = {'cmd':cmd,'filename':filename,'file_size':file_size}
print(header_dic)
header_json = json.dumps(header_dic)
header_bytes = header_json.encode(self.coding)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes)
send_size = 0
with open(filepath, 'rb') as fd:
for line in fd: # 一行一行读取
self.socket.send(line)
send_size = send_size + len(line)
print('总大小:%s 已发送大小:%s' % (file_size, send_size))
2、服务器端
其实上传和下载的方法,在服务端跟客户端只是反一下,变化不大
服务端的run方法会比较不同
因为客户端先发的是一个报头,所以服务端就先接受4字节的包头,并且客户解析出是上传还是下载,如果是上传就调用上传的方法,如果是下载就调用下载的方法,传入的参数都是报头,如header_dic = {‘cmd’: ‘get’, ‘filename’: filename, ‘file_size’: file_size }
def run(self):
while True: # 连接循环
self.conn, self.client_addr = self.get_accept()
print(self.client_addr)
while True: # 通信循环
try:
# 1、接收报头的长度
head_struct = self.conn.recv(4)
if not head_struct: break # 适用于linux操作系统,如果客户端断开了连接
header_len = struct.unpack('i',head_struct)[0]
header_json = self.conn.recv(header_len).decode(self.coding)
header_dic = json.loads(header_json)
print(header_dic)
# 2、解析出文件名称
#header_dic = {'cmd': 'get', 'filename': filename, 'file_size': file_size }
cmd = header_dic['cmd']
if hasattr(self,cmd):
func = getattr(self,cmd)
func(header_dic)
except ConnectionResetError: # 适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
break
self.conn.close()
四:完整代码
服务端代码
#--coding:utf-8--
import socket
import struct
import json
import os
class MyTcpServer:
'''
文件上传下载的服务器端
'''
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding = 'gbk'
request_queue_size = 5
server_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\server\share'
def __init__(self,server_address,bind_and_activate=True):
"""
socket 初始化
:param server_address: 服务器地址,(ip,端口)
:param bind_and_activate:
"""
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_listen()
except:
self.server_close()
raise
def server_bind(self):
"""绑定"""
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()
print(self.server_address)
def server_listen(self):
"""监听"""
self.socket.listen(self.request_queue_size)
print("Staring")
def server_close(self):
"""关闭socket"""
self.socket.close()
def get_accept(self):
"""获取跟客户端绑定的信息"""
return self.socket.accept()
def run(self):
while True:
self.conn, self.client_addr = self.get_accept()
print(self.client_addr)
while True:
try:
# 1、接收报头的长度
head_struct = self.conn.recv(4)
if not head_struct: break # 适用于linux操作系统,如果客户端断开了连接
header_len = struct.unpack('i',head_struct)[0]
header_json = self.conn.recv(header_len).decode(self.coding)
header_dic = json.loads(header_json)
print(header_dic)
# 2、解析出文件名称
#header_dic = {'cmd': 'get', 'filename': filename, 'file_size': file_size }
cmd = header_dic['cmd']
if hasattr(self,cmd):
func = getattr(self,cmd)
func(header_dic)
except ConnectionResetError: # 适用于windows系统,如果客户端断开连接,在windows系统就会报ConnectionResetError的错误
break
self.conn.close()
def get(self,args):
"""读取服务端文件发送给客户端"""
#获取文件名:
filename = args['filename']
# 3、获取到文件大小
file_size = os.path.getsize('%s\%s' % (self.server_dir, filename))
# 第一步制作固定长度的包头
header_dic = {
'cmd': args['cmd'],
'filename': filename, # a.txt
'file_size': file_size
}
# 将字典转化成字符串
header_json = json.dumps(header_dic)
# 在将字符串转换为bytes
header_bytes = header_json.encode(self.coding)
# 第二步,先发送包头的长度
self.conn.send(struct.pack('i', len(header_bytes)))
# 第三步: 发送报头
self.conn.send(header_bytes)
# 第四步:读取文件内容,发送给客户端
with open('%s\%s' % (self.server_dir, filename), 'rb') as fd:
for line in fd: # 一行一行读取
self.conn.send(line)
def put(self,args):
filename = args['filename'] # e:\selenium.png
file_size = args['file_size']
with open('%s\%s' % (self.server_dir, filename),'wb') as fd:
recv_size = 0 # 接收的数据大小
while recv_size < file_size:
data = self.conn.recv(self.max_packet_size)
fd.write(data)
recv_size = recv_size + len(data)
print('总大小:%s 已下载大小:%s' % (file_size,recv_size))
if __name__ == '__main__':
tcpserver1 = MyTcpServer(('127.0.0.1',8891))
tcpserver1.run()
客户端代码
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 = 'gbk'
downloads_dir = r'F:\myfile\python\code\python3进阶\chepter07\文件上传下载面向对象\client\downloads'
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]
filepath = args[1]
if not os.path.isfile(filepath):
print('file: %s is not exists' %filepath)
return
else:
file_size = os.path.getsize(filepath)
filename = str(filepath).split('\\')[-1]
header_dic = {'cmd':cmd,'filename':filename,'file_size':file_size}
print(header_dic)
header_json = json.dumps(header_dic)
header_bytes = header_json.encode(self.coding)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes)
send_size = 0
with open(filepath, 'rb') as fd:
for line in fd: # 一行一行读取
self.socket.send(line)
send_size = send_size + len(line)
print('总大小:%s 已发送大小:%s' % (file_size, send_size))
def get(self,args):
cmd = args[0]
filename = args[1]
header_dic = {'cmd':cmd,'filename':filename,'file_size':10}
#print(header_dic)
header_json = json.dumps(header_dic)
header_bytes = header_json.encode(self.coding)
self.socket.send(struct.pack('i', len(header_bytes)))
self.socket.send(header_bytes) #发送命令
#接收报头
head_struct = self.socket.recv(4)
if not head_struct: return # 适用于linux操作系统,如果客户端断开了连接
header_len = struct.unpack('i', head_struct)[0]
header_json = self.socket.recv(header_len).decode(self.coding)
header_dic = json.loads(header_json)
print(header_dic)
filename = header_dic['filename']
file_size = header_dic['file_size']
with open('%s/%s' % (self.downloads_dir, filename), 'wb') as fd:
recv_size = 0 # 接收的数据大小
while recv_size < file_size:
data = self.socket.recv(self.max_packet_size)
fd.write(data)
recv_size = recv_size + len(data)
print('总大小:%s 已下载大小:%s' % (file_size, recv_size))
if __name__ == '__main__':
tcpclient1 = MyTcpClient(('127.0.0.1',8891))
tcpclient1.run()