python-selctors實現檔案上傳
阿新 • • 發佈:2018-12-07
服務端程式碼:程式目錄server/server.py 上傳檔案目錄:server/upload
import os import time import socket import selectors #封裝了一些相應的操作 BASE_DIR=os.path.dirname(os.path.abspath(__file__)) #第二步: class selectFtpServer: def __init__(self): self.dic = {} #建立空字典 self.hasReceived=0 self.sel = selectors.DefaultSelector() #通過selectors模組下的DefaultSelector這個類拿I/O多路方法拿到例項物件變成例項變數self.sel self.create_socket() #create_socket()是建立socket物件函式完成繫結功能 self.handle() #handle()函式完成迴圈監聽 #create_socket函式是用來建立socket物件完成繫結功能 def create_socket(self): server = socket.socket() #建立socket物件賦值server server.bind(('192.168.1.160', 8080)) #繫結 server.listen(5) #監聽 server.setblocking(False) #設定非阻塞 self.sel.register(server, selectors.EVENT_READ,self.accept) #有了server物件通過self.sel例項變數register註冊完成繫結功能(server跟self.accept做繫結) print("服務端已啟動,等待客戶端連線.....") #handle()函式完成迴圈監聽 def handle(self): while True: #第一步:程式啟動後走while迴圈 #第七步:執行完accept函式後到這裡 #所有操作圍繞一個物件self.sel例項變數核心物件展開的 events = self.sel.select() #第二步:呼叫self.sel例項變數,監聽的內容server封裝到events物件裡 #第八步:如果客戶端發過來此刻監聽的內容就有變化有倆個物件server和conn封裝到events物件裡 for key, mask in events: #第三步:for迴圈events(可迭代物件)拿到key和mask #第九步:for迴圈events(可迭代物件)拿到key和mask callback = key.data #第四步:當前key.data是self.accept函式賦值給callback #第十步:當前key.data是read函式賦值給callback #key.fileobj是拿到的監聽的物件。 callback(key.fileobj, mask) #第五步:執行callback執行accpt()函式 #第十一步:執行callback執行read()函式裡面放到的之前連結相應的檔案描述符conn #print('當前監聽的物件:', key.fileobj) #第六步:accept函式接收連線服務端的客戶端資訊 def accept(self,sock,mask): #接收sock和mask conn, addr = sock.accept() #sock.accept接收此時連結服務端的socket物件拿到conn和addr #print('accepted', conn, 'from', addr) print("from %s %s connected" %addr) conn.setblocking(False) #設定成非阻塞 self.sel.register(conn, selectors.EVENT_READ, self.read) #把conn跟read做繫結後程序返回到handle()函式裡的events繼續監聽 #print(conn) self.dic[conn] = {} #在空字典裡進行了conn賦值,self.dic={conn:{},} #第十二步:read函式返回給客戶端ok def read(self,conn, mask): #接收了conn和mask try: #加異常防止客戶端突然斷開 if not self.dic[conn]: #判斷self.dic[conn]裡面是否是空字典,如果是空字典,代表第一次進來 data = conn.recv(1024) #conn接收了客戶端發來的資料 cmd,filename,filesize = str(data,encoding='utf-8').split('|') #把接收到客戶端發來的包解開拿到cmd,filename,filesize個資訊 self.dic={conn:{"cmd" : cmd, "filename" : filename, "filesize" : int(filesize)}} #把拿到的cmd,filename,filesize資訊放到self.dic字典裡去後程式返回到handle()函式裡的events繼續監聽 if cmd == 'put': #如果接收的資訊是put conn.send(bytes("OK",encoding='utf8')) #給客戶端返回一條資料 if self.dic[conn]['cmd'] == 'get': file = os.path.join(BASE_DIR,"upload",filename) if os.path.exists(file): fileSize = os.path.getsize(file) send_info = '%s|%s' %('YES',fileSize) conn.send(bytes(send_info, encoding='utf8')) else: send_info = '%s|%s' %('NO',0) conn.send(bytes(send_info, encoding='utf8')) else: #如果不是空字典代表不是第一次進來 if self.dic[conn].get('cmd',None): #對接收的命令進行分發判斷是put還是get cmd=self.dic[conn].get('cmd') if hasattr(self, cmd): #如果cmd=put呼叫put函式,如果是cmd=get函式呼叫get函式 func = getattr(self,cmd) func(conn) else: print("error cmd!") conn.close() else: print("error cmd!") conn.close() except Exception as e: print('斷開的客戶端資訊是:', conn) self.sel.unregister(conn) #如果沒有接收到資料做一個關閉解除 conn.close() #put上傳函式 def put(self, conn): fileName = self.dic[conn]['filename'] fileSize = self.dic[conn]['filesize'] path = os.path.join(BASE_DIR,"upload",fileName) #拿到要接收的資訊 #print(fileName,fileSize,path) recv_data = conn.recv(1024) #接收客戶端上傳的資料1024位元組 self.hasReceived += len(recv_data) #把接收的資料累加到變數self.hasReceived with open(path, 'ab') as f: #開啟檔案 f.write(recv_data) #把接收的資料寫到檔案裡去 if fileSize == self.hasReceived: #判斷檔案大小跟接收大小是否一樣 if conn in self.dic.keys(): #如果檔案大小跟接收大小一樣清空字典 self.dic[conn] = {} print("%s 上傳完畢!" %fileName) if __name__=='__main__': selectFtpServer() #第一步:例項化觸發selectFtpServer這個類
客戶端程式碼:目錄結果:/client/clenb.py
import socket import os,sys BASE_DIR=os.path.dirname(os.path.abspath(__file__)) class selectFtpClient: def __init__(self): self.args=sys.argv #sys.argv在命令列輸入的引數,第一個引數預設檔名,第二個引數跟IP地址和埠 if len(self.args)>1: #如果大於1把第二個引數倆個值賦值給port self.port=(self.args[1],int(self.args[2])) else: self.port=("192.168.1.160",8080) #如果沒有第二個引數預設取這個 self.create_socket() # self.command_fanout() #進行命令分發 #create_socket函式建立socket物件連線服務端 def create_socket(self): try: self.sk = socket.socket() self.sk.connect(self.port) print('連線FTP伺服器成功!') except Exception as e: print("eroor:",e) #command_fanout()函式進行命令分發 def command_fanout(self): while True: cmd = input('>>>').strip() #引導使用者輸入上傳還是下載 if cmd == 'exit()': break cmd,file = cmd.split() #把輸入的命令分開進行反射 if hasattr(self,cmd): func = getattr(self,cmd) func(cmd,file) else: print('呼叫錯誤!') #put()上傳函式 def put(self,cmd,file): if os.path.isfile(file): #判斷本地檔案是否存在 fileName = os.path.basename(file) #取出檔案的名字 fileSize = os.path.getsize(file) #取出檔案的大小 fileInfo = '%s|%s|%s'%(cmd,fileName,fileSize) #給檔名字大小打包成fileInf self.sk.send(bytes(fileInfo, encoding='utf8')) #呼叫send方法把fileInf發給服務端 recvStatus = self.sk.recv(1024) #接收服務端返回的OK內容 print('recvStatus' , recvStatus) hasSend = 0 if str(recvStatus, encoding='utf8') == "OK": #如果接收到服務端返回的OK with open(file, 'rb') as f: #開啟檔案 while fileSize > hasSend : #迴圈的去上傳檔案 contant = f.read(1024) recv_size = len(contant) self.sk.send(contant) hasSend += recv_size s=str(int(hasSend/fileSize*100))+"%" print("正在上傳檔案: "+fileName+" 已經上傳:" +s) print('%s檔案上傳完畢' % (fileName,)) else: print('檔案不存在') #get()下載函式 def get(self,cmd,file): pass if __name__=='__main__': selectFtpClient()