python网络编程(四)用面向对象方式实现文件上传下载

一:背景

在之前已经实现了文件的下载,现在再来完善上传功能,并且使用面向对象来封装,让代码看起来更加清楚明了。

二: 使用规则和运行结果

  • 下载文件,下载格式 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()

 

2024-01-31 15:52:01【出处】:https://blog.csdn.net/javascript_good/article/details/131461941

=======================================================================================

posted on 2024-01-31 15:54  jack_Meng  阅读(18)  评论(0编辑  收藏  举报

导航