项目

程序结构说明

# 文件上传-断点续传

文件上传时可能会异常终止,因此只下载了一部分,所以我们可以重新连接之后接着下载.

# 目录结构

|- 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()

原文地址:https://www.cnblogs.com/bladecheng/p/11102975.html