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