python學習之路(三)使用socketserver進行ftp斷點續傳


最近學習python到socketserver,本着想試一下水的深淺,采用Python3.6.

目錄結構如下:

receive_file和file為下載或上傳文件存放目錄,ftp_client為ftp客戶端,ftp_server為server端。

server端源碼:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socketserver
import os
error_code = {'400':'FILE IS NOT EXISTS'}   

file_path = os.path.join(os.path.abspath('.'),'file')   #獲取文件目錄路徑
'''服務端采用socketserver方式'''
class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
           # print('new conn',self.client_address)
            data = self.request.recv(100)  #接收客戶端請求
            if not data.decode():
                break
            elif data.decode().split()[0] == 'get':    #server判斷是下載還是上傳文件,get是下載
                offset = data.decode().split('|')[1]   #取出偏移量
                file = data.decode().split()[1].split('|')[0]   #取出要下載的文件名
                filename = os.path.join(file_path,file)
                read_len = 0
                if os.path.exists(filename) :   #判斷是否有資源
                    with open(filename,'rb') as fd:
                        while True:
                            send_data = fd.read(1024)
                            read_len += len(send_data)  #記錄讀取數據長度
                            if send_data and read_len > int(offset):   #達到偏移量發送數據
                                ack_msg = "SEND SIZE|%s" % len(send_data)
                                self.request.send(ack_msg.encode())
                                client_ack = self.request.recv(50)
                                if client_ack.decode() =="CLIENT_READY_TO_RECV":
                                    self.request.send(send_data)
                            elif read_len <= int(offset):
                                continue
                            else:
                                send_data ='END'
                                self.request.send(send_data.encode())   #數據傳輸完畢發送finally信號
                                break
                else:
                    msg = '400'
                    self.request.send(msg.encode())
            elif data.decode().split()[0] == 'put':  #判斷客戶端是不是上傳行為
                file = data.decode().split()[1]      #獲取需要上傳的文件名
                filename = os.path.join(file_path,file)   #定義文件路徑
                log = "%s.%s" % (file,'log')           #指定記錄偏移日志文件名
                logname = os.path.join(file_path,log)   #定義日志路徑
                if os.path.exists(filename) and os.path.exists(logname):    #如果要上傳的文件和日志文件同時存在,說明需要進行續傳
                    with open(logname) as f:
                        offset = f.read().strip()   #讀取偏移量
                else:
                    offset = 0          #表示不需要進行續傳,直接從頭開始傳
                server_syn_msg = "offset %s" % offset   #把偏移信息發送給客戶端
                self.request.send(server_syn_msg.encode())
                total_len = int(offset)    #獲取已傳輸完的文件長度,即從這個位置開始接收新的數據
                while True:
                    receive_ack = self.request.recv(100)   #客戶端接收到偏移信息后通知服務端要發送數據的長度信息,相當於一個ack
                    res_msg = receive_ack.decode().split('|')
                    if receive_ack.decode() == 'END':   #判斷文件是否上傳完成,完成后刪掉偏移日志
                        os.remove(logname)
                        break
                    elif res_msg[0].strip() =='SEND SIZE':   #如果服務端收到了客戶端發送過來的ack,給客戶端返回一個syn信息,表示可以開始傳數據了
                        res_size = res_msg[1]
                        self.request.send(b'CLIENT_READY_TO_RECV')
                        recv_data = self.request.recv(1024)  #接收數據
                        total_len += len(recv_data)   #記錄接收數據長度
                    with open(filename,'ab') as fd:    #以追加的方式寫入文件
                        fd.write(recv_data)
                    with open(logname,'w') as f:   #把已接收到的數據長度寫入日志
                        f.write(str(total_len))
if __name__ == '__main__':
    host,port = "localhost",5000
    server = socketserver.ThreadingTCPServer((host,port),MyTCPHandler)
    server.serve_forever()   #開啟服務端

客戶端源碼:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import os,sys

receive_file_path = os.path.abspath(os.path.join(os.path.abspath('.'),'receive_file'))  #指定文件目錄路徑
error_code = {'400':'FILE IS NOT EXISTS'}
'''使用類的方式,方便反射'''
class SOCKET(object):
    def __init__(self,ip,port):
        self.ip = ip
        self.port = port
    def socket_obj(self):
        sk = socket.socket()
        sk.connect((self.ip,self.port))
        return sk
    def get(self):  #get表示從服務端下載文件到本地
        conn = self.socket_obj()  #生成對象
        user_input = input('get filename:')   #指定輸入命令格式 get filename
       # print(msg,type(msg))
        filename = user_input.split()[1]   #獲取文件名
        file = os.path.join(receive_file_path,filename)   #下載文件的絕對路徑
        logname = '%s.%s' % (filename,'log')    #生成日志名
        log = os.path.join(receive_file_path,logname)   #偏移量日志的絕對路徑
        if os.path.exists(log) and os.path.exists(file):    #判斷是否需要續傳,如果需要就讀出偏移量
            with open(log) as f:
                offset = f.read().strip()
        else:
            offset = 0                   # 否則偏移量置0
        msg = "%s|%s" %(user_input,offset)
        conn.send(msg.encode())
        total_length = int(offset)           #記錄傳輸完成了多少
        while True:
            server_ack_msg = conn.recv(100)   #接收第一個ack
            if server_ack_msg.decode().strip() == '400':    #如果ftp服務器沒有這個資源,返回錯誤
                print('400', error_code['400'])
                conn.close()
                break
            elif server_ack_msg.decode().strip() == 'END':   #傳輸完成,ftp server返回字段,並刪除偏移量日志
                conn.close()
                os.remove(log)
                break
            res_msg = server_ack_msg.decode().split('|')  #接收server的syn和傳輸數據大小的信息
            if res_msg[0].strip() == "SEND SIZE":
                res_size = int(res_msg[1])
                conn.send(b'CLIENT_READY_TO_RECV')   #給server返回ack
                receive_data = conn.recv(1024)    #接收server的數據
                total_length += len(receive_data)  #記錄接收到了多少數據
               # print(receive_data.decode())
              # print(total_length)
            with open(file,'ab') as fd:   #以追加的方式寫文件
                fd.write(receive_data)
            with open(log,'w') as f:     #把已接收數據長度寫進日志
                f.write(str(total_length))

    def put(self):  #put表示上傳文件至服務端
        conn = self.socket_obj() #生成對象
        msg = input('put filename:')   #指定命令輸入格式,put filename
        filename = os.path.join(receive_file_path, msg.split()[1])   #生成上傳文件路徑
        if  os.path.exists(filename):  #判斷文件存在與否,不存在返回錯誤
            conn.send(msg.encode())   #發送文件行為與文件名至服務端
            server_syn_msg = conn.recv(100)  #接收服務端發送的偏移量信息
            offset = server_syn_msg.decode().split()[1]
            read_length = 0   #重置需要讀取文件的長度
            with open(filename,'rb') as fd:
                while True:
                    send_data = fd.read(1024)   #開始讀取文件,每次讀取1024字節
                    read_length += len(send_data)  #記錄讀取數據長度
                    if send_data and read_length> int(offset):  #和服務端發送的偏移量進行比較,只有數據不為空和讀到超過偏移量才會發送數據
                        ack_msg = "SEND SIZE|%s" %len(send_data)  #給服務端發送本次要發送數據的長度,相當於一個syn
                        conn.send(ack_msg.encode())
                        client_ack = conn.recv(100) #接收到服務端發送的ack確認信息,收到之后開始傳輸數據
                        if client_ack.decode() =='CLIENT_READY_TO_RECV':
                            conn.send(send_data)
                    elif read_length <= int(offset):  #如果讀取到的數據長度沒到偏移量就繼續循環讀取文件
                        continue
                    else:
                        send_data = 'END'   #文件已經讀完,表示已經全部發送完成,給服務端發送信息說明客戶端已經發送完成
                        conn.send(send_data.encode())
                        break
        else:
            print('400', error_code['400'])


if __name__ == '__main__':
    c = SOCKET('127.0.0.1',5000)
    
    if hasattr(c,sys.argv[1]):
        func = getattr(c,sys.argv[1])
        func()

由於時間原因,存在在傳輸的過程中有些文件里面涉及到中文的可能會報錯的bug,只是功能基本實現,給大家分享一下我的思路,方便交流


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM