当前位置: 首页 > article >正文

[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)

[Python学习日记-80] 用 socket 实现文件传输功能(上传下载)

简介

简单版本

函数版本

面向对象版本

简介

        到此为止网络编程基础的介绍已经接近尾声了,而在本篇当中我们会基于上一篇博客代码的基础上来实现文件传输功能。文件传输其实与远程执行命令的程序原理是一摸一样的,比如下载文件的过程:

  1. 客户端提交命令
  2. 服务器端接收命令,解析和执行下载文件的方法,即以读的方式打开文件,for 循环读出文件的一行行内容,然后用 send() 发送给客户端
  3. 客户端以写的方式打开文件,将接收的内容写入文件中

简单版本

目录结构:

./简单版本/

| --  client/

|      | --  download/         # 用于存放客户端从服务器端下载的文件

|      | --  share/               # 用于存放客户端需要上传到服务器端的文件 

|      | --  客户端.py

|

| --  server/

|      | --  put/                   # 用于存放客户端上传上来的文件

|      | --  share/               # 用于存放服务器端提供下载的文件

|      | --  服务器端.py

服务器端:

import socket
import struct
import json
import os

ip_port = ('127.0.0.1',8080)
res_size = 8096

share_dir = r'G:\joveProject\socket\文件传输\简单版本\server\share'
put_dir = r'G:\joveProject\socket\文件传输\简单版本\server\put'
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(ip_port)
server.listen(5)

print('starting...')
while True:  # 链接循环
    conn, client_addr = server.accept()
    print(client_addr)

    while True:  # 通讯循环
        try:
            # 1、收命令
            res = conn.recv(res_size)   # 这里的命令格式是由你,程序开发者定义,是需要用户按规定使用的
            if not res: break

            # 2、解析命令,提取相应命令参数
            cmds = res.decode('utf-8').split()  # ['get', 'a.txt']
            filename = cmds[1]

            if cmds[0] == 'get':
                # 3、以读的方式打开文件,读取文件内容发送给客户端
                # 第一步: 制作报头
                header_dic = {  # 使用字典,解决了报头信息少的问题
                    'filename': filename,
                    'md5': 'xxxxdxxx',
                    'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
                }
                header_json = json.dumps(header_dic)
                header_bytes = header_json.encode('utf-8')

                # 第二步: 先发送报头长度
                conn.send(struct.pack('i',len(header_bytes)))  # 字典的bytes的长度很小,'i'已经足够使用了

                # 第三步: 再发报头
                conn.send(header_bytes)

                # 第四步: 再发送真实的数据
                with open('%s/%s' % (share_dir, filename), 'rb') as f:
                    for line in f:  # 每读出文件当中一行数据就发一行,每次内存当中就只有一行
                        conn.send(line)
            elif cmds[0] == 'put':
                # 3、以写的方式打开一个新文件,接收客户端上传的文件内容
                # 第一步: 先收报头长度
                obj = conn.recv(4)
                header_size = struct.unpack('i', obj)[0]

                # 第二步: 再接收报头
                header_bytes = conn.recv(header_size)

                # 第三步: 从报头中解析出真实数据的描述信息
                header_json = header_bytes.decode('utf-8')
                header_dic = json.loads(header_json)
                total_size = header_dic['file_size']

                # 第四步: 接收真实数据
                with open('%s/%s' % (put_dir, filename), 'wb') as f:
                    recv_size = 0
                    while recv_size < total_size:
                        line = conn.recv(1024)
                        f.write(line)
                        recv_size += len(line)

        except ConnectionResetError:
            break
    conn.close()
server.close()

客户端:

import socket
import struct
import json
import os

ip_port = ('127.0.0.1',8080)
info_size = 1024

download_dir = r'G:\joveProject\socket\文件传输\简单版本\client\download'
share_dir = r'G:\joveProject\socket\文件传输\简单版本\client\share'
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
client.connect(ip_port)

while True:
    # 1、发命令
    cmd = input('>>: ').strip()
    if not cmd:continue
    client.send(cmd.encode('utf-8'))

    if cmd.split()[0] == 'get':
        # 2、以写的方式打开一个新文件,接收服务端发来的文件内容,写入客户的新文件中
        # 第一步: 先收报头的长度
        obj = client.recv(4)
        header_size = struct.unpack('i',obj)[0]

        # 第二步: 再收报头
        header_bytes = client.recv(header_size)

        # 第三步: 从报头中解析出对真实数据的描述信息
        header_json = header_bytes.decode('utf-8')
        header_dic = json.loads(header_json)
        filename = header_dic['filename']
        total_size = header_dic['file_size']

        # 第四步: 接收真实的数据
        with open('%s/%s' % (download_dir, filename), 'wb') as f:
            recv_size = 0
            while recv_size < total_size:
                line = client.recv(info_size)
                f.write(line)
                recv_size += len(line)  # 计算真实的接收长度,如果以后增加打印进度条的时候就可以精确无误的表示
                print(int((recv_size / total_size) * 100), '%')
    elif cmd.split()[0] == 'put':
        # 2、解析命令,提取参数
        cmds = cmd.split()
        filename = cmds[1]

        # 3、以读的方式打开文件,读取文件内容上传给服务器
        # 第一步: 制作报头
        header_dic = {
            'filename': filename,
            'md5': 'xxxxxxx',
            'file_size': os.path.getsize('%s/%s' % (share_dir, filename))
        }
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode('utf-8')

        # 第二步: 先发送报头长度
        client.send(struct.pack('i', len(header_bytes)))

        # 第三步: 再发报头
        client.send(header_bytes)

        # 第四步: 再发送真实数据
        with open('%s/%s' % (share_dir, filename), 'rb') as f:
            send_size = 0
            for line in f:
                client.send(line)
                send_size += len(line)
                print(((send_size / header_dic['file_size']) * 100), '%')

client.close()

代码效果如下:

下载:

上传:

函数版本

        函数版本主要的变化是函数把代码封装成了一个函数,可以更加高效的复用,组织结构相对于简单版本更加好了,可扩展性也更加强了。

目录结构:

./函数版本/

| --  client/

|      | --  download/         # 用于存放客户端从服务器端下载的文件

|      | --  share/               # 用于存放客户端需要上传到服务器端的文件 

|      | --  客户端.py

|

| --  server/

|      | --  put/                   # 用于存放客户端上传上来的文件

|      | --  share/               # 用于存放服务器端提供下载的文件

|      | --  服务器端.py

服务器端:

import socket
import struct
import json
import os

share_dir = r'G:\joveProject\socket\文件传输\函数版本\server\share'
put_dir = r'G:\joveProject\socket\文件传输\函数版本\server\put'

def get(conn, cmds):
    filename = cmds[1]

    # 3、以读的方式打开文件,读取文件内容发送给客户端
    # 第一步: 制作报头
    header_dic = {  # 使用字典,解决了报头信息少的问题
        'filename': filename,
        'md5': 'xxxxdxxx',
        'file_size': os.path.getsize(r'%s/%s' % (share_dir, filename))
    }
    header_json = json.dumps(header_dic)
    header_bytes = header_json.encode('utf-8')

    # 第二步: 先发送报头长度
    conn.send(struct.pack('i', len(header_bytes)))  # 字典的bytes的长度很小,'i'已经足够使用了

    # 第三步: 再发报头
    conn.send(header_bytes)

    # 第四步: 再发送真实的数据
    with open('%s/%s' % (share_dir, filename), 'rb') as f:
        for line in f:  # 每读出文件当中一行数据就发一行,每次内存当中就只有一行
            conn.send(line)

def put(conn, cmds):
    filename = cmds[1]

    # 3、以写的方式打开一个新文件,接收客户端上传的文件内容
    # 第一步: 先收报头长度
    obj = conn.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步: 再接收报头
    header_bytes = conn.recv(header_size)

    # 第三步: 从报头中解析出真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    total_size = header_dic['file_size']

    # 第四步: 接收真实数据
    with open('%s/%s' % (put_dir, filename), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = conn.recv(1024)
            f.write(line)
            recv_size += len(line)

def run():
    ip_port = ('127.0.0.1',8080)
    res_size = 8096
    server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    server.bind(ip_port)
    server.listen(5)

    print('starting...')
    while True:  # 链接循环
        conn, client_addr = server.accept()
        print(client_addr)

        while True:  # 通讯循环
            try:
                # 1、收命令
                res = conn.recv(res_size)   # 这里的命令格式是由你,程序开发者定义,是需要用户按规定使用的
                if not res: break

                # 2、解析命令,提取相应命令参数
                cmds = res.decode('utf-8').split()  # ['get', 'a.txt']


                if cmds[0] == 'get':
                    get(conn, cmds)
                elif cmds[0] == 'put':
                    put(conn, cmds)

            except ConnectionResetError:
                break
        conn.close()
    server.close()

if __name__ == '__main__':
    run()

客户端:

import socket
import struct
import json
import os

download_dir = r'G:\joveProject\socket\文件传输\函数版本\client\download'
share_dir = r'G:\joveProject\socket\文件传输\函数版本\client\share'

def get(client, info_size):
    # 2、以写的方式打开一个新文件,接收服务端发来的文件内容,写入客户的新文件中
    # 第一步: 先收报头的长度
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步: 再收报头
    header_bytes = client.recv(header_size)

    # 第三步: 从报头中解析出对真实数据的描述信息
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(header_json)
    filename = header_dic['filename']
    total_size = header_dic['file_size']

    # 第四步: 接收真实的数据
    with open('%s/%s' % (download_dir, filename), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = client.recv(info_size)
            f.write(line)
            recv_size += len(line)  # 计算真实的接收长度,如果以后增加打印进度条的时候就可以精确无误的表示
            print(int((recv_size / total_size) * 100), '%')

def put(client, cmds):
    filename = cmds[1]

    # 3、以读的方式打开文件,读取文件内容上传给服务器
    # 第一步: 制作报头
    header_dic = {
        'filename': filename,
        'md5': 'xxxxxxx',
        'file_size': os.path.getsize('%s/%s' % (share_dir, filename))
    }
    header_json = json.dumps(header_dic)
    header_bytes = header_json.encode('utf-8')

    # 第二步: 先发送报头长度
    client.send(struct.pack('i', len(header_bytes)))

    # 第三步: 再发报头
    client.send(header_bytes)

    # 第四步: 再发送真实数据
    with open('%s/%s' % (share_dir, filename), 'rb') as f:
        send_size = 0
        for line in f:
            client.send(line)
            send_size += len(line)
            print(((send_size / header_dic['file_size']) * 100), '%')

def run():
    ip_port = ('127.0.0.1',8080)
    info_size = 1024
    client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    client.connect(ip_port)

    while True:
        # 1、发命令
        cmd = input('>>: ').strip()
        if not cmd:continue
        client.send(cmd.encode('utf-8'))
        # 2、解析命令,提取参数
        cmds = cmd.split()

        if cmd.split()[0] == 'get':
            get(client, info_size)
        elif cmd.split()[0] == 'put':
            put(client, cmds)

    client.close()

if __name__ == '__main__':
    run()

代码效果如下:

下载:

上传:

面向对象版本

        面向对象版本在函数版本的基础上进一步进行了封装,使代码模块化,可以把我们写的文件传输功能直接复制到其他项目当中使用,并且加入了面向对象之后我们所使用的数据和方法整合到了一起了,与函数版本相比程序的组织结构提高得更好,可扩展性更强。

目录结构:

./面向对象版本/

| --  client/

|      | --  download/         # 用于存放客户端从服务器端下载的文件

|      | --  share/               # 用于存放客户端需要上传到服务器端的文件 

|      | --  客户端.py

|

| --  server/

|      | --  put/                   # 用于存放客户端上传上来的文件

|      | --  share/               # 用于存放服务器端提供下载的文件

|      | --  服务器端.py

服务器端:

import socket
import struct
import json
import os


class Server:
    """ 服务器 """
    address_family = socket.AF_INET
    address_reuse = True
    socket_type = socket.SOCK_STREAM
    share_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\share'
    put_dir = r'G:\joveProject\socket\文件传输\面向对象版本\server\put'
    max_listen = 5
    recv_max_size = 8096

    def __init__(self, server_address, bind_and_active = True):
        self.server_address = server_address
        self.socket = socket.socket(self.address_family, self.socket_type)
        if bind_and_active:
            try:
                self.bind()
                self.listen()
            except:
                self.socket.close()
                raise

    def bind(self):
        """ socket绑定地址 """
        if self.address_reuse:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)

    def listen(self):
        """ socket监听 """
        self.socket.listen(self.max_listen)

    def accpet(self):
        """ socket等待接收 """
        return self.socket.accept()

    def recv(self, conn):
        """ 接收函数 """
        return conn.recv(self.recv_max_size)

    def connect_close(self):
        """ 断开连接 """
        self.conn.close()

    def get(self, cmds):
        """
        下载
        :param cmds:
        :return:
        """
        filename = cmds[1]
        header_dic = {
            'filename': filename,
            'md5': 'xxxxxx',
            'file_size': os.path.getsize('%s/%s' % (self.share_dir, filename))
        }
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode('utf-8')

        self.conn.send(struct.pack('i', len(header_bytes)))
        self.conn.send(header_bytes)
        with open('%s/%s' % (self.share_dir, filename), 'rb') as f:
            for line in f:
                self.conn.send(line)

    def put(self, cmds):
        """
        上传
        :param cmds:
        :return:
        """
        obj = self.conn.recv(4)
        header_size = struct.unpack('i', obj)
        header_bytes = self.conn.recv(header_size[0])
        header_json = header_bytes.decode('utf-8')
        header_dic = json.loads(header_json)
        total_size = header_dic['file_size']

        with open('%s/%s' % (self.put_dir, header_dic['filename']), 'wb') as f:
            rec_size = 0
            while rec_size < total_size:
                line = self.conn.recv(self.recv_max_size)
                f.write(line)
                rec_size += len(line)

    def run(self):
        """ run运行程序 """
        while True:  # 链接循环
            self.conn, self.client_addr = self.accpet()
            print(self.client_addr)
            while True:  # 通讯循环
                try:
                    res = self.recv(self.conn)
                    if not res: break
                    cmds = res.decode('utf-8').split()

                    if hasattr(self, cmds[0]):  # 通过反射找到对应的方法
                        getattr(self, cmds[0])(cmds)
                except ConnectionResetError:
                    break
        self.connect_close()


if __name__ == '__main__':
    print('starting...')
    ip_port = ('127.0.0.1', 8080)
    server = Server(ip_port)
    server.run()
    server.socket.close()

客户端:

import socket
import struct
import json
import os


class Client:
    """ 客户端 """
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    recv_max_size = 1024
    download_dir = r'G:\joveProject\socket\文件传输\面向对象版本\client\download'
    share_dir = r'G:\joveProject\socket\文件传输\面向对象版本\client\share'

    def __init__(self,server_address,connect_and_active = True):
        self.server_address = server_address
        self.socket = socket.socket(self.address_family,self.socket_type)
        if connect_and_active:
            try:
                self.connect()
            except:
                self.socket.close()
                raise

    def connect(self):
        """ 与服务器创建链接 """
        self.socket.connect(self.server_address)

    def connect_close(self):
        """ 断开连接 """
        self.socket.close()

    def get(self, cmds):
        """
        接收下载数据
        :return:
        """
        obj = self.socket.recv(4)
        header_size = struct.unpack('i', obj)
        header_bytes = self.socket.recv(header_size[0])
        header_json = header_bytes.decode('utf-8')
        header_dic = json.loads(header_json)
        total_size = header_dic['file_size']
        filename = header_dic['filename']

        with open('%s/%s' % (self.download_dir, filename), 'wb') as f:
            recv_size = 0
            while recv_size < total_size:
                line = self.socket.recv(self.recv_max_size)
                f.write(line)
                recv_size += len(line)
                print(((recv_size / total_size) * 100), '%')

    def put(self, cmds):
        """
        上传数据到服务端
        :type cmds:
        :return:
        """
        header_dic = {
            'filename': cmds[1],
            'md5': 'xxxxxx',
            'file_size': os.path.getsize('%s/%s' % (self.share_dir, cmds[1]))
        }
        header_json = json.dumps(header_dic)
        header_bytes = header_json.encode('utf-8')
        self.socket.send(struct.pack('i', len(header_bytes)))
        self.socket.send(header_bytes)
        with open('%s/%s' % (self.share_dir, cmds[1]), 'rb') as f:
            send_size = 0
            for line in f:
                self.socket.send(line)
                send_size += len(line)
                print(((send_size / header_dic['file_size']) * 100), '%')

    def run(self):
        """ run运行程序 """
        while True:
            cmd = input('>>: ').strip()
            if not cmd: continue
            self.socket.send(cmd.encode('utf-8'))
            cmds = cmd.split()
            if hasattr(self, cmds[0]):  # 通过反射找到对应的方法
                getattr(self, cmds[0])(cmds)


if __name__ == '__main__':
    ip_port = ('127.0.0.1', 8080)
    client = Client(ip_port)
    client.run()
    client.socket.close()

代码效果如下:

下载:

上传:


http://www.kler.cn/a/525501.html

相关文章:

  • Linux pkill 命令使用详解
  • 17.Word:李楠-学术期刊❗【29】
  • HTML 标题
  • [C语言日寄] <stdio.h> 头文件功能介绍
  • 【llm对话系统】大模型 RAG 之回答生成:融合检索信息,生成精准答案
  • 【云安全】云原生-K8S-搭建/安装/部署
  • 设计模式 - 行为模式_Template Method Pattern模板方法模式在数据处理中的应用
  • C#方法作用
  • Java基础知识总结(二十八)--可变参数(...)、静态导入、枚举
  • JMeter插件 Arrivals Thread Group 源码解析:实现原理与性能测试中的应用
  • C24.【C++ Cont】结构体
  • springboot 简化 spring开发
  • 智能家居能源管理系统:Python与AI的完美结合
  • QT设置应用程序图标
  • LeetCode:56.合并区间
  • 工业相机常用词语解释
  • Vue.js 使用 Vuex 管理组件间的共享状态
  • 【GStreamer】GstBuffer的简单分析
  • 10.7 获得程序版本信息
  • 【DeepSeek】LLM强化学习GRPO Trainer详解
  • Baklib在知识管理效率提升中的独特价值与其他产品的比较探析
  • RocketMQ 中如何实现消息的可靠传递?
  • C++,STL 简介:历史、组成、优势
  • 9.1 LangChain深度解析:大模型应用开发的“万能胶水”与核心架构设计
  • 数论问题77一一3x+1问题
  • 【deepseek实战】绿色好用,不断网