Python--socket和threading編程


網絡編程基礎(一)

    •   TCP/IP協議
      •   OSI/RM協議
      •   特點:獨立於計算機硬件和操作系統,統一分配網絡地址,提供可靠服務,隔離了物理網絡的硬件差異
      •   協議分層(百度):網絡接口層:IEE802.3(以太網協議集),IEEE802.4(令牌環網);網絡層(IP);傳輸層(tcp/udp);應用層(FTP/HTTP/SMTP/DNS)
    •   IP地址和端口
  • 網絡編程基礎(二)
    •   UDP協議
    •   TCP協議
    •   套接字Socket
      •   TCP連接的端點稱作套接字
      •   表示方法:IP地址:端口號,一個socket就是:(ip地址:端口號)
      •   一個TCP連接就是兩個套接字,也就是{(IP地址:端口號),(IP地址:端口號)}
      •   每一條TCP連接被兩個套接字確定,同一個ip地址可以有不同的TCP連接,同一個端口號可以出現在不同的TCP連接中
    •   TCP和UDP的不同點
      •   TCP先建立連接,再通信,最后釋放連接,udp不用連接
      •   TCP保證數據可靠交付;TCP不保證可靠交付,用戶自行處理可靠性
      •   TCP連接開銷大,UDP小;TCP使用實時性低,數據可靠性高的場合,UDP適合實用性高,數據可靠性低的場合
    •   TCP和UDP的相同點
      •   都位於TCP/IP協議的第四層
      •   為應用層提供服務,都要通過網際層來一線數據的傳輸ICMP協議
    •   TCP協議
      •   HTTP,FTP,TELNET,POP,SMTP
    •   UDP協議
      •   TFTP,DNS,SNMP,VOIP,QQ
  • 服務器端socket的建立
    •   C/S模式簡介:客戶/服務器模式,客戶端為主動向服務器發出服務請求的一方。服務器一般在系統啟動時自動調用運行,等待客戶機的請求
      與C/S模式相對的是B/S(瀏覽器/服務器模式),客戶端使用同意的瀏覽器,而不用裝門部署,服務器和瀏覽器使用HTTP協議進行通信
    •   套接字網絡編程
      •   TCP通信
      •   UDP通信
  • Python中的socket
    •   socket對象是支持網絡通信的,socket對象支持使用TCP/UDP協議進行網絡通信(只能選擇其中一個協議)
    •   socket編程所需要的對象函數和常量
    •   創建套接字:socket.socket(family,type)   family表示套接字使用什么版本協議    Type=SOCK_STREAM(使用TCP)   type=sock_DGRAM(UDP協議)
    •   服務器端套接字的方法
      •   bind(address)綁定,address為(ip:port):將套接字綁定到本地主機的ip或者端口上;
      •   listen(backlog):開始Tcp轉入連接。backlog拒絕連接前允許操作系統掛起的連接數,1-5
      •   accept():接收TCP連接,並返回連接上的套接字對象和客戶端地址構成的元組。返回的連接上的套接字對象可用於接收和發送信息
    •   客戶端socket對象的方法
      •   connect(address),address=(hostname,port)構成的元組,建立與服務器間的連接
    •   TCP協議的socket收發數據的方法
      •   recv(【buffersize】):接收數據,參數為接收最大數據量,返回接收的數據
      •   send(bytes)通過socket發送數據
      •   sendall(bytes)通過socket發送數據(返回前將數據都發送出去)
    •   UDP協議的socket收發數據方法
      •   recvfrom(與上面類似)
      •   sendto(bytes,address)發送的字節和指定發送的目的地址
    •   關閉socket;close()
    •   其他相關函數
      •   gethostname()返回本地主機名
      •   gethostbyname_ex(hostname)#返回元組(hostname,aliaslist,ipaddrrlist)
    •   用socket建立TCP服務器端方法
      •   基本步驟
        •   創建套接字並綁定地址,開始監聽連接,接收連接並收發數據,關閉套接字
        • #coding=gbk
          
          import socket
          
          HOST = ''
          PORT = 321
          
          s=socket.socket()
          s.bind((HOST,PORT))
          
          s.listen(5)
          
          client,addr=s.accept()#接收客戶端的連接,返回一個客戶端,
          
          print('client address:',addr)
          
          while True:
              data = client.recv(1024)#接收數據
              if not data:#為空,斷開連接
                  break
              else:
                  print('receive data :',data.decode('utf-8'))#數據不為空,輸出
              client.send(data)#將數據發揮客戶端
          client.close()
          s.close()
          View Code
    •   用socket建立UDP服務器端方法
      •   基本步驟
        •   創建套接字並綁定地址,開始監聽連接,收發數據,關閉套接字
        • #coding=gbk
          
          import socket
          
          HOST = ''
          PORT = 3214
          
          #socket.socket(family,type)   family表示套接字使用什么版本協議    Type=SOCK_STREAM(使用TCP)   type=sock_DGRAM(UDP協議)
          s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#使用iPv4協議
          s.bind((HOST,PORT))
          
          data = True
          
          
          while data:
              data,addr = s.recvfrom(1024)
              if data==b'bye':#為空,斷開連接
                  break
              else:
                  print('receive data :',data.decode('utf-8'))#數據不為空,輸出
              s.send(data,addr)#將數據發揮客戶端
          s.close()
          View Code
  • 客戶端socket建立
    •   socket的異常
      •   error:套接字或者地址有關的錯誤
      •   herror:error的子類,表示發生與地址有關的錯誤
      •   gaierror:getaddressinfo()或者gethostinfo()中的與地址有關的錯誤
      •   timeout:套接字超時錯誤
      •   處理異常
        •   try,catch進行
    •   用TCP實現客戶端
      •   基本步驟
        •   創建套接字,用套接字連接服務器;收發數據;關閉套接字
          #coding=gbk
          import socket
          
          
          HOST = '127.0.0.1'
          PORT = 3215
          s=socket.socket()
          
          try:
              s.connect((HOST,PORT))
              data="nihao!"
              while data:
                  s.sendall(data.encode('utf-8'))#編碼發送出去的信息
                  data=s.recv(1024)#接收數據
                  print('reveive is :\n',data.decode('utf-8'))#解碼打印收到的數據
                  data=input('please input string:\n')
          except socket.errno as err:
              print(err)
          finally:
              s.close()
              
                  
          View Code
    •   用UDP實現客戶端
      •   基本步驟
        •   創建套接字,收發數據,關閉套接字
        • #coding=gbk
          import socket
          
          
          HOST = '127.0.0.1'
          PORT = 3215
          s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#使用iPv4協議)
          
          data='nihao'
          while data:
                  s.sendto(data.encode('utf-8'),(HOST,PORT))#編碼發送出去的信息
                  if data=='bye':
                      break
                  data,addr=s.recvfrom(1024)
                  print('receive is :\n',data.decode('utf-8'))
              
                  data=input('please input string:\n')
          
          s.close()
              
                  
          View Code
  • 備份服務器的服務器端的實現
    •   嘗試一個C/S模式下網絡遠程備份系統,C/S模式下開發服務器端和客戶端,穿送的是文件所以使用TCP協議進行
    •   備份服務器的功能分析
      •   可以自定義服務的IP的地址和端口號
      •   指定保存備份文件的目錄
      •   關閉服務器
      •   以多線程的方式同時為多個客戶端提供備份服務
      • #coding=gbk
        from tkinter import *
        from tkinter.ttk import *
        import socket
        
        import struct
        
        
        def start(host,port):
            pass
        
        def MyFrame(Frame):
            #初始化構造方法
            def __init__(self,root):
                super().__init__(root)
                self.root=root
                self.grid()#布局方式網格布局
                self.local_ip='127.0.0.1'#服務器端默認的IP地址
                self.serv_ports=[10888,20888,30888]#三個服務器的端口
                self.init_components()#初始化界面方法
            
            #界面函數的實現
            def init_components(self):#初始化用戶界面的方法
                proj_name=Label(self,text='遠程備份服務器')
                proj_name.grid(columnspan=2)#網格式布局,占用兩列
                
                serve_ip_label=Label(self,text='服務地址')
                serve_ip_label.grid(row=1)#列是默認為0列
                
                #下拉列表,顯示服務器的地址拱用戶選擇
                self.serv_ip=Combobox(self,values=self.get_ipaddr())
                #設置默認的服務器的ip地址
                self.serv_ip.set(self.local_ip)
                self.serv_ip.grid(row=1,column=1)
                
                #服務端口的LABEL
                serv_port_label=Label(self,text='服務端口')
                serv_port_label.grid(row=2) 
                #下拉列表,顯示服務器的服務端口拱用戶選擇
                self.serv_port=Combobox(self,values=self.serv_ports)
                #設置默認的服務端口
                self.serv_port.set(self.serv_ports[1])
                #網格布局  放置指定位置
                self.serv_port.grid(row=2,column=1)
                
                #啟動按鈕
                self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv)
                self.start_serv_btn.grid(row=3)
                
                #退出服務的按鈕
                self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy)
                self.start_exit_btn.grid(row=3,column=1)
                
            def get_ipaddr(self):
                #獲取本機的ip地址
                #獲取名字
                host_name=socket.gethostname()
                #獲取信息
                info=socket.gethostbyname_ex(host_name)
                info=info[2]
                info.append(self.local_ip)
            
            #定義啟動服務器的方法    
            def start_serv(self):
                print(self.serv_ip.get(),self.serv_port.get())
                start(self.serv_ip.get(),self.serv_port.get())
                
        if __name__=='__main__':
            root=Tk()
            root.title('備份服務器')
            root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
            a=MyFrame(root)
            a.mainloop()
        View Code
    •    最簡備份服務器端建立
      •   同一時間只能連接一個客戶並且為其備份
      •   備份客戶端的一個目錄及其子目錄的所有文件
      •   與客戶端交互
        •   客戶端發送即將要發送文件信息的大小
        •   服務器端接收客戶端通知的文件信息的大小
        •   客戶端發送文件信息(包括文件大小。文件名)
        •   服務器端依照文件信息的大小接收文件信息
        •   客戶端逐個發送文件數據,每發送完一個文件數據,接收該文件的備份結果
        •   服務器端接收文件數據並保存備份至文件系統,每接收完一個文件就返回備份結果
        • #coding=gbk
          from tkinter import *
          from tkinter.ttk import *
          import socket
          import os
          import struct
          import pickle
          #建立一個默認的備份目錄
          BAK_PATH=r'e:\bak'
          
          #根據指定長度來接受文件信息
          def recv_unit_data(clnt,infos_len):
              data=b'' #原來文件為空
              #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可
              if 0<infos_len<=1024:#
                  data+=clnt.recv(infos_len)#接收文件
              else:#長度太長
                  while  True: 
                      if infos_len >1024:
                         data+=clnt.recv(1024) 
                         infos_len-=1024
                      else:
                          data+=clnt.recv(infos_len)
                          break
              return data
          
          def get_files_info(clnt):
              fmt_str='Q'#用於向服務器端傳送文件信息的大小
              headsize=struct.calcsize(fmt_str)#計算長度
              data=clnt.recv(headsize)
              infos_len=struct.unpack(fmt_str, data)[0]
              data =recv_unit_data(clnt, infos_len)
              return pickle.loads(data)#得到文件信息的列表
             
          def mk_math(filepath): #建立文件路徑
              paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割
              p=BAK_PATH
              for path in paths:#遍歷用戶端傳來的路徑
                  p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上
                  if not os.path.exists(p):#如果路徑不存在就建立路徑
                      os.mkdir(p)
                      
          #接收客戶端傳來文件,並且根據文件信息來進行保存備份
          def recv_file(clnt,infos_len,filepath):            
              mk_math(filepath)#遍歷文件   通過路徑
              filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名
              f = open(filepath,'wb+')#新建一個文件
              #接收文件
              try:
                  if 0 < infos_len <=1024:
                      data = clnt.recv(infos_len)
                      f.write(data)
                  else:
                      while True:
                          if infos_len >1024:
                              data=clnt.recv(1024)
                              f.write(data)
                              infos_len-=1024
                          else:
                              data = clnt.recv(infos_len)
                              f.write(data)
                              break
              except:
                  print('error')
              else:
                  return True
              finally:
                  f.close()
            
            #向客戶端發送失敗成功消息      
          def send_echo(clnt,res):
              if res:
                  clnt.sendall(b'success')
              else:
                  clnt.sendall(b'failure')
                  
          #啟動服務器的方法
          def start(host,port):
              if not os.path.exists(BAK_PATH):
                  os.mkdir(BAK_PATH)
              st=socket.socket() #tcp協議
              st.bind(host,port) #綁定套接字
              st.listen(1) #偵聽網絡,一個客戶端連接
              client,addr=st.accept() #接收連接,建立連接
              
              files_lst=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組)
              for size,filepath in files_lst:#遍歷得到文件大小和路徑
                  res = recv_file(client,size,filepath)#接收所有的文件    返回備份的結果:true或者false
                  send_echo(client,res)#保存成功標志,發送給客戶端
                  
              client.close()#關閉客戶端
              st.close()
          
          def MyFrame(Frame):
              #初始化構造方法
              def __init__(self,root):
                  super().__init__(root)
                  self.root=root
                  self.grid()#布局方式網格布局
                  self.local_ip='127.0.0.1'#服務器端默認的IP地址
                  self.serv_ports=[10888,20888,30888]#三個服務器的端口
                  self.init_components()#初始化界面方法
              
              #界面函數的實現
              def init_components(self):#初始化用戶界面的方法
                  proj_name=Label(self,text='遠程備份服務器')
                  proj_name.grid(columnspan=2)#網格式布局,占用兩列
                  
                  serve_ip_label=Label(self,text='服務地址')
                  serve_ip_label.grid(row=1)#列是默認為0列
                  
                  #下拉列表,顯示服務器的地址拱用戶選擇
                  self.serv_ip=Combobox(self,values=self.get_ipaddr())
                  #設置默認的服務器的ip地址
                  self.serv_ip.set(self.local_ip)
                  self.serv_ip.grid(row=1,column=1)
                  
                  #服務端口的LABEL
                  serv_port_label=Label(self,text='服務端口')
                  serv_port_label.grid(row=2) 
                  #下拉列表,顯示服務器的服務端口拱用戶選擇
                  self.serv_port=Combobox(self,values=self.serv_ports)
                  #設置默認的服務端口
                  self.serv_port.set(self.serv_ports[1])
                  #網格布局  放置指定位置
                  self.serv_port.grid(row=2,column=1)
                  
                  #啟動按鈕
                  self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv)
                  self.start_serv_btn.grid(row=3)
                  
                  #退出服務的按鈕
                  self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy)
                  self.start_exit_btn.grid(row=3,column=1)
                  
              def get_ipaddr(self):
                  #獲取本機的ip地址
                  #獲取名字
                  host_name=socket.gethostname()
                  #獲取信息
                  info=socket.gethostbyname_ex(host_name)
                  info=info[2]
                  info.append(self.local_ip)
              
              #定義啟動服務器的方法    
              def start_serv(self):
                  print(self.serv_ip.get(),self.serv_port.get())
                  start(self.serv_ip.get(),int(self.serv_port.get()))
                  
          if __name__=='__main__':
              root=Tk()
              root.title('備份服務器')
              root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
              a=MyFrame(root)
              a.mainloop()
                  
          View Code
  • 備份服務器的基本客戶端實現
    •   功能
      •   設置連接服務器的IP地址和端口號
      •   輸入備份目錄,備份其中的所有文件
      •   顯示服務器端發來的備份結果
      •   選擇備份時啟用壓縮備份
  •   客戶端與服務器最終版
  • #coding=gbk
    from tkinter import *
    from tkinter.ttk import *
    import socket
    import os
    import struct
    import pickle
    #建立一個默認的備份目錄
    BAK_PATH=r'e:\bak'
    
    #根據指定長度來接受文件信息
    def recv_unit_data(clnt,infos_len):
        data=b'' #原來文件為空
        #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可
        if 0<infos_len<=1024:#
            data+=clnt.recv(infos_len)#接收文件
        else:#長度太長
            while  True: 
                if infos_len >1024:
                   data+=clnt.recv(1024) 
                   infos_len-=1024
                else:
                    data+=clnt.recv(infos_len)
                    break
        return data
    
    def get_files_info(clnt):
        fmt_str='Q'#用於向服務器端傳送文件信息的大小
        headsize=struct.calcsize(fmt_str)#計算長度
        data=clnt.recv(headsize)
        infos_len=struct.unpack(fmt_str, data)[0]
        data =recv_unit_data(clnt, infos_len)
        return pickle.loads(data)#得到文件信息的列表
       
    def mk_math(filepath): #建立文件路徑
        paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割
        p=BAK_PATH
        for path in paths:#遍歷用戶端傳來的路徑
            p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上
            if not os.path.exists(p):#如果路徑不存在就建立路徑
                os.mkdir(p)
                
    #接收客戶端傳來文件,並且根據文件信息來進行保存備份
    def recv_file(clnt,infos_len,filepath):            
        mk_math(filepath)#遍歷文件   通過路徑
        filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名
        f = open(filepath,'wb+')#新建一個文件
        #接收文件
        try:
            if 0 < infos_len <=1024:
                data = clnt.recv(infos_len)
                f.write(data)
            else:
                while True:
                    if infos_len >1024:
                        data=clnt.recv(1024)
                        f.write(data)
                        infos_len-=1024
                    else:
                        data = clnt.recv(infos_len)
                        f.write(data)
                        break
        except:
            print('error')
        else:
            return True
        finally:
            f.close()
      
      #向客戶端發送失敗成功消息      
    def send_echo(clnt,res):
        if res:
            clnt.sendall(b'success')
        else:
            clnt.sendall(b'failure')
            
    #啟動服務器的方法
    def start(host,port):
        if not os.path.exists(BAK_PATH):
            os.mkdir(BAK_PATH)
        st=socket.socket() #tcp協議
        st.bind((host,port)) #綁定套接字
        st.listen(1) #偵聽網絡,一個客戶端連接
        client,addr=st.accept() #接收連接,建立連接
        
        files_lst=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組)
        for size,filepath in files_lst:#遍歷得到文件大小和路徑
            res = recv_file(client,size,filepath)#接收所有的文件    返回備份的結果:true或者false
            send_echo(client,res)#保存成功標志,發送給客戶端
            
        client.close()#關閉客戶端
        st.close()
    
    class MyFrame(Frame):
        #初始化構造方法
        def __init__(self,root):
            super().__init__(root)
            self.root=root
            self.grid()#布局方式網格布局
            self.local_ip='127.0.0.1'#服務器端默認的IP地址
            self.serv_ports=[10888,20888,30888]#三個服務器的端口
            self.init_components()#初始化界面方法
        
        #界面函數的實現
        def init_components(self):#初始化用戶界面的方法
            proj_name=Label(self,text='遠程備份服務器')
            proj_name.grid(columnspan=2)#網格式布局,占用兩列
            
            serve_ip_label=Label(self,text='服務地址')
            serve_ip_label.grid(row=1)#列是默認為0列
            
            #下拉列表,顯示服務器的地址拱用戶選擇
            self.serv_ip=Combobox(self,values=self.get_ipaddr())
            #設置默認的服務器的ip地址
            self.serv_ip.set(self.local_ip)
            self.serv_ip.grid(row=1,column=1)
            
            #服務端口的LABEL
            serv_port_label=Label(self,text='服務端口')
            serv_port_label.grid(row=2) 
            #下拉列表,顯示服務器的服務端口拱用戶選擇
            self.serv_port=Combobox(self,values=self.serv_ports)
            #設置默認的服務端口
            self.serv_port.set(self.serv_ports[1])
            #網格布局  放置指定位置
            self.serv_port.grid(row=2,column=1)
            
            #啟動按鈕
            self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv)
            self.start_serv_btn.grid(row=3)
            
            #退出服務的按鈕
            self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy)
            self.start_exit_btn.grid(row=3,column=1)
            
        def get_ipaddr(self):
            #獲取本機的ip地址
            #獲取名字
            host_name=socket.gethostname()
            #獲取信息
            info=socket.gethostbyname_ex(host_name)
            info=info[2]
            info.append(self.local_ip)
        
        #定義啟動服務器的方法    
        def start_serv(self):
            print(self.serv_ip.get(),self.serv_port.get())
            start(self.serv_ip.get(),int(self.serv_port.get()))
            
    if __name__=='__main__':
        root=Tk()
        root.title('備份服務器')
        root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
        a=MyFrame(root)
        a.mainloop()
    
    
    
    
    #coding=gbk
    from tkinter import *
    from tkinter.ttk import *
    import socket
    import os
    import pickle
    import struct
    
    #獲取給出路徑的文件信息
    def get_file_info(path):
        if not path or not os.path.exists(path):
            return NONE
        files=os.walk(path)#獲取文件
        infos=[]
        file_paths=[]
        for p,d,fs in files:#文件都在fs列表中
            for f in fs:
                file_name=os.path.join(p,f)#獲取文件的文件名
                file_size=os.stat(file_name).st_size#獲取文件的大小
                file_paths.append(file_name)#加入到file_path
                file_name=file_name[len(path)+1:]
                infos.append((file_size,file_name))#將文件信息加入到文件信息中
        return infos,file_paths
    
    #向服務器端發送文件信息
    def send_files_infos(my_sock,infos):
        fmt_str='Q'
        infos_bytes=pickle.dumps(infos)#對文件信息進行二進制編碼
        infos_bytes_len=len(infos_bytes)#獲取長度
        infos_len_pack=struct.pack(fmt_str,infos_bytes_len)#對長度利用struct進行二進制編碼
        my_sock.sendall(infos_len_pack)#將整個發送放到服務器端
        my_sock.sendall(infos_bytes)#發送文件信息
    
    def send_files(my_sock,file_path):#本機文件,本機文件路徑
        f = open(file_path,'rb')
        try:
            while True:
                data=f.read(1024)
                if data:
                    my_sock.sendall(data)#發送
                else:
                    break
        finally:
                    f.close()
    
    def get_bak_info(my_sock,size=7):
        info = my_sock.recv(size)
        print(info.decode('utf-8'))
    
    def start(host,port,src):
        if not os.path.exists(src):
            print('備份的目標不存在!')
            return 
        s = socket.socket()#TCP協議
        s.connect((host,port))
        path = src#獲取用戶的備份路徑
        file_infos,file_paths=get_file_info(path)#獲取要備份的文件信息和路徑
        send_files_infos(s,file_infos)#發送文件信息
        for fp in file_paths:#發送所有信息至S
            send_files(s,fp)
            print(fp)#把發送出去的文件的信息打印
            get_bak_info(s)#獲取備份的結果
        s.close()
        
        
    class MyFrame(Frame):
        #初始化構造方法
        def __init__(self,root):
            super().__init__(root)
            self.root=root
            self.grid()#布局方式網格布局
            self.remote_ip='127.0.0.1'#服務器端的IP地址默認值
            self.remote_ports=10888#默認的端口
            self.remote_ip_var=StringVar()#輸入框
            self.remote_ports_var=IntVar()
            self.bak_src_var=StringVar()
            
            self.init_components()#初始化界面方法
        
        #界面函數的實現
        def init_components(self):#初始化用戶界面的方法
            proj_name=Label(self,text='遠程備份客戶端')
            proj_name.grid(columnspan=2)#網格式布局,占用兩列
            
            serve_ip_label=Label(self,text='服務地址:')
            serve_ip_label.grid(row=1)#列是默認為0列
            
            #下拉列表,顯示服務器的地址拱用戶選擇
            self.serv_ip=Entry(self,textvariable=self.remote_ip_var)
            #設置默認的服務器的ip地址e
            self.remote_ports_var.set(self.remote_ip)
            self.serv_ip.grid(row=1,column=1)
            
            #服務端口的LABEL
            serv_port_label=Label(self,text='服務端口:')
            serv_port_label.grid(row=2) 
            #下拉列表,顯示服務器的服務端口拱用戶選擇
            self.serv_port=Entry(self,textvariable=self.remote_ports_var)
            #設置默認的服務端口
            self.remote_ports_var.set(self.remote_ports)
            #網格布局  放置指定位置
            self.serv_port.grid(row=2,column=1)
            
            #用戶備份的數據
            src_label=Label(self,text='備份的目標:')
            src_label.grid(row=3)
            
            #輸入框
            self.bak_src=Entry(self,textvariable=self.bak_src_var)
            self.bak_src.grid(row=3,column=1
                              )
            #
            self.start_serv_btn=Button(self,text='開始備份',command=self.start_send)
            self.start_serv_btn.grid(row=4)
            
            #
            self.start_exit_btn=Button(self,text='退出程序',command=self.root.destroy)
            self.start_exit_btn.grid(row=4,column=1)
    
        
        #定義啟動服務器的方法    
        def start_send(self):
            print(self.remote_ip_var.get(),self.remote_ports_var.get())
            print('start...')
            start(self.remote_ip_var.get(),int(self.remote_ports_var.get()),self.bak_src_var.get())#想服務器發送東西
            
    if __name__=='__main__':
        root=Tk()
        root.title('遠程備份客戶機')
        root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
        a = MyFrame(root)
        a.mainloop()
    View Code
  • 通過多線程實現備份服務器端
    •   單線程服務端問題
      •   啟動服務,界面停止響應
      •   一個客戶端正在備份,其他客戶端不能連接
    •   建立多線程服務器
      •   解決點擊啟動服務,頁面停止響應的問題,實現多個客戶端進行交互
      •   退出服務器:將服務線程配置為后台線程(可能使文件丟失);應用線程同步的手段退出服務(這個方法好)
      •   可壓縮備份服務
        •   客戶端發送已壓縮文件
        •   與客戶端交互基本流程
  • 通過多線程實現備份客戶端
  • 最終版
  • #coding=gbk
    from tkinter import *
    from tkinter.ttk import *
    import socket
    import threading
    import os
    import struct
    import pickle
    import threading
    
    #使用戶圖形界面和服務器退出循環變量
    SERV_RUN_FLAG=TRUE
    flag_lock = threading.Lock()
    #建立一個默認的備份目錄
    BAK_PATH=r'e:\bak'
    
    #根據指定長度來接受文件信息
    def recv_unit_data(clnt,infos_len):
        data=b'' #原來文件為空
        #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可
        if 0<infos_len<=1024:#
            data+=clnt.recv(infos_len)#接收文件
        else:#長度太長
            while  True: 
                if infos_len >1024:
                   data+=clnt.recv(1024) 
                   infos_len-=1024
                else:
                    data+=clnt.recv(infos_len)
                    break
        return data
    
    def get_files_info(clnt):
        fmt_str='Q?'#用於向服務器端傳送文件信息的大小,文件信息的壓縮選項
        headsize=struct.calcsize(fmt_str)#計算長度
        data=clnt.recv(headsize)
        infos_len,compress=struct.unpack(fmt_str, data)
        data =recv_unit_data(clnt, infos_len)
        return pickle.loads(data),compress#得到文件信息的列表
       
    def mk_math(filepath): #建立文件路徑
        paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割
        p=BAK_PATH
        for path in paths:#遍歷用戶端傳來的路徑
            p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上
            if not os.path.exists(p):#如果路徑不存在就建立路徑
                os.mkdir(p)
                
    def get_compress_size(clnt):            
        fmt_str = 'Q'#長整型
        size=struct.calcsize(fmt_str)
        data = clnt.recv(size)
        size,=struct.unpack(fmt_str,data)#得到壓縮后文件的大小
        return size
        
                
    #接收客戶端傳來文件,並且根據文件信息來進行保存備份
    def recv_file(clnt,infos_len,filepath,compress):            
        mk_math(filepath)#遍歷文件   通過路徑
        filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名
        #根據壓縮選項判斷
        if compress :
            infos_len = get_compress_size(clnt)#壓縮后文件的長度
            filepath = ''.join(os.path.splitext(filepath)[0],'.tar.gz')
        f = open(filepath,'wb+')#新建一個文件
        #接收文件
        try:
            if 0 < infos_len <=1024:
                data = clnt.recv(infos_len)
                f.write(data)
            else:
                while True:
                    if infos_len >1024:
                        data=clnt.recv(1024)
                        f.write(data)
                        infos_len-=1024
                    else:
                        data = clnt.recv(infos_len)
                        f.write(data)
                        break
        except:
            print('error')
        else:
            return True
        finally:
            f.close()
      
      #向客戶端發送失敗成功消息      
    def send_echo(clnt,res):
        if res:
            clnt.sendall(b'success')
        else:
            clnt.sendall(b'failure')
    
    
    def client_operate(client):
            #compress 可壓縮選項
            files_lst,compress=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組)
            for size,filepath in files_lst:#遍歷得到文件大小和路徑
                res = recv_file(client,size,filepath,compress)#接收所有的文件    返回備份的結果:true或者false
                send_echo(client,res)#保存成功標志,發送給客戶端
            
            client.close()#關閉客戶端
           
    #啟動服務器的方法
    def start(host,port):
        if not os.path.exists(BAK_PATH):
            os.mkdir(BAK_PATH)
        st=socket.socket() #tcp協議
        st.settimeout(1)        #為了退出的時候能夠有時間獲得共享資源的鎖,保證服務器端正常的退出;防止while中可以退出
        st.bind((host,port)) #綁定套接字
        st.listen(1) #偵聽網絡,一個客戶端連接
        #獲得serv_run_falg的訪問權
        flag_lock.acquire()
        while SERV_RUN_FLAG:#多次服務     多線程
            flag_lock.release()#釋放訪問權
            client=None
            try:
                
                client,addr=st.accept() #接收連接,建立連接
            #線程化的啟動client_operater函數       防止當前進程正在執行的時候其他線程也要進程這個服務,所以我們就每次有客戶端想要進行連接的時候,我們就創建一個線程去為每一個要求服務的東西提供服務
            #多個服務器為多個客戶端進行服務
            except socket.timeout:#超時
                pass
            if client: 
                t =threading.Thread(target=client_operate,args=(client,))
                t.start()
            flag_lock.acquire()#為了下次進入循環的時候仍然要鎖定共享變量
        st.close()
    
    class MyFrame(Frame):
        #初始化構造方法
        def __init__(self,root):
            super().__init__(root)
            self.root=root
            self.grid()#布局方式網格布局
            self.local_ip='127.0.0.1'#服務器端默認的IP地址
            self.serv_ports=[10888,20888,30888]#三個服務器的端口
            self.init_components()#初始化界面方法
        
        #界面函數的實現
        def init_components(self):#初始化用戶界面的方法
            proj_name=Label(self,text='遠程備份服務器')
            proj_name.grid(columnspan=2)#網格式布局,占用兩列
            
            serve_ip_label=Label(self,text='服務地址')
            serve_ip_label.grid(row=1)#列是默認為0列
            
            #下拉列表,顯示服務器的地址拱用戶選擇
            self.serv_ip=Combobox(self,values=self.get_ipaddr())
            #設置默認的服務器的ip地址
            self.serv_ip.set(self.local_ip)
            self.serv_ip.grid(row=1,column=1)
            
            #服務端口的LABEL
            serv_port_label=Label(self,text='服務端口')
            serv_port_label.grid(row=2) 
            #下拉列表,顯示服務器的服務端口拱用戶選擇
            self.serv_port=Combobox(self,values=self.serv_ports)
            #設置默認的服務端口
            self.serv_port.set(self.serv_ports[1])
            #網格布局  放置指定位置
            self.serv_port.grid(row=2,column=1)
            
            #啟動按鈕
            self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv)
            self.start_serv_btn.grid(row=3)
            
            #退出服務的按鈕
            self.start_exit_btn=Button(self,text='退出服務',command=self.root.destroy)
            self.start_exit_btn.grid(row=3,column=1)
            
        def get_ipaddr(self):
            #獲取本機的ip地址
            #獲取名字
            host_name=socket.gethostname()
            #獲取信息
            info=socket.gethostbyname_ex(host_name)
            info=info[2]
            info.append(self.local_ip)
        
        #定義啟動服務器的方法    
        def start_serv(self):
            #線程化運行
    #         print(self.serv_ip.get(),self.serv_port.get())
    #         start(self.serv_ip.get(),int(self.serv_port.get()))
            host = self.serv_ip.get()#獲取服務器地址
            port = int(self.serv_port.get())#獲取服務端口,轉化為整型
            serv_th= threading.Thread(target=start,args=(host,port))#建立線程化服務器
            serv_th.start()
            #當點擊啟動服務之后,我們關閉這個按鈕不讓服務再次啟動
            self.start_serv_btn.state(['disabled',])
            
    #建立一個自己的跟窗口的類,為了退出
    class MyTk(Tk):
        def destroy(self):
            global SERV_RUN_FLAG
            while True:
                if flag_lock.acquire():#獲取全局共享變量
                    SERV_RUN_FLAG=False
                    flag_lock.release()
                    break
                super().destroy()
                
    if __name__=='__main__':
        root=MyTk()
        root.title('備份服務器')
        root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
        a=MyFrame(root)
        a.mainloop()
    
    
    
    
    #coding=gbk
    from tkinter import *
    from tkinter.ttk import *
    import socket
    import os
    import pickle
    import struct
    import time
    import threading
    import tarfile,tempfile
    #獲取給出路徑的文件信息
    def get_file_info(path):
        if not path or not os.path.exists(path):
            return NONE
        files=os.walk(path)#獲取文件
        infos=[]
        file_paths=[]
        for p,d,fs in files:#文件都在fs列表中
            for f in fs:
                file_name=os.path.join(p,f)#獲取文件的文件名
                file_size=os.stat(file_name).st_size#獲取文件的大小
                file_paths.append(file_name)#加入到file_path
                file_name=file_name[len(path)+1:]
                infos.append((file_size,file_name))#將文件信息加入到文件信息中
        return infos,file_paths
    
    #向服務器端發送文件信息
    def send_files_infos(my_sock,infos,compress):
        fmt_str='Q?'
        infos_bytes=pickle.dumps(infos)#對文件信息進行二進制編碼
        infos_bytes_len=len(infos_bytes)#獲取長度
        infos_len_pack=struct.pack(fmt_str,infos_bytes_len,compress)#對長度利用struct進行二進制編碼
        my_sock.sendall(infos_len_pack)#將整個發送放到服務器端
        my_sock.sendall(infos_bytes)#發送文件信息
    
    def send_files(my_sock,file_path,compress):#本機文件,本機文件路徑
        if not compress:
            f = open(file_path,'rb')
        else:
            f = tempfile.NamedTemporaryFile()
            tar=tarfile.open(mode='w|gz',fileobj=f)
            tar.add(file_path)
            tar.close()
            f.seek(0)
            filesize = os.stat(f.name).st_size
            filesize_bytes=struct.pack('Q', filesize)
            my_sock.sendall(filesize_bytes)
        try:
            while True:
                data=f.read(1024)
                if data:
                    my_sock.sendall(data)#發送
                else:
                    break
        finally:
                    f.close()
    
    def get_bak_info(my_sock,size=7):
        info = my_sock.recv(size)
        print(info.decode('utf-8'))
    
    def start(host,port,src,compress):
        if not os.path.exists(src):
            print('備份的目標不存在!')
            return 
        s = socket.socket()#TCP協議
        s.connect((host,port))
        path = src#獲取用戶的備份路徑
        file_infos,file_paths=get_file_info(path)#獲取要備份的文件信息和路徑
        send_files_infos(s,file_infos,compress)#發送文件信息
        for fp in file_paths:#發送所有信息至S
            send_files(s,fp,compress)
            print(fp)#把發送出去的文件的信息打印
            get_bak_info(s)#獲取備份的結果
        s.close()
        
        
    class MyFrame(Frame):
        #初始化構造方法
        def __init__(self,root):
            super().__init__(root)
            self.root=root
            self.grid()#布局方式網格布局
            self.remote_ip='127.0.0.1'#服務器端的IP地址默認值
            self.remote_ports=10888#默認的端口
            self.remote_ip_var=StringVar()#輸入框
            self.remote_ports_var=IntVar()
            self.bak_src_var=StringVar()
            self.compress_var=BooleanVar()
            self.init_components()#初始化界面方法
        
        #界面函數的實現
        def init_components(self):#初始化用戶界面的方法
            proj_name=Label(self,text='遠程備份客戶端')
            proj_name.grid(columnspan=2)#網格式布局,占用兩列
            
            serve_ip_label=Label(self,text='服務地址:')
            serve_ip_label.grid(row=1)#列是默認為0列
            
            #下拉列表,顯示服務器的地址拱用戶選擇
            self.serv_ip=Entry(self,textvariable=self.remote_ip_var)
            #設置默認的服務器的ip地址e
            self.remote_ports_var.set(self.remote_ip)
            self.serv_ip.grid(row=1,column=1)
            
            #服務端口的LABEL
            serv_port_label=Label(self,text='服務端口:')
            serv_port_label.grid(row=2) 
            #下拉列表,顯示服務器的服務端口拱用戶選擇
            self.serv_port=Entry(self,textvariable=self.remote_ports_var)
            #設置默認的服務端口
            self.remote_ports_var.set(self.remote_ports)
            #網格布局  放置指定位置
            self.serv_port.grid(row=2,column=1)
            
            #用戶備份的數據
            src_label=Label(self,text='備份的目標:')
            src_label.grid(row=3)
            
            #輸入框
            self.bak_src=Entry(self,textvariable=self.bak_src_var)
            self.bak_src.grid(row=3,column=1)
            
            tar_label=Label(self,text='備份壓縮:')
            tar_label.grid(row =4 )
            
            self.compress_on=Checkbutton(self,text='開始壓縮',variable=self.compress_var,onvalue=1,offvalue=0)
            self.compress_on.grid(row=4,column=1)
            #
            self.start_serv_btn=Button(self,text='開始備份',command=self.start_send)
            self.start_serv_btn.grid(row=5)
            
            #
            self.start_exit_btn=Button(self,text='退出程序',command=self.root.destroy)
            self.start_exit_btn.grid(row=5,column=1)
    
        
        #定義啟動服務器的方法    
        def start_send(self):
    #         print(self.remote_ip_var.get(),self.remote_ports_var.get())
    #         print('start...')
            host = self.remote_ip_var.get()
            port = self.remote_ports_var.get()
            compress=self.compress_var.get()
            src=self.bak_src_var.get()
            self.bak_src_var.set('')
            t = threading.Thread(target=start,args=(host,int(port),src,compress))
            t.start()
    #         start(self.remote_ip_var.get(),int(self.remote_ports_var.get()),self.bak_src_var.get())#想服務器發送東西
            
    if __name__=='__main__':
        root=Tk()
        root.title('遠程備份客戶機')
        root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
        a = MyFrame(root)
        a.mainloop()
    View Code
  • socketserver框架的使用
    •   編寫網絡服務應用器的框架,划分了一個基本服務器框架,划分了處理請求(服務器類和請求處理器類)
    •   服務器構建
      •   建立客戶端處理類;初始化服務器類傳入相關參數;啟動服務器
    •   基本對象
      •   BasesSrver(通過繼承定制服務器類)
        •   方法介紹:serve_forever啟動服務器,
          •   handle_request()處理請求:順序:先調用get_request()獲取客戶端請求連接,verify()對客戶端請求連接進行認證,process_request():實現與客戶端進行交互
          •   finish_request()創建
        •   關閉服務器shutdown();shutdown必須在serve_forever()不同線程中調用才能關閉服務器
      •   TCPServer
        •   繼承baseserver的服務器類,可以直接初始化TCP服務器,初始化參數:(host,post)服務器服務地址;handler類:處理客戶端數據
      •   UDPServer:TCPServer的子類,可以直接初始化
      •   BaseRequestHandle
        •   setup方法:准備請求處理器
        •   handle方法:完成請求具體操作(一般只用這個)
        •   finish方法:清理setup期間的相關資源
      •   StreamRequestHandler(上面那個類的子類)使用TCP協議

        •   定制請求處理器時可以只覆蓋handle()
        •   實例屬性request代表和客戶端連接的socket可以用它實現TCP數據的接收
        • #coding=gbk
          
          import  socketserver
          import threading
          
          #關閉服務器
          def sd():
              if serv:
                  serv.shutdown()#關閉服務器
                  #shutdown必須在serve_forever()不同線程中調用才能關閉服務器
          
          class MyHdl(socketserver.StreamRequestHandler):#tcp協議的服務器
              def handle(self):#覆蓋handle方法
                  #和客戶端進行交互
                  while True:
                      data = self.request.recv(1024)#接收數據
                      print('收到數據:',data.decode('utf-8'))#解碼
                      if data==b'bye':
                          break
                      self.request.sendall(data)#將收到的數據傳回給客戶端
                  print('本次服務結束')    
                  threading.Thread(target=sd).start()
                  
          if __name__=='__main__':
                  HOST=''
                  PORT=3214
                  #實例化TCPserver
                  serv=socketserver.TCPServer((HOST,PORT),MyHdl)#服務的地址,自定義的類
                  #啟動服務器
                  serv.serve_forever()
              
          
          
          #coding=gbk
          import socket
          
          HOST = '127.0.0.1'
          PORT = 3214
          
          s=socket.socket()#tcp協議
          s.connect((HOST,PORT))
          data='你好'
          while data:
              #發送數據到服務器端
              s.sendall(data.encode('utf_8'))
              if data=='bye':
                  break
              #從服務器端接收數據
              data = s.recv(1024)
              print('收到數據;',data.decode('utf-8'))
              data=input('輸入要發送的信息:')
          s.close()
          View Code
        •   客戶端發過來的數據也可以rfile屬性來處理,rfile是一個類file對象,有緩沖,可以按行分次讀取,其方法主要有:read(n),readline()
        •   發往客戶端的數據通過wfile屬性來處理,wfile不軟沖數據,對客戶端發送的數據需要一次性寫入,寫入時用write(data)
        • #coding=gbk
          
          import socketserver
          
          class MyHdl(socketserver.StreamRequestHandler):
              def handle(self):
                  while True:
                      #從客戶端讀取一行數據
                      data = self.rfile.readline()
                      if  not data:
                          break
                      print('收到:',data.decode('utf-8'.strip('\n')))#strip去除末尾的換行符
                      #把收到數據發回給客戶端   
                      self.wfile.write(data)
                      
          if __name__=='__main__':
              HOST = ''
              PORT = 3214
              s = socketserver.TCPServer((HOST,PORT),MyHdl)
              s.serve_forever()
          
          
          
          #coding=gbk
          import socket
          
          HOST = '127.0.0.1'
          PORT = 3214
          
          s=socket.socket()#tcp協議
          s.connect((HOST,PORT))
          data='你好'
          while data:
              #為了讓服務器按行接收
              data +='\n'
              s.sendall(data.encode('utf_8'))
              data = s.recv(1024)
              print('收到數據;',data.decode('utf-8').strip('\n'))#strip  去除換行符
              data=input('輸入要發送的信息:')
          s.close()
          View Code
      •   DatagramRequestHandeler  使用udp協議
        •   略
        • #coding=gbk
          
          import socketserver
          
          class MyHdl(socketserver.DatagramRequestHandler):
              def handle(self):
                      #UDP無連接的,從客戶端獲取字符和套接字
                      data,socket = self.request
                      print('收到:',data.decode('utf-8'.strip('\n')))#strip去除末尾的換行符
                      #把收到數據發回給客戶端   
                      socket.sendto(data,self.client_address)
                      
          if __name__=='__main__':
              HOST = ''
              PORT = 3214
              s = socketserver.UDPServer((HOST,PORT),MyHdl)
              s.serve_forever()
          
          
          #coding=gbk
          import socket
          
          HOST = '127.0.0.1'
          PORT = 3214
          
          s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#UDP協議
          data='你好'
          s.sendto(data.encode('utf-8'),(HOST,PORT))
          while data != 'bye':
              data =b''
              while len(data)==0:
                  data,addr=s.recvfrom(1024)
              print('收到數據;',data.decode('utf-8'))
              data=input('輸入要發送的信息:')
              if data == '':
                  data = 'bye'
              s.sendto(data.encode('utf-8'),(HOST,PORT))
          
              
          s.close()
          View Code
  • 使用socketserver重新編寫備份服務器端
    • #coding=gbk
      from tkinter import *
      from tkinter.ttk import *
      import socket
      import threading
      import os
      import struct
      import pickle
      import threading
      import socketserver
      
      #建立一個默認的備份目錄
      BAK_PATH=r'e:\bak'
      
      #根據指定長度來接受文件信息
      def recv_unit_data(clnt,infos_len):
          data=b'' #原來文件為空
          #如果要接受的文件在0-1024之間就直接接收該文件,如果大於1024需要循環分段接收,每次只是接收1024個字節,剩下的在全部接收即可
          if 0<infos_len<=1024:#
              data+=clnt.recv(infos_len)#接收文件
          else:#長度太長
              while  True: 
                  if infos_len >1024:
                     data+=clnt.recv(1024) 
                     infos_len-=1024
                  else:
                      data+=clnt.recv(infos_len)
                      break
          return data
      
      def get_files_info(clnt):
          fmt_str='Q?'#用於向服務器端傳送文件信息的大小,文件信息的壓縮選項
          headsize=struct.calcsize(fmt_str)#計算長度
          data=clnt.recv(headsize)
          infos_len,compress=struct.unpack(fmt_str, data)
          data =recv_unit_data(clnt, infos_len)
          return pickle.loads(data),compress#得到文件信息的列表
         
      def mk_math(filepath): #建立文件路徑
          paths=filepath.split(os.path.sep)[:-1]#將文件路徑進行分割
          p=BAK_PATH
          for path in paths:#遍歷用戶端傳來的路徑
              p=os.path.join(p,path)#將保存的路徑添加到默認的路徑上
              if not os.path.exists(p):#如果路徑不存在就建立路徑
                  os.mkdir(p)
                  
      def get_compress_size(clnt):            
          fmt_str = 'Q'#長整型
          size=struct.calcsize(fmt_str)
          data = clnt.recv(size)
          size,=struct.unpack(fmt_str,data)#得到壓縮后文件的大小
          return size
          
                  
      #接收客戶端傳來文件,並且根據文件信息來進行保存備份
      def recv_file(clnt,infos_len,filepath,compress):            
          mk_math(filepath)#遍歷文件   通過路徑
          filepath = os.path.join(BAK_PATH,filepath)#服務器上的路徑的文件名
          #根據壓縮選項判斷
          if compress :
              infos_len = get_compress_size(clnt)#壓縮后文件的長度
              filepath = ''.join(os.path.splitext(filepath)[0],'.tar.gz')
          f = open(filepath,'wb+')#新建一個文件
          #接收文件
          try:
              if 0 < infos_len <=1024:
                  data = clnt.recv(infos_len)
                  f.write(data)
              else:
                  while True:
                      if infos_len >1024:
                          data=clnt.recv(1024)
                          f.write(data)
                          infos_len-=1024
                      else:
                          data = clnt.recv(infos_len)
                          f.write(data)
                          break
          except:
              print('error')
          else:
              return True
          finally:
              f.close()
        
        #向客戶端發送失敗成功消息      
      def send_echo(clnt,res):
          if res:
              clnt.sendall(b'success')
          else:
              clnt.sendall(b'failure')
      
      
      def client_operate(client):
              #compress 可壓縮選項
              files_lst,compress=get_files_info(client)#獲取客戶端要傳送的文件列表(包括文件大小和文件路徑:元組)
              for size,filepath in files_lst:#遍歷得到文件大小和路徑
                  res = recv_file(client,size,filepath,compress)#接收所有的文件    返回備份的結果:true或者false
                  send_echo(client,res)#保存成功標志,發送給客戶端
              
              client.close()#關閉客戶端
             
      #建立服務器
      class BakHdl(socketserver.StreamRequestHandler):
          def handle(self):
              client_operate(self.request)
              
      #建立服務器和啟動服務器
      def start(host,port):
          #初始化
          server = socketserver.ThreadingTCPServer((host,port),BakHdl)
          #線程的方法啟動服務器
          s = threading.Thread(target=server.serve_forever)
          s.start()
          return server
      
      
      class MyFrame(Frame):
          #初始化構造方法
          def __init__(self,root):
              super().__init__(root)
              self.root=root
              self.server = None
              self.grid()#布局方式網格布局
              self.local_ip='127.0.0.1'#服務器端默認的IP地址
              self.serv_ports=[10888,20888,30888]#三個服務器的端口
              self.init_components()#初始化界面方法
          
          #界面函數的實現
          def init_components(self):#初始化用戶界面的方法
              proj_name=Label(self,text='遠程備份服務器')
              proj_name.grid(columnspan=2)#網格式布局,占用兩列
              
              serve_ip_label=Label(self,text='服務地址')
              serve_ip_label.grid(row=1)#列是默認為0列
              
              #下拉列表,顯示服務器的地址拱用戶選擇
              self.serv_ip=Combobox(self,values=self.get_ipaddr())
              #設置默認的服務器的ip地址
              self.serv_ip.set(self.local_ip)
              self.serv_ip.grid(row=1,column=1)
              
              #服務端口的LABEL
              serv_port_label=Label(self,text='服務端口')
              serv_port_label.grid(row=2) 
              #下拉列表,顯示服務器的服務端口拱用戶選擇
              self.serv_port=Combobox(self,values=self.serv_ports)
              #設置默認的服務端口
              self.serv_port.set(self.serv_ports[1])
              #網格布局  放置指定位置
              self.serv_port.grid(row=2,column=1)
              
              #啟動按鈕
              self.start_serv_btn=Button(self,text='啟動服務',command=self.start_serv)
              self.start_serv_btn.grid(row=3)
              
              #退出服務的按鈕
              self.start_exit_btn=Button(self,text='退出服務',command=self.exit)#退出服務關閉圖形界面
              self.start_exit_btn.grid(row=3,column=1)
              
          def exit(self):
              if self.server:
                  threading.Thread(target=self.server.shutdown).start()#啟動關閉服務器的線程
              self.root.destroy()
              
          def get_ipaddr(self):
              #獲取本機的ip地址
              #獲取名字
              host_name=socket.gethostname()
              #獲取信息
              info=socket.gethostbyname_ex(host_name)
              info=info[2]
              info.append(self.local_ip)
          
          #定義啟動服務器的方法    
          def start_serv(self):
              if not os.path.exists(BAK_PATH):
                  os.mkdir(BAK_PATH)
      #         print(self.serv_ip.get(),self.serv_port.get())
      #         start(self.serv_ip.get(),int(self.serv_port.get()))
              host = self.serv_ip.get()#獲取服務器地址
              port = int(self.serv_port.get())#獲取服務端口,轉化為整型
              self.server = start(host,port)
      #         serv_th= threading.Thread(target=start,args=(host,port))#建立線程化服務器
      #         serv_th.start()
      #         #當點擊啟動服務之后,我們關閉這個按鈕不讓服務再次啟動
              self.start_serv_btn.state(['disabled',])
              
      if __name__=='__main__':
          root=Tk()
          root.title('備份服務器')
          root.resizable(FALSE, FALSE)#允許用戶更改窗體大小
          a=MyFrame(root)
          a.mainloop()
      View Code
  • 用socket實現FTP服務器和FTP客戶端
    •   FTP協議
      •   提供可靠的文件傳輸,屬於TCP/IP協議,位於應用層,采用典型的C/S模式工作,可用匿名或者指定用戶登錄,下層采用有連接可靠的TCP協議
    •   工作原理及過程
      •   FTP客戶端             FTP服務器端
            登錄服務器   <----->登錄驗證
         傳輸文件操作 <-----> 接收或者發送文件
          退出登錄結束  <----->結束FTP服務
          文件操作
    •   工作模式
      •   主動模式(PORT):數據連接有服務器端發起,客戶端建立接收服務器
      •   被動模式(PASV):和上面相反
    •   ftp最小實現:
      •   FTP命令:USER,QUIT,PORT,TYPE,MODE,STRU,RETR,STOP,NOOP
      •   命令返回碼:2XX命令成功;4XX客戶端錯誤;5XX服務錯誤
      •   傳輸方式:流模式文件傳輸,文件傳輸
    •   功能分析
      •   控制模塊
        •   接收客戶端命令,操作后返回結果;用戶可以用空用戶名或者匿名用戶名登錄;用port/pasv是服務器工作於不同模式
      •   數據傳輸模塊:與客戶端進行文件傳輸及其他數據交換
  • FTP最終代碼
    •   
      #coding=gbk
      import socket
      import socketserver
      import threading
      import time
      import os
      
      #requesthandler類
      class FTPHdl(socketserver.StreamRequestHandler):
          def __init__(self,request=None,client_address=None,server=None):
              self.coms_keys = ('QUIT','USER','NOOP','TYPE','PASV','PORT','RETP','STOR')
              #建立命令所對應的方法
              self.coms={}
              #
              self.init_coms()
              self.server
              #服務器命令模塊的端口
              self.cmd_port=21
              #數據模塊端口
              self.data_port=20
              #保存ip地址和端口號
              self.pasv_data_ip=None
              self.pasv_data_port=None
              
              self.args=None
              self.loged=False
              #模式
              self.pasv_mode=None
              super().__init__(request, client_address, server)
          
          #字典方法  命令
          def init_coms(self):
              for k in self.coms_keys:
                  self.coms[k]=getattr(self,'exe_' + k.lower())#獲取當前類的方法    exe為前綴,lower為命令的小寫
          #用於對客戶單進行處理
          def handle(self):
              while True:
                  #接收用戶端命令,讀取一行
                  cmds = self.rfile.readline()
                  if not cmds:
                      continue
                  cmds = cmds.decode('utf-8')
                  #建立命令動詞    命令行的分析
                  cmd = self.deal_args(cmds)
                  if cmd in self.coms_keys:#命令動詞是否在ftp所有的命令中
                      self.coms.get(cmd)()
                  else:
                      #返回錯誤代碼
                      self.send(500,'Invaild command.')
                      #如果命令為退出
                  if cmd == 'QUIT':
                      break
                  #分析命令信息
          def deal_args(self,cmds):
              #如果空格在命令行中   必須分開   前面是命令動詞  后面是命令參數
              if ' ' in cmds:
                  cmd,args=cmds.split(' ')
                  args = args.strip('\n').strip()#對參數進行處理
              else:
                  cmd=cmds.strip('\n')#刪除換行符
                  args=''
              if args:
                  self.args=args
              return cmd.upper()#返回命令動詞    大寫
          def exe_quit(self):
              self.send(221,'bye')
          def exe_user(self):
              user=self.args
              if user in ('','anonymous'):
                  user = 'annoymous'
                  self.loged=True
                  self.send(230,'identified!')
              else:
                  self.send(530,'Only use annoymous')
                  
          def exe_pasv(self):
              if not self.loged:
                  self.send(332,'Please login.')
                  return 
              if self.pasv_mode:
                  info = 'entering passive mode (%s)' % self.make_pasv_info()
                  self.send(227,info)
                  return
              try:
                  self.enter_pasv()
                  info = 'entering passive mode (%s)' %self.make_pasv_info()
                  self.pasv_mode=True
                  self.send(227,info)
              except Exception as e:
                  print(e)
                  self.send(500,'failure change to passive mode.')
          def enter_pasv(self):
              if self.server.data_server is None:
                  self.pasv_data_ip,self.pasv_data_port=self.server.create_data_server()
          def exe_port(self):
              self.send(500,'Do not offer port mode.')
          def exe_noop(self):
              self.send(200,'ok')
          def exe_type(self):
              self.send(200,'ok')
          def exe_retr(self):
              if not os.path.exists(self.args):
                  self.send(550,'File is not exists.')
                  return 
              client_addr=self.request.getpeername()[0]
              self.add_opr_file(client_addr,('RETR',self.args))
              self.send(150,'ok.')
          def exe_stor(self):
              client_addr=self.request.getpeername()[0]
              self.add_opr_file(client_addr,('STOP',self.args))
          def add_opr_file(self,client_addr,item):
              if client_addr in DataHdl.client_opr:
                  DataHdl.client_opr[client_addr].append(item)
              else:
                  DataHdl.client_opr[client_addr]=[item,]
                  
          def sebd(self,code,info):
              infos='%d %s\n' % (code,info)
              self.request.sendall(infos.encode('utf_8'))
              
      class MyThrTCPServ(socketserver.ThreadingTCPServer):
          def __init__(self,addr,Hdl):
              self.data_server=None
              super().__init__(addr,Hdl)
          def shutdown(self):
              if self.data_server:
                  threading.Thread(target=self.data_server.shutdown).start()
              super().shutdown()
          def create_data_server(self):
              self.data_server=socketserver.ThreadingTCPServer(('127.0.0.1',0),DataHdl)
              pasv_data_ip,pasv_data_port=self.data_server.server_address
              threading.Thread(target=self.data_server.serve_forever).start()
              return pasv_data_ip,pasv_data_port
          
      class DataHdl(socketserver.StreamRequestHandler):
          client_opr={}
          def handle(self):
              peerip=self.request.getpeername()[0]
              opr=self.get_opr_args(peerip)
              if opr:
                  if opr[0]=='RETR':
                      self.retr_file(opr[1])
                  elif opr[0]=='STOR':
                      self.stor_file(opr[1])
              self.request.close()
              
          def get_opr_args(self,peerip):
              if peerip in self.client_opr:
                  opr= self.client_opr[peerip].pop(0)
                  if not self.client_opr[peerip]:
                      self.client_opr.pop(peerip)
                  return opr
          def retr_file(self,filepath):
              f = open(filepath,'rb')
              while True:
                  data = f.read(1024)
                  if data:
                      self.request.sendall(data)
                  else:
                      break
              f.close()
          def stor_file(self,filepath):
              f=open(os.path.join('.','baket',filepath),'wb')
              while True:
                  data =self.request.recv(1024)
                  if data:
                      f.write(data)
                  else:
                      break
              f.close()
              
      if __name__=='__main__':
          server = MyThrTCPServ(('127.0.0.1',21),FTPHdl)
          threading.Thread(target=server.serve_forever).start()
          print('FTP start...')
          time.sleep(30)
          server.shutdown()
          
              
              
              
                  
          #coding=gbk
      import os
      import socket
      import threading
      import socketserver
      
      def get_file(host,port,filepath):
          s=socket.socket()
          s.connect((host,port))
          filepath=os.path.join('.','bakt',filepath)
          f=open(filepath,'wb')
          data = True
          while data:
              data=s.recv(1024)
              if data:
                  f.write(data)
          s.close()
          f.close()
      def put_file(host,port,filepath):
          s=socket.socket()
          s.connect((host,port))
          f=open(filepath,'rb')
          while true:
              data=f.read(1024)
              if data:
                  s.sendall(data)
              else:
                  break
          s.close()
          f.close()
          
      class FtpClient:
          def __init__(self,host='localhost',port=21):
              self.host=host
              self.port=port
              self.cmds=('QUIT','USER','NOOP','TYPE','PASV','PORT','RETP','STOR')
              self.linesep='\n'
              self.data_port=None
              self.loged=False
              self.sock=None
              self.pasv_mode=None
              self.pasv_host=None
              self.pasv_port=None
              
          def cmd_connect(self):
              self.sock=socket.socket()
              self.sock.connect((self.host,self.port))
              self.data_port=self.sock.getsockname()[0]
              
          def start(self):
              print('支持的命令:',self.cmds)
              self.cmd_connect()
              self.login()
              while True:
                  cmd=input('請輸入FTP命令: ')
                  if not cmd:
                      print('FTP命令不能為空。')
                      continue
                  cmd,args=self.split_args(cmd)
                  if not self.send_cmd(cmd,args):
                      continue
                  res = self.readline(self.sock)
                  print(res)
                  if cmd.stratswith('PASV') and res.startswith('227'):
                      self.pasv_mode=True
                      servinfo =res[res.index('(')+1:res.index(')')]
                      self.pasv_host='.'.join(servinfo.split(',')[:4])
                      servinfo =servinfo.split(',')[-2:]
                      self.pasv_port=256*int(servinfo[0])+int(servinfo[1])
                  if cmd.startswith('RETR'):
                      if self.pasv_mode:
                          threading.Thread(target=get_file,args=(self.pasv_host,self.pasv_port,args)).start()
                  if cmd.startswith('STOR'):
                      if self.pasv_mode:
                          threading.Thread(target=put_file,args=(self.pasv_host,self.pasv_port,args)).start()       
                  if cmd.startswith('QUIT'):
                      break
              self.sock.close()
              self.sock=None
              
          def login(self):
              if self.sock:
                  self.send_cmd('USER')
                  res=self.readline(self.sock)
                  if res.startswith('230'):
                      print('登錄成功!')
                      self.loged
                      
          def readline(self,sock):
              data = ''
              while not data.endswith(self.linesep):
                  d=sock.recv(1)
                  data +=d.decode('utf-8')
              return data
          def split_args(self,cmds):
              if ' ' in cmds:
                  cmdlsts = cmds.split(' ')
                  cmd = cmdlsts[0]
                  args=' '.join(cmdlsts[1:])
              else:
                  cmd = cmds
                  args = ''
                  return cmd.upper(),args
          def send_cmd(self,cmd,args=''):
              if self.sock:
                  if args:
                      cmd = ' '.join((cmd,args))
                  if cmd.startswith('RETR') or cmd.startswith('STOR'):
                      if self.pasv_mode is None:
                          print('Please appoint port or stor mode.')
                          return False
                      if not args:
                          return False
                  if cmd.startswith('STOR'):
                      if args:
                          if not os.path.exists(args):
                              print('File is not exists')
                              return False
                  cmd+=self.linesep
                  self.sock.sendall(cmd.encode('utf-8'))
                  return True
              
      if __name__=='__main__':
          fc=FtpClient()
          fc.start()
                          
                  
                  
                  
              
          
      View Code

       


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM