1. 程式人生 > >python學習之路(三)使用socketserver進行ftp斷點續傳

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

def += __init__ con 不存在 不為 local 接收 class

最近學習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(bCLIENT_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(bCLIENT_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,只是功能基本實現,給大家分享一下我的思路,方便交流

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