1. 程式人生 > 程式設計 >python 基於selectors庫實現檔案上傳與下載

python 基於selectors庫實現檔案上傳與下載

server.py

import selectors
import socket
import os
import time


BASE_DIR =os.path.abspath(os.path.dirname(__file__))

class selectFtpserver:
  def __init__(self):
    self.dic = {} # 建立空字典
    self.hasReceived = 0
    self.hasSend=0
    self.sel = selectors.DefaultSelector() # 生成一個select物件
    self.create_socket() #create_socket()是建立socket物件函式完成繫結功能
    self.hanle() #handle()函式完成迴圈監聽

  def create_socket(self):
    sock = socket.socket()
    sock.bind(('127.0.0.1',8899))
    sock.listen()
    sock.setblocking(False)
    self.sel.register(sock,selectors.EVENT_READ,self.accept) # 把剛生成的sock連線物件註冊到select連線列表中,並交給accept函式處理
    print("服務端已開啟,請連線客戶端")

  def hanle(self):
    while True:
      events = self.sel.select() # 預設是阻塞,有活動連線就返回活動的連線列表
      # 這裡看起來是select,其實有可能會使用epoll,如果你的系統支援epoll,那麼預設就是epoll
      # print("event==",events)
      for key,mask in events:
        callback = key.data # 去調accept函式
        callback(key.fileobj,mask) # key.fileobj就是readable中的一個socket連線物件

  def accept(self,sock,mask):
    conn,addr = sock.accept() # Should be ready
    print('accepted',conn,'from',addr)
    conn.setblocking(False) # 設定非阻塞
    self.sel.register(conn,self.read) # 新連線註冊read回撥函式
    self.dic[conn] = {} # 在空字典裡進行了conn賦值,self.dic={conn:{},}

  def read(self,mask): # 接收了conn和mask
    try: # 加異常防止客戶端突然斷開
      if not self.dic[conn]: # 判斷self.dic[conn]裡面是否是空字典,如果是空字典,代表第一次進來
        print('====第一次進來')
        data = conn.recv(1024) # conn接收了客戶端發來的資料
        print("data==",str(data,encoding='utf-8'))
        cmd,filename,filesize = str(data,encoding='utf-8').split('|') # 把接收到客戶端發來的包解開拿到cmd,filesize個資訊
        self.dic = {conn: {"cmd": cmd,"filename": filename,"filesize": int(filesize)}} # 把拿到的cmd,filesize資訊放到self.dic字典裡去後程式返回到handle()函式裡的events繼續監聽
        print(self.dic)
        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):
            print("檔案存在的情況,返回YES給客戶端")
            filesize = os.path.getsize(file)
            self.dic[conn]['filesize'] = filesize
            print("self.dic",self.dic)
            send_info = '%s|%s' % ('YES',filesize)
            conn.send(bytes(send_info,encoding='utf8'))
          else:
            print("檔案不存在情況下")
            send_info = '%s|%s' % ('NO',0)
            conn.send(bytes(send_info,encoding='utf8'))
            self.dic[conn] = {} #檔案不存在的情況下,要將清空字典
      else: # 如果不是空字典代表不是第一次進來
        print('不是第一次來的')
        print(self.dic)
        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']
    # print("BASE_DIR",BASE_DIR)
    path = os.path.join(BASE_DIR,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] = {}
      self.hasReceived = 0 #S上傳結束之後,需要將self.hasReceived 重置成功
      print("%s 上傳完畢!" % fileName)

  def get(self,conn):
    fileName = self.dic[conn]['filename']
    file = os.path.join(BASE_DIR,fileName)
    # fileSize = os.path.getsize(file)
    fileSize=self.dic[conn]['filesize']

    data = conn.recv(1024) # conn接收了客戶端發來的資料
    dataOK = str(data,encoding='utf-8')

    if dataOK == 'OK':
      with open(file,'rb') as f: # 開啟檔案
        while fileSize > self.hasSend: # 迴圈的傳送檔案給客戶端
          contant = f.read(1024)
          recv_size = len(contant)
          conn.send(contant)
          self.hasSend += recv_size
          s = str(int(self.hasSend / fileSize * 100)) + "%"
          print("正在下載檔案: " + fileName + " 已經下載:" + s)

      if fileSize == self.hasSend: # 判斷檔案大小跟接收大小是否一樣
        if conn in self.dic.keys(): # 如果檔案大小跟接收大小一樣清空字典
          self.dic[conn] = {}
        print("%s 下載完畢!" % fileName)
        self.hasSend = 0

if __name__ == '__main__':
  selectFtpserver()

client.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=("127.0.0.1",8899)        #如果沒有第二個引數預設取這個
    self.create_socket()               #
    self.command_fanout()              #進行命令分發
    self.mainPath = os.path.join(BASE_DIR,'filename') # 獲取該客戶端下的filename路徑

  #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:
      try:
        print("----------------welcome to ftp client-------------------")
        self.help_info()
        cmd_info = input('>>>請輸入操作命令:').strip() # put 12.png images
        if not cmd_info:
          continue
        cmd,file = cmd_info.split() ##按照空格分隔
        # print("命令是什麼",cmds)
        if cmd == "quit":
          break
        if hasattr(self,cmd):
          func = getattr(self,cmd)
          func(cmd,file)
          Tag = input("是否繼續進入ftp clinet,請選擇Y/N:").strip()
          if Tag.upper() == 'Y':
            continue
          else:
            break
        else:
          print('No such command,please try again')
      except Exception as e: # server關閉了
        print('%s' % e)
        break

  def help_info(self):
    print ('''
       get + (檔名)  表示下載檔案
       put + (檔名)  表示上傳檔案
       quit       表示退出登入
    ''')

  #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,fileName):
    path = os.path.join(BASE_DIR,"download",fileName) # 拿到要接收的資訊
    fileSize=0
    fileInfo = '%s|%s|%s' % (cmd,fileSize) # 給檔名字大小打包成fileInf
    print(fileInfo)
    self.sk.send(bytes(fileInfo,encoding='utf8')) # 呼叫send方法把fileInfo發給服務端

    recvdata = self.sk.recv(1024) # 接收服務端返回的是否存在檔案內容
    recvStatus,fileSize = str(recvdata,encoding='utf-8').split('|')
    print("recvStatus==",recvStatus,fileSize)
    fileSize = int(fileSize)

    hasReceived = 0
    if recvStatus == "YES": # 如果接收到服務端返回的YES
      self.sk.send(bytes('OK',encoding='utf8')) # 通知服務端可以正常下載了

      while fileSize > hasReceived: # 迴圈的傳送檔案給客戶端
        recv_data = self.sk.recv(1024) # 接收客戶端上傳的資料1024位元組
        hasReceived += len(recv_data) # 把接收的資料累加到變數self.hasReceived
        print("hasReceived",hasReceived)

        with open(path,'ab') as f: # 開啟檔案
          f.write(recv_data) # 把接收的資料寫到檔案裡去

        if fileSize == hasReceived: # 判斷檔案大小跟接收大小是否一樣
          print("%s 下載完畢!" % fileName)
          recvStatus = 'YESS'
    else:
       print('要下載的檔案不存在')


if __name__=='__main__':
  selectFtpClient()

以上就是python 基於selectors庫實現檔案上傳與下載的詳細內容,更多關於python 上傳下載的資料請關注我們其它相關文章!