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

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

简介

简单版本

函数版本

面向对象版本

简介

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

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

简单版本

目录结构:

./简单版本/

| -- client/

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

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

| | -- 客户端.py

|

| -- server/

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

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

| | -- 服务器端.py

服务器端:

python 复制代码
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()

客户端:

python 复制代码
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

服务器端:

python 复制代码
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()

客户端:

python 复制代码
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

服务器端:

python 复制代码
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()

客户端:

python 复制代码
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()

代码效果如下:

下载:

上传:

相关推荐
纠结哥_Shrek4 分钟前
PyTorch 与 Python 版本对应关系
人工智能·pytorch·python
Lostgreen30 分钟前
线性回归简介:从理论到应用
python·机器学习·回归·线性回归
游王子1 小时前
Python NumPy(7):连接数组、分割数组、数组元素的添加与删除
开发语言·python·numpy
MarisolHu1 小时前
vue2项目(一)
开发语言·前端·javascript·vue.js
躲在没风的地方2 小时前
vue相关的页面和js编写
开发语言·javascript·vue.js
讓丄帝愛伱2 小时前
jvisualvm工具使用
开发语言·jvm
SteveKenny3 小时前
Python 梯度下降法(四):Adadelta Optimize
开发语言·python·深度学习·机器学习·numpy·matplotlib
Jam-Young5 小时前
使用scikit-learn中的KNN包实现对鸢尾花数据集或者自定义数据集的的预测。
python·信息可视化·scikit-learn
Luzem03195 小时前
使用scikit-learn中的线性回归包对自定义数据集进行拟合
python·线性回归·scikit-learn