最近学习python到socketserver,本着想试一下水的深浅,采用Python3.6.

目录结构如下:

python 断点续传 python ftp断点续传_偏移量

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,只是功能基本实现,给大家分享一下我的思路,方便交流