Python利用tcp_socket实现简单文件下载功能
文章目录
一、客户端
客户端比较好写一点,我们先来实现客户端的功能
客户端实现原理:
- 创建tcp_socket, 通过IP地址与端口连接服务端
- 通过input函数获取filename并发送到服务端
- 接收到来自服务端发来的文件内容
- 判断发来的文件内容是否为空,如果为空,代表服务端发送异常,停止创建文件
- 如果不为空,创建新文件,写入服务端发来的内容
服务端发送的都是byte类型的数据,所以我们用wb方式写入文件
代码如下:
import os
import socket
def main():
tcp_download_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_download_socket.connect((b'127.0.0.1',7788))
while True:
file_name=input('请输入要下载的文件内容')
tcp_download_socket.send(file_name.encode('utf-8')) #发送filename
recv_data=tcp_download_socket.recv(1024*1024) #最大限制1MB
print('开始写入文件---')
if recv_data:
with open('new_'+ file_name,'bw') as doenload_file:
doenload_file.write(recv_data)
doenload_file.close()
if os.path.isfile('new_'+ file_name):
print('下载文件成功')
answer = input("是否下载其他文件")
if answer == 'y' or answer == 'Y':
continue
else:
break
else:
print("服务端不存在该文件,请检查后重试")
continue
if __name__ == '__main__':
main()
二、服务端
客户端没问题了,我们实现一下服务端
服务端实现原理:
- 创建服务端socket,并绑定7788端口
- 开始持续监听7788端口,此时阻塞
- 客户端发送连接请求,连接客户端成功,服务端解阻塞
- 接收到客户端发送的filename,调用os模块判断文件是否存在
- 若存在,使用with以rb方式查看文件,并赋值data=file.read(),向客户端发送data
- 若不存在文件,不返回任何内容,此时服务端也会做出相应动作
服务端代码如下:
import socket
import os
def main():
server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(('',7788))
server_socket.listen(128) #最多允许128个客户端同时连接
while True:
print("===开始接收请求===")
new_server_socket,client_addr=server_socket.accept()
print(f'客户端已建立连接,客户端地址是{client_addr}')
while True:
if new_server_socket:
file_name=new_server_socket.recv(1024) #接收filename
if os.path.isfile(file_name):
with open(file_name,'rb') as file:
data=file.read()
new_server_socket.send(data)
else:
print("文件不存在")
new_server_socket.close()
break
else:
print("客户端关闭连接")
break
if __name__ == "__main__" :
main()
三、成果展示
为了方便,我们用pycharm来演示
3.1 创建测试文件
aaa.txt
3.2 下载测试文件
客户端操作如下:
服务端响应如下:
3.3 查看下载文件
四、文件下载器升级版
之前的文件下载虽然实现了下载的功能,但是如果是陌生的服务器,我们并不知道对方服务器的文件到底有哪些,于是我就升级了一下程序
4.1、更新公告
- 服务端会首先向客户端发送程序所在目录的文件列表,以供客户端下载
- 使用logging模块,打印标准日志,更易观察
- 修复已知BUG
代码如下:
4.1.1 升级版客户端代码
import ast
import logging
import os
import socket
from prettytable import PrettyTable
u = PrettyTable(['文件名', '文件大小'])
Format = logging.Formatter('%(levelname)s %(asctime)s %(filename)s %(funcName)s %(message)s ')
logger = logging.getLogger()
logger.setLevel('DEBUG')
console_handle = logging.StreamHandler()
console_handle.setLevel(level='INFO')
console_handle.setFormatter(Format)
logger.addHandler(console_handle)
def get_file_info():
tcp_download_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_download_socket.connect((b'127.0.0.1', 7789))
tcp_download_socket.send('getfile'.encode('utf-8'))
filelist = tcp_download_socket.recv(1024)
# 使用ast模块的literal_eval,将str转换为dict
file_info = ast.literal_eval(filelist.decode('utf-8'))
# 通过for循环与preaytable模块,创建表格
for key, value in file_info.items():
u.add_row([key, str(value)])
print(u)
tcp_download_socket.close()
def main():
print('目标服务器文件列表如下')
get_file_info()
while True:
tcp_download_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_download_socket.connect((b'127.0.0.1', 7788))
print('\n')
file_name = input('请输入要下载的文件内容:')
tcp_download_socket.send(file_name.encode('utf-8')) # 发送filename
recv_data = tcp_download_socket.recv(1024 * 1024) # 最大限制1MB
logger.info('开始写入文件---')
if recv_data:
if recv_data.decode('utf-8') == 'filenotexists':
logger.error('file not exists')
tcp_download_socket.close()
continue
else:
with open('new_' + file_name, 'bw') as doenload_file:
doenload_file.write(recv_data)
doenload_file.close()
if os.path.isfile('new_' + file_name):
logger.info('下载文件成功')
tcp_download_socket.close()
answer = input("是否下载其他文件")
if answer == 'y' or answer == 'Y':
continue
else:
break
if __name__ == '__main__':
main()
4.1.2 升级版服务端代码
import socket
import os
import logging
import threading
Format = logging.Formatter('%(levelname)s %(asctime)s %(filename)s %(funcName)s %(message)s ')
logger = logging.getLogger()
logger.setLevel('DEBUG')
console_handle = logging.StreamHandler()
console_handle.setLevel(level='INFO')
console_handle.setFormatter(Format)
logger.addHandler(console_handle)
def send_file_info():
while True:
logger.info("===开始接收发送信息请求===")
file_info = {}
files = os.listdir('.')
for file in files:
# 获取文件大小,因为文件太小多以取两位小数
file_size = round(os.path.getsize(file) / 1024, 3)
file_info[file] = f'{file_size}kb'
tcp_getlist_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 为避免端口冲突,使用其他端口
tcp_getlist_socket.bind(('', 7789))
tcp_getlist_socket.listen(128)
logger.info('创建发送文件信息套接字')
new_server_socket, client_addr = tcp_getlist_socket.accept()
logger.info(f'接收信息客户端已建立连接,客户端地址是{client_addr}')
new_server_socket.send(str(file_info).encode('utf-8'))
logger.info('成功发送客户端信息')
# new_server_socket.close()
# tcp_getlist_socket.close()
logger.info('关闭发送文件信息套接字')
def main():
# 创建send_file线程
t1 = threading.Thread(target=send_file_info)
t1.start()
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('', 7788))
server_socket.listen(128) # 最多允许128个客户端同时连接
while True:
logger.info("===开始接收下载请求===")
new_server_socket, client_addr = server_socket.accept()
logger.info(f'下载客户端已建立连接,客户端地址是{client_addr}')
while True:
if new_server_socket:
file_name = new_server_socket.recv(1024).decode('utf-8') # 接收filename
if os.path.isfile(file_name):
with open(file_name, 'rb') as file:
data = file.read()
logger.info('发送文件{0}内容'.format(file_name))
new_server_socket.send(data)
new_server_socket.close()
break
else:
logger.error("文件不存在")
new_server_socket.send('filenotexists'.encode('utf8'))
new_server_socket.close()
break
else:
print("客户端关闭连接")
break
if __name__ == "__main__":
main()
4.2 升级版成果展示
服务端启动服务开始监听
客户端启动:
首先会打印服务端文件内容,以列表形式展示
输入下载文件名 aaa.txt
成功下载
服务端日志如下:
很明显看到我是建立了两次链接的,前一次是为了接受文件列表信息
当文件不存在时,服务端会发送 file not exists 消息,客户端收到消息后,做出相应动作
重新填写正确文件,下载成功
问题处理
1、发送文件函数只发送一次,当客户端第二次启动时,会报连接失败
原因:发送文件函数只调用一次,第二次客户端连接时,函数socket实际上已关闭
解决方法:
给函数加上while True,去掉close方法,创建多线程
# 创建send_file线程
t1 = threading.Thread(target=send_file_info)
t1.start()