程序结构说明
# 文件上传-断点续传
文件上传时可能会异常终止,因此只下载了一部分,所以我们可以重新连接之后接着下载.
# 目录结构
|- client.py
|- server.py
|- conf
|- settings.py
|- lib
|- common.py
|- log
|- log.log
|- db
|- server_db
|- client_db
|- README.md
|- requirement.txt
# client.py
发送指令给服务端,下载文件.
# server.py
实现并发的服务端,提供文件.
## 文件的多种状态
1. 全新的文件,未下载的文件
2. 下载一部分的文件
3. 下载完成的文件
# conf/settings.py
IP = ''
PORT = ''
BASE_PATH = ''
DB_PATH = ''
CLIENT_DB_PATH = ''
SERVER_DB_PATH = ''
LOG_PATH = ''
日志的模板
# lib/common.py
通用模板,如:日志.
# log/log.log
记录日志,**按天新增文件**
# db
存储客户端/服务端数据
1. ls
conf 配置文件目录
####################
# log_settings.py #
####################
## 日志模块配置
import os
# 定义三种日志输出格式 开始
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]' # 其中name为getLogger()指定的名字;lineno为调用日志输出函数的语句所在的代码行
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'
# 定义日志输出格式 结束
logfile_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # log文件的目录,需要自定义文件路径 # atm
logfile_dir = os.path.join(logfile_dir, 'log') # C:UsersoldboyDesktopatmlog
logfile_name = '{name}.log' # log文件名,需要自定义路径名
# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir): # C:UsersoldboyDesktopatmlog
os.mkdir(logfile_dir)
# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name) # C:UsersoldboyDesktopatmloglog.log
# 定义日志路径 结束
# log配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {}, # filter可以不定义
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'default': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': logfile_path, # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M (*****)
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置。如果''设置为固定值logger1,则下次导入必须设置成logging.getLogger('logger1')
'': {
# 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': False, # 向上(更高level的logger)传递
},
},
}
################
# setttings.py #
################
import os
IP = '192.168.11.210'
PORT = 8000
BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DB_PATH = os.path.join(BASE_PATH, 'db')
CLIENT_DB_PATH = os.path.join(DB_PATH, 'client')
SERVER_DB_PATH = os.path.join(DB_PATH, 'server')
LOG_PATH = os.path.join(BASE_PATH, 'log')
db 数据库文件夹
client
server
lib 文件夹
##############
# common.py #
##############
import os
import logging
import logging.config
from conf import log_settings,settings
def load_my_logging_cfg(name):
logfile_name = f'{name}.log'
logfile_path = os.path.join(settings.LOG_PATH, logfile_name)
log_settings.LOGGING_DIC['handlers']['default']['filename'] = logfile_path
logging.config.dictConfig(log_settings.LOGGING_DIC) # 导入上面定义的logging配置
logger = logging.getLogger(name) # 生成一个log实例
logger.info(f'{name} works!') # 记录该文件的运行状态
return logger
if __name__ == '__main__':
load_my_logging_cfg('aaa')
log 文件夹
作用:记录日志信息
clent 客户端主程序
#############
# client.py #
#############
import os
import struct
from socket import *
from conf import settings
from lib import common
logger = common.load_my_logging_cfg('client')
client = socket()
client.connect(('127.0.0.1', 8000))
def save_file_content(filename, file_content):
"""保存文件内容"""
file_path = os.path.join(settings.CLIENT_DB_PATH, filename)
with open(file_path, 'ab') as fw:
fw.write(file_content)
while True:
# 接收文件列表
client.send('ls'.encode('utf8'))
file_list = client.recv(1024)
file_list = eval(file_list.decode('utf8'))
for ind, val in enumerate(file_list):
print(ind, val)
# 选择文件
file_choice = input('请选择你需要下载的文件:') # 0
filename = file_list[int(file_choice)] # type:str
# 发送文件和文件大小
file_path = os.path.join(settings.CLIENT_DB_PATH, filename)
if os.path.exists(file_path):
file_size = os.path.getsize(file_path)
client.send(str((filename, file_size)).encode('utf8'))
else:
client.send(str((filename, 0)).encode('utf8'))
# 接收文件并保存
# 接收文件头
file_head = client.recv(4)
file_head = struct.unpack('i', file_head)[0] # file_size
print(file_head)
# 接收文件内容
# print(file_head)
recv_size = 0
if file_head!=0:
while recv_size <= file_head:
file_content = client.recv(10240)
recv_size += 10240
print('recv_size:', recv_size)
save_file_content(filename, file_content)
else:
print('啥玩意')
else:
print('文本已经存在')
logger.info(f'{filename}下载成功')
server 服务端主程序
##############
# server.py #
##############
import os
import struct
from socket import *
from lib import common
from conf import settings
logger = common.load_my_logging_cfg('server')
server = socket()
server.bind((settings.IP, settings.PORT))
server.listen(5)
def get_filename_list():
"""获取db/server下的所有文件名"""
filename_list = os.listdir(settings.SERVER_DB_PATH)
return filename_list
def get_file_content(filename, file_size):
"""通过文件名获取文件路径"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
with open(filename_path, 'rb') as fr:
fr.seek(file_size, 0)
file_content = fr.read(1024)
return file_content
def get_file_content_iter(filename, file_size):
"""通过文件名获取文件路径"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
with open(filename_path, 'rb') as fr:
fr.seek(file_size, 0)
while True:
file_content = fr.read(10240)
print(file_content)
if file_content:
yield file_content
else:
break
def set_file_head(filename, file_size):
"""设计一个文件头"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
file_head = os.path.getsize(filename_path) - file_size
file_head = struct.pack('i', file_head)
return file_head
print('start...')
while True:
conn, client_addr = server.accept()
print(client_addr)
while True:
try:
# 发送文件列表
data = conn.recv(1024)
if data.decode('utf8') == 'ls':
filename_list = get_filename_list()
conn.send(str(filename_list).encode('utf8'))
logger.info(f'{client_addr}查看文件列表')
# 收到文件名
filename, file_size = eval(conn.recv(1024).decode('utf8'))
logger.info(f'{client_addr}下载文件{filename}')
file_content = get_file_content(filename, file_size)
# 发送文件
file_head = set_file_head(filename, file_size)
# 发送文件头
conn.send(file_head)
# 发送文件内容
for file in get_file_content_iter(filename, file_size):
if file:
conn.send(file)
else:
break
logger.info(f'成功给{client_addr}发送文件{filename}')
except ConnectionResetError:
break
conn.close()
server 服务端支持并发
##########################
# socketserver_server.py #
##########################
import socketserver
import os
import struct
from lib import common
from conf import settings
logger = common.load_my_logging_cfg('server')
class MyHandler(socketserver.BaseRequestHandler):
@staticmethod
def get_filename_list():
"""获取db/server下的所有文件名"""
filename_list = os.listdir(settings.SERVER_DB_PATH)
return filename_list
@staticmethod
def get_file_content(filename, file_size):
"""通过文件名获取文件路径"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
with open(filename_path, 'rb') as fr:
fr.seek(file_size, 0)
file_content = fr.read(1024)
return file_content
@staticmethod
def get_file_content_iter(filename, file_size):
"""通过文件名获取文件路径"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
with open(filename_path, 'rb') as fr:
fr.seek(file_size, 0)
while True:
file_content = fr.read(10240)
yield file_content
@staticmethod
def set_file_head(filename, file_size):
"""设计一个文件头"""
filename_path = os.path.join(settings.SERVER_DB_PATH, filename)
file_head = os.path.getsize(filename_path) - file_size
file_head = struct.pack('i', file_head)
return file_head
def handle(self):
print(f'{self.client_address}成功连接')
logger.info(f'{self.client_address}成功连接')
print(self.client_address)
while True:
try:
# 发送文件列表
data = self.request.recv(1024)
if data.decode('utf8') == 'ls':
filename_list = self.get_filename_list()
self.request.send(str(filename_list).encode('utf8'))
logger.info(f'{self.client_address}查看文件列表')
# 收到文件名
filename, file_size = eval(self.request.recv(1024).decode('utf8'))
logger.info(f'{self.client_address}下载文件{filename}')
file_content = self.get_file_content(filename, file_size)
# 发送文件
file_head = self.set_file_head(filename, file_size)
# 发送文件头
self.request.send(file_head)
# 发送文件内容
for file in self.get_file_content_iter(filename, file_size):
self.request.send(file)
logger.info(f'成功给{self.client_address}发送文件{filename}')
except ConnectionResetError:
break
self.request.close()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('192.168.11.210', 8000), MyHandler, bind_and_activate=True)
print('start...')
server.serve_forever()