网络编程之文件传输实例

一、文件传输包括两部分,服务端收发文件和客户端(即用户)收发文件。

  收发文件与远程执行命令的程序原理是一样的,比如客户端下载文件的过程:

    1、客户端提交下载命令;

    2、服务端接收命令,解析,执行下载文件的方法,即以读的方式打开文件,利用for循环读出一行行内容,

    然后发送(send)给客户端。

    3、客户端以写的方式打开文件,将接受的文件内容接收写入文件中。

 

二、文件传输的几种模式

  1、基础版

    服务端文件夹应包括共享文件夹share和server.py

    server.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json
import os
share_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输基础版服务端share'
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',3361))
server.listen(5)

while True:
    conn,client_addres = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            if not cmd:break # 适用于linux
            # 执行客户端的命令,并返回执行后的信息
            # 1、解析客户端的指令,提取文件名
            data = cmd.decode('gbk').split()
            file_name = data[1]
            # 1、制作报头
            header_dic = {
                'file_name':file_name, # 客户端需要下载的文件名
                'md5':'xxxxxxxx',
                'total_size':os.path.getsize('%s\%s'%(share_dir,file_name)) # 服务端对应文件的大小
            }
            # 2、编辑并发送报头长度
            header_json = json.dumps(header_dic) # 报头字典转字符串
            header_bytes = header_json.encode('gbk') # 字符串转bytes
            conn.send(struct.pack('i',len(header_bytes)))# 发送报头长度
            # 3、发送报头信息
            conn.send(header_bytes)
            # 4、发送真实数据
            with open('%s\%s'%(share_dir,file_name),'rb') as f:
                for line in f:
                    conn.send(line)
        except ConnectionResetError:
            break
    conn.close()
server.close()
View Code

    客户端文件夹应包括下载或上传文件夹down和client.py

    client.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json
downland_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输基础版客户端dowland'
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

client.connect(('127.0.0.1',3361))

while True:
    cmd = input('输入下载的文件及后缀名>>:').strip()
    if not cmd:continue
    client.send(cmd.encode('gbk'))
    # 等待接收
    # 1、获取报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i',obj)[0]
    # 2、获取报头内容
    header_bytes = client.recv(header_size)
    # 3、解析报头,获取真实数据长度
    header_data = header_bytes.decode('gbk') # bytes解码成字符串
    header_dic = json.loads(header_data)  # 字符串反序列化为字典(报头)
    total_size = header_dic['total_size'] # 得到真实数据长度
    file_name = header_dic['file_name']
    # 4、接收真实数据
    with open('%s\%s'%(downland_dir,file_name),'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = client.recv(1024)
            f.write(line)
            recv_size += len(line)
            print('文件总大小:%s	已下载大小:%s'%(total_size,recv_size))

client.close()
View Code

  

  2、函数版

    服务端文件夹包括recieve、share文件夹和server.py

    server.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json
import os
share_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_函数版服务端share'
recieve_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_函数版服务端
ecieve'

def get(cmd,conn):
    # 执行客户端的命令,并返回执行后的信息
    # 1、解析客户端的指令,提取文件名
    data = cmd.decode('gbk').split()
    file_name = data[1]
    # 1、制作报头
    header_dic = {
        'file_name': file_name,  # 客户端需要下载的文件名
        'md5': 'xxxxxxxx',
        'total_size': os.path.getsize('%s\%s' % (share_dir, file_name))  # 服务端对应文件的大小
    }
    # 2、编辑并发送报头长度
    header_json = json.dumps(header_dic)  # 报头字典转字符串
    header_bytes = header_json.encode('gbk')  # 字符串转bytes
    conn.send(struct.pack('i', len(header_bytes)))  # 发送报头长度
    # 3、发送报头信息
    conn.send(header_bytes)
    # 4、发送真实数据
    with open('%s\%s' % (share_dir, file_name), 'rb') as f:
        for line in f:
            conn.send(line)
def put(cmd,conn):
    '''
    获取用户上传的内容
    :param cmd:
    :param conn:
    :return:
    '''
    # 1、接收报头的大小
    obj = conn.recv(4)
    header_size = struct.unpack('i', obj)[0]
    # 2、接收报头的信息
    head_bytes = conn.recv(header_size)
    # 3、解析报头信息并获取真实数据的大小
    head_data = head_bytes.decode('gbk') # bytes-->str
    head_dic = json.loads(head_data)  # str-->dict
    file_name = head_dic['file_name']
    total_size = head_dic['total_size']
    # 4、接收真实数据
    with open('%s\%s'%(recieve_dir,file_name),'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = conn.recv(1024)
            f.write(line)
            recv_size += len(line)
            print('文件总大小为:%s	已接收的文件大小为:%s'%(total_size,recv_size))

def run():
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server.bind(('127.0.0.1', 8889))
    server.listen(5)
    while True:
        conn,client_addres = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                data = cmd.decode('gbk').split()
                if data[0] == 'get':
                    get(cmd,conn)
                elif data[0] == 'put':
                    put(cmd,conn)

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

if __name__ == '__main__':
    run()
View Code

    客户端文件夹包括dowland、share文件夹和client.py

    client.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json
import os
downland_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_函数版客户端dowland'
share_dir = r'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_函数版客户端share'

def get(client,cmd):
    '''
    用户下载功能
    :param client:
    :param cmd:
    :return:
    '''
    # 等待接收
    # 1、获取报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i',obj)[0]
    # 2、获取报头内容
    header_bytes = client.recv(header_size)
    # 3、解析报头,获取真实数据长度
    header_data = header_bytes.decode('gbk')  # bytes解码成字符串
    header_dic = json.loads(header_data)  # 字符串反序列化为字典(报头)
    total_size = header_dic['total_size']  # 得到真实数据长度
    file_name = header_dic['file_name']
    # 4、接收真实数据
    with open('%s\%s' % (downland_dir, file_name), 'wb') as f:
        recv_size = 0
        while recv_size < total_size:
            line = client.recv(1024)
            f.write(line)
            recv_size += len(line)
            print('文件总大小:%s	已下载大小:%s' % (total_size, recv_size))
def put(client,cmd):
    '''
    用户上传功能
    :param cmd:
    :return:
    '''
    # 1、制作报头
    file_name = cmd.split()[1]
    file_dic = {
        'file_name':file_name,
        'md5':'xxxxxxxx',
        'total_size':os.path.getsize('%s\%s'%(share_dir,file_name))
    }
    # 2、编辑并发送报头长度
    header_json = json.dumps(file_dic)
    head_bytes = header_json.encode('gbk')
    client.send(struct.pack('i',len(head_bytes)))
    # 3、发送报头信息
    client.send(head_bytes)
    # 4、发送真实数据信息
    with open('%s\%s'%(share_dir,file_name),'rb') as f:
        for line in f:
            client.send(line)
            # print('文件总大小为:%s	已上传文件大小为:%s'%(file_dic['total_size'],))
def run():
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(('127.0.0.1', 8889))
    while True:
        cmd = input('输入下载(get)或上传(put)的文件及后缀名>>:').strip()
        if not cmd:continue
        client.send(cmd.encode('gbk'))
        res = cmd.split()[0]
        if res == 'get':
            print('你正在进行下载操作...')
            get(client,cmd)
        elif res == 'put':
            print('你正在进行上传操作...')
            put(client,cmd)

    client.close()
if __name__ == '__main__':
    run()
View Code

 

  3、面向对象版

    服务端包括server_db文件夹(用于存放文件)和server.py

    server.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import struct
import json
import os

class My_server:
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_addres = False # 端口重用
    max_size = 8192  # 最大单次接收字节数目
    coding = 'utf-8' # 编码,windows系统,mac或linux使用utf-8
    listen_queue = 5  # 监听数
    #server_dir = 'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_面向对象服务端server_db' # 服务器文件目录
    server_dir = 'server_db'
    def __init__(self,server_addres,bind_and_active=True):
        '''由构造函数调用来绑定套接字'''
        self.server_addres = server_addres
        self.socket = socket.socket(self.address_family,self.socket_type)
        if bind_and_active:
            try:
                self.server_bind()
                self.server_active()
            except:
                self.server_close()
                raise

    def server_bind(self):
        '''服务器socket套接字绑定方法'''
        if self.allow_reuse_addres:
            self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 设置端口重用
        self.socket.bind(self.server_addres)
        self.server_addres = self.socket.getsockname()  # 获取本地端点的地址

    def server_active(self):
        '''由构造函数调用以激活服务器 '''
        self.socket.listen(self.listen_queue)

    def get_request(self):
        '''从套接字获取请求和客户端地址'''
        return self.socket.accept()

    def close_request(self,request):
        '''调用以清理单个请求'''
        request.close()

    def server_close(self):
        '''调用以清理服务器'''
        self.socket.close()

    def run(self):
        '''入口程序'''
        while True:
            self.conn,self.client_addres = self.get_request()
            print('from client:',self.client_addres)
            while True:
                try:  # 异常处理
                    head_struct = self.conn.recv(4)#接收报头
                    #if not head_struct:break # linux适用
                    print(head_struct)
                    head_len = struct.unpack('i',head_struct)[0] # 获取报头字典长度
                    head_json = self.conn.recv(head_len).decode(self.coding) #接收字典并转成字符串
                    head_dic = json.loads(head_json) # 将字符串转为字典
                    print(head_dic)
                    cmd = head_dic['cmd']
                    if hasattr(self,cmd): # 反射,判断是否有字符串对应的方法
                        func = getattr(self,cmd)  # 获取字符串对应的方法
                        func(head_dic)  # 执行对应方法
                except Exception:
                    break

    def get(self,dic):
        '''执行用户输入的下载命令'''
        print(dic)
        cmd = dic['cmd']
        file_name = dic['filename'] # 获取下载的文件名
        file_path = os.path.normpath(os.path.join(self.server_dir,file_name)) # 获取文件位置
        if not os.path.isfile(file_path):
            print('文件%s不存在,请核对后再输入!'%file_name)
        else:
            file_size = os.path.getsize(file_path) # 获取文件大小
        # 制作报头
        head_dic = {
            'cmd':cmd,
            'filename':file_name,
            'filesize':file_size,
            'md5':'xxxxxx'
        }
        head_str = json.dumps(head_dic) # 字典转为字符串
        head_bytes= head_str.encode(self.coding) # 字符串转为bytes
        head_struct = struct.pack('i',len(head_bytes)) # 打包成固定字节长度的串
        # 发送报头长度
        self.conn.send(head_struct)
        # 发送报头
        self.conn.send(head_bytes)
        # 发送正式数据
        send_size = 0
        with open(file_path,'rb') as f:
            while send_size<file_size:
                for line in f:
                    self.conn.send(line)
                    send_size += len(line)
                    print('文件总大小:%s,已发送文件:%s'%(file_size,send_size/file_size))

    def put(self,dic):
        '''接收用户上传的文件'''
        file_path = os.path.normpath(os.path.join(self.server_dir,dic['filename'])) #设置存放文件的位置
        file_size= dic['filesize'] # 获取上传文件的大小
        recv_size = 0
        with open(file_path,'wb') as f:
            while recv_size<file_size:
                recv_data = self.conn.recv(self.max_size)
                f.write(recv_data)
                recv_size+=len(recv_data)
                print("文件总大小:%s,已接收文件大小:%s"(file_size,recv_size))

myserver = My_server(('127.0.0.1',3301))
if __name__ == '__main__':
    myserver.run()
View Code

    客户端包括client_db文件夹(存放文件)和client.py

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# write by congcong

import socket
import json
import os
import struct

class My_client:
    addres_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    allow_reuse_addres = False # 端口重用
    max_size = 8192 # 单次最大接收或发出文件数据量
    coding = 'utf-8' # 文件或命令编码格式
    listen_queue = 5
    #client_dir = 'D:pycharmTest1Thrid_module网络编程文件传输文件传输优化_面向对象客户端client_db'
    client_dir = 'client_db'
    def __init__(self,server_addres,connect=True):
      '''由构造函数调用来绑定套接字'''
      self.server_addres = server_addres
      self.socket = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
      if connect:
          try:
              self.client_connect() # 调用链接方法
          except:
              self.client_connect()
              raise
    def client_connect(self):
        '''链接方法'''
        self.socket.connect(self.server_addres)
    def client_close(self):
        '''关闭链接方法'''
        self.socket.close()
    def run(self):
        '''客户端程序入口'''
        while True:
            res = input('输入getput 文件名+后缀>>').strip()
            if not res:continue
            str =res.split()
            cmd = str[0] # 获取get或put命令
            file_name = str[1]
            if hasattr(self,cmd):
                func = getattr(self,cmd)
                func(str)
    def get(self,res):
        '''执行下载方法'''
        cmd = res[0] # 得到下载命令
        file_name = res[1] # 得到文件名
        # 制作报头
        head_dic ={
            'cmd':cmd,
            'filename':file_name,
            'md5':'xxxxx'
        }
        print(head_dic)
        head_str = json.dumps(head_dic) # 字典转字符串
        head_byte= head_str.encode(self.coding) # 字符串转byte
        head_stuct = struct.pack('i',len(head_byte)) # byte打包为指定字节长度的包
        # 发送报头长度
        self.socket.send(head_stuct)
        # 发送报头里的数据
        self.socket.send(head_byte)
        # 接收下载信息的报头
        head_recv_struct = self.socket.recv(4)
        # 解析报头,获取下载文件的大小
        head_recv_size = struct.unpack('i',head_recv_struct)[0]
        # 接收下载信息的报头数据,并解码
        head_recv_data = self.socket.recv(head_recv_size).decode(self.coding)
        # 将解码的字符串反序列化为字典
        head_recv_dic = json.loads(head_recv_data)
        print(head_recv_dic)
        # 获取待接收的文件的大小和文件名
        file_size = head_recv_dic['filesize']
        recv_file_name = head_recv_dic['filename']
        # 接收下载数据
        file_path = os.path.normpath(os.path.join(self.client_dir, file_name))  # 设定下载文件存放位置
        recv_size = 0
        with open(file_path,'wb') as f:
            while recv_size < file_size:
                recv_data = self.socket.recv(self.max_size)
                f.write(recv_data)
                recv_size += len(recv_data)
                print('文件总大小:%s,已下载文件:%s'%(file_size,recv_size/file_size))

    def put(self, str):
        cmd = str[0]  # 获得'put'
        file_name = str[1]
        file_path = os.path.normpath(os.path.join(self.client_dir, file_name))
        # 制定报头
        if not os.path.isfile(file_path):
            # os.path.isfile(path)  #判断路径是否为文件
            print('%s is not exist' % file_name)
            return
        else:
            file_size = os.path.getsize(file_path)  # 获取文件大小
        head_dic = {
            'cmd': cmd,
            'filename': os.path.basename(file_path),
            'filesize': file_size,
        }  # 制作报头
        print(head_dic)
        head_json = json.dumps(head_dic)  # 将字典转为字符串
        head_bytes = bytes(head_json, encoding=self.coding)  # 字符串转为bytes
        print(head_bytes)  # b'{"cmd": "put", "filename": "pic.jpg", "filesize": 7}'
        head_struct = struct.pack('i', len(head_bytes))  # 打包成固定字节长度的bytes数据
        self.socket.send(head_struct)  # 发送报头长度
        self.socket.send(head_bytes)  # 发送报头
        send_size = 0
        with open(file_path, 'rb') as f:
            for line in f:
                self.socket.send(line)
                send_size += len(line)
                print("你上传的文件总大小为:%d	已上传:%d%%" % (file_size, (send_size / file_size) * 100))
            print('上传成功')

myclient = My_client(('127.0.0.1',3301))
if __name__ == '__main__':
    myclient.run()
View Code

 

  

原文地址:https://www.cnblogs.com/schut/p/8680707.html