zoukankan      html  css  js  c++  java
  • 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 :
            ',data.decode('utf-8'))#解码打印收到的数据
                    data=input('please input string:
            ')
            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 :
            ',data.decode('utf-8'))
                
                    data=input('please input string:
            ')
            
            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:ak'
            
            #根据指定长度来接受文件信息
            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:ak'
      
      #根据指定长度来接受文件信息
      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:ak'
      
      #根据指定长度来接受文件信息
      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('
            ')))#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 +='
            '
                s.sendall(data.encode('utf_8'))
                data = s.recv(1024)
                print('收到数据;',data.decode('utf-8').strip('
            '))#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('
            ')))#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:ak'
        
        #根据指定长度来接受文件信息
        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('
        ').strip()#对参数进行处理
                else:
                    cmd=cmds.strip('
        ')#删除换行符
                    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
        ' % (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='
        '
                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
  • 相关阅读:

    卖了5个月水果之后再看互联网思维
    实时流式计算框架Storm 0.9.0发布通知(中文版)
    Top100Summit全球案例研究峰会第一天总结——云计算和大数据
    [若有所悟]提升工作效率的一些小技巧——资源管理器篇
    [若有所悟]主持会议的八大戒条
    惊魂36小时,一次生产事故,动态磁盘删除卷分区丢失,数据恢复案例实战
    一个不用的域名到期导致整个服务器上其他网站都无法访问的线上事故
    guava cache大量的WARN日志的问题分析
    IDEA下运行 mybatis报错 Parameter 'arg0' not found. Available parameters are [autoRecharge, id, param1, param2]
  • 原文地址:https://www.cnblogs.com/Kobe10/p/5714622.html
Copyright © 2011-2022 走看看