文件下载
使用argparse库实现命令行
使用pathlib库完成目录、文件部分
使用socket库完成tcp下载
使用concurrent.futures库实现线程池
服务端
import argparse
import os
import pathlib
import socket
from concurrent.futures import ThreadPoolExecutor
class CLI(object):
def __init__(self):
parser = argparse.ArgumentParser(
prog='FileTransfer',
description='Download the file from server to client by socket',
epilog='www.911205.xyz',
)
# 可选参数
parser.add_argument('-i', dest='ip', help='服务器地址', default='0.0.0.0', type=str)
parser.add_argument('-p', dest='port', help='服务器端口', default=9876, type=int)
parser.add_argument('-f', dest='file', help='服务器文件, 支持相对路径和绝对路径', type=str)
parser.add_argument('-s', dest='size', help='单次传输文件大小, 默认1024字节(Byte), 即1KB', default=1024, type=int)
args = parser.parse_args()
print(f'服务器监听地址: {args.ip}')
print(f'服务器监听端口: {args.port}')
print(f'服务器监听文件: {pathlib.Path(args.file).resolve()}')
print(f'每次传送文件大小: {args.size}字节')
SendFile(*args.__dict__.values())
class SendFile:
def __init__(self, ip: str, port: int, file: str, size: int):
self.file, self.file_name = self.get_file(file)
# tcp服务端套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 关闭后立即释放端口(不经历TIME_WAIT阶段)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定端口
self.tcp_server_socket.bind((ip, port))
# 主动套接字变被动
self.tcp_server_socket.listen(os.cpu_count()*2)
# 提供一次性下载服务
# # 获取客户端的套接字
# self.client_socket, clinet_info = self.tcp_server_socket.accept()
# self.client_socket.send(file_name.encode('utf-8'))
# # 发送文件
# self.send_file(file, clinet_info)
# 启用线程池
thread_pool = ThreadPoolExecutor(max_workers=os.cpu_count())
# 提供多次下载服务(服务器不关闭, 多个客户端均可下载)
while True:
self.client_socket, clinet_info = self.tcp_server_socket.accept()
# self.client_socket.send(file_name.encode('utf-8'))
# self.send_file(file, size, clinet_info)
# 使用线程池同时处理多个请求
thread_pool.submit(self.send_file_in_thread_pool, *[size, clinet_info])
def get_file(self, file):
"""获取文件的绝对路径和名称"""
file, file_name = self.is_file(file)
print(f'准备发送文件: {file_name}')
return file, file_name
@staticmethod
def is_file(file):
"""判断文件是否存在并返回绝对路径和文件名称"""
file = pathlib.Path(file)
if not file.is_file():
raise FileNotFoundError(f'文件{file}不存在')
return file.resolve(), file.name
def send_file_in_thread_pool(self, size, clinet_info):
"""使用进程池, 可同时处理多个客户端请求"""
self.client_socket.send(self.file_name.encode('utf-8'))
self.send_file(size, clinet_info)
def send_file(self, size, clinet_info):
"""发送文件到客户端"""
with open(self.file, "rb") as f:
while True:
# 读取数据
result = f.read(size)
if result:
# 发送数据
self.client_socket.send(result)
else:
print(f"文件传输完成...{clinet_info}")
break
self.close_socket()
def close_socket(self):
"""关闭套接字"""
# 关闭套接字 -> 不再进行收发数据
self.client_socket.close()
# 关闭套接字 -> 不再接收其他客户端与其建立连接
# self.tcp_server_socket.close()
def main():
try:
CLI()
except FileNotFoundError as e:
print(f'错误: {e}')
except Exception as e:
print(f'错误: {e}')
if __name__ == '__main__':
main()
客户端
import argparse
import pathlib
import socket
from typing import Union
class CLI(object):
def __init__(self):
parser = argparse.ArgumentParser(
prog='FileTransfer',
description='Download the file from server to client by socket',
epilog='www.911205.xyz',
)
# 位置参数
parser.add_argument('ip', help='服务器地址', type=str)
# 可选参数
parser.add_argument('-p', dest='port', help='服务器端口', default=9876, type=int)
parser.add_argument('-o', dest='output', help='文件输出位置, 后面根文件名则表示重命名', type=str)
args = parser.parse_args()
print(f'服务器监听地址: {args.ip}')
print(f'服务器监听端口: {args.port}')
ReceiveFile(*args.__dict__.values())
class ReceiveFile:
def __init__(self, ip: str, port: int, output: Union[str, None]):
# 创建tcp客户端套接字
self.tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接
self.tcp_client_socket.connect((ip, port))
# 接收文件名称
self.file_name = self.tcp_client_socket.recv(1024).decode('utf-8')
print(f'准备下载文件: {self.file_name}')
# 下载文件
self.receive_file(output)
def receive_file(self, output):
if output:
file = pathlib.Path(output)
if file.is_dir():
file = file.joinpath(self.file_name)
else:
file = pathlib.Path(self.file_name)
file = file.resolve()
print(f'文件另存为: {file}')
with open(f'{file}', "wb") as f:
while True:
# 接收数据
recv_data = self.tcp_client_socket.recv(1024)
if recv_data:
# 写入数据
f.write(recv_data)
else:
print("文件下载成功...")
break
self.close_socket()
def close_socket(self):
"""关闭套接字"""
self.tcp_client_socket.close()
def main():
try:
CLI()
except ConnectionRefusedError as e:
print('错误: 无法连接服务器')
except Exception as e:
print(f'错误: {e}')
if __name__ == '__main__':
main()