基于SocketServer,实现一个FTP传输协议

一、为了实现一个FTP协议(大型文件的传输协议),这里引入了Python中的SocketServer模块,可以处理不同的任务请求,即上传和下载功能。

二,SocketServer模块底层的实现细节可以查看下面链接的博客,讲的十分详细:

点击打开链接

这里说明一下自己的理解。

首先,该模块在实例化的过程中,通过层层的继承关系,创建套接字socket,绑定到服务端口,再监听客户端;如果有链接进来,在建立链接。

其次,再建立完成链接之后,如果有事件进来,则会触发该模块创建新的线程来处理该事件请求。

最后,来一个新的事件,就会创建一个新的线程来处理请求。直到链接断开则关闭与该客户端的链接。

三、谈一下大型文件传输的粘包问题和如何确认传输完成的问题

1、粘包问题:

当发送文件的大小超过recv()中接收的长度时,多余的数据会被存储在缓冲区中;这样的话就会造成下一次发送的数据,接收端接收的可能是上一次在缓冲区中的数据。

为了防止这种情况的发生:可以利用recv一次,send一次,来判断是否粘包。

2、大文件的传输:

大文件的传输很容易造成粘包问题,因此在发送和接收的时候可以利用文件的大小作判断,循环发送或者接收文件。

四、以下是ftp的一个简单的实现

1、服务器端代码

#基于SocketServer实现的ftp服务器端
#named:ftp_server_socket.py

import socketserver,os
import json

Addr = ('localhost', 9533)

class MyRequestHandle(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                #接受客户端发送过来的指令
                self.data = self.request.recv(1024).strip()
                #打印客户端的链接地址
                print('the client address is','{}:'.format(self.client_address[0]))
                #利用json包解析发送过来的命令
                cmd_dir = json.loads(self.data.decode())
                cmd = cmd_dir['action']

                # 利用反射的原理,根据客户端发送过来的指令,决定是执行上传还是下载命令
                if hasattr(self, cmd):
                    func = getattr(self, cmd)
                    func(cmd_dir)
            except ConnectionResetError as e:
                print('err',e)
                break

    def put(self, *argv):
        cmd_str = argv[0]
        filename = cmd_str['filename']
        file_size = cmd_str['size']
        #检测服务器端是否存在该文件;如果存在重新命名
        if os.path.isfile(filename):
            f = open(filename+'.new', 'wb')
        else:
            f = open(filename, 'wb')

        #向客户端发送已经准备好可以接受数据了
        self.request.send(b'200 OK!')
        received_size = 0 #已经接受的数据大小
        while received_size < file_size:
            file_data = self.request.recv(1024)
            f.write(file_data)
            received_size += len(file_data)
        else:
            print('%s has been loaded over'%filename)

    def get(self, *argv):
        cmd_str = argv[0]
        filename = cmd_str['filename']
        #检测服务器端是否存在需要下载的文件
        if os.path.isfile(filename):
            file_size = os.stat(filename).st_size
            self.request.send(str(file_size).encode())
            client_response = self.request.recv(1024).decode()
            print('client response is %s'%client_response)
            f = open(filename, 'rb')
            for line in f:
                self.request.send(line)
            else:
                print('%s has been sent over'%filename)
            f.close()
        else:
            print('%s is not exisiting in server'%filename)

Ftp_Server = socketserver.TCPServer(Addr,MyRequestHandle)
print('waiting for connection...')
Ftp_Server.serve_forever()

2、客户端代码

#基于SocketServer的ftp客户端
#named:ftp_client_socket.py

import socket, os
import json

Addr = ('localhost',9533)

class FTP_Client(object):
    def __init__(self):
        self.ftp_socket = socket.socket()

    def connection(self, ADDR):
        self.ftp_socket.connect(ADDR)

    def help(self):
        msg = {
        'get filename',
        'put filename'
        }
        print('the use of ftp is:',msg)

    #客户端的接口,利用反射进行具体的上传和下载功能
    def interactive(self):
        while True:
            cmd = input('>>:').strip()  #strip()函数功能可以实现对字符串头部和尾部的空格和换行符进行删除处理
            if len(cmd)==0:
                continue
            cmd_order = cmd.split()[0]
            #利用反射选择到底是执行上传功能还是下载功能
            if hasattr(self, 'cmd_%s'%cmd_order):
                func = getattr(self, 'cmd_%s'%cmd_order)
                func(cmd)
            else:
                self.help()

    #上传文件
    def cmd_put(self, *argv):
        cmd_str = argv[0].split() #客户端输入的命令
        if len(cmd_str)>1:
            filename = cmd_str[1] #上传文件的文件名
            if os.path.isfile(filename): #如果当前路径下存在该文件
                #以json的格式将上传文件的相关信息发送过去
                file_size = os.stat(filename).st_size
                msg_dic = {
                    'action':'put',
                    'filename':filename,
                    'size':file_size,
                    'overloaded':False
                }
                self.ftp_socket.send(json.dumps(msg_dic).encode())
                #防止粘包,等待客户端确认
                server_response = self.ftp_socket.recv(1024).decode()
                print(server_response)
                #发送文件
                f = open(filename, 'rb')
                for line in f:
                    self.ftp_socket.send(line)
                else:
                    print('%s has been sent over'%filename)
                f.close()
            else:
                print('the %s is not exisit'%filename)
        else:
            self.help()


    #下载文件
    def cmd_get(self, *argv):
        cmd_str = argv[0].split()
        if len(cmd_str) > 1:
            action = cmd_str[0]
            filename = cmd_str[1]
            msg_dic = {
                'action':action,
                'filename':filename,
                'overloaded':False
            }
            self.ftp_socket.send(json.dumps(msg_dic).encode())
            #接收文件的大小
            file_size = int(self.ftp_socket.recv(1024).decode())
            #向服务器端发送准备接收文件的命令
            self.ftp_socket.send(b'400 OK!')
            received_size = 0
            #检测客户端是否存在该文件
            if os.path.isfile(filename):
                f = open(filename+'.new', 'wb')
            else:
                f = open(filename, 'wb')
            while received_size < file_size:
                file_data = self.ftp_socket.recv(1024)
                f.write(file_data)
                received_size += len(file_data)
            else:
                print('%s has been loaded'%filename)
        else:
            self.help()

Ftp_Client = FTP_Client()
Ftp_Client.connection(Addr)
Ftp_Client.interactive()

3、上述的实现过程可以解决粘包问题,实现了大文件的上传和下载。


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值