zoukankan      html  css  js  c++  java
  • Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输

    一、UDP套接字

    服务端

      # udp是无链接的,先启动哪一端都不会报错
      # udp没有链接,与tcp相比没有链接循环,只有通讯循环
      server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)   #创建一个服务器的套接字
      server.bind()       #绑定服务器套接字
      inf_loop:       #服务器无限循环
          cs = server.recvfrom()/server.sendto() # 对话(接收与发送)
      server.close()                         # 关闭服务器套接字
    

      

    客户端

      
      client = socket()   # 创建客户套接字
      comm_loop:      # 通讯循环
          client.sendto()/client.recvfrom()   # 对话(发送/接收)
      client.close()                      # 关闭客户套接字
     
    

      

     简单例子

    服务端

      import socket
      ​
      ip_port = ('127.0.0.1',8081)
      server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
      server.bind(ip_port)
      while True:
          print('udp服务端开始运行了')
          data,addr = server.recvfrom(1024)
          print(data.decode('utf-8'))
          msg = input("请输入").strip()
          server.sendto(msg.encode("utf-8"),addr)
    

      

    客户端

      
      import socket
      ​
      ip_port = ('127.0.0.1', 8081)
      server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      while True:
          print('udp客户端开始运行了')
          msg = input("请输入").strip()
          server.sendto(msg.encode("utf-8"), ip_port)
          data, addr = server.recvfrom(1024)
          print(data.decode("utf-8"))
    

      

    注意:udp 可以发空 数据报协议 说是发空,其实不是空 ,还有一个IP 端口的信息,发空时 带个端口信息,

    tcp:不是一一对应的,udp:是一一对应的 数据报完整的

    用upd做一个ntp时间服务器

    服务端

      
      import socket
      import time
      ​
      ip_port = ("127.0.0.1",8080)
      buffer_size = 1024
      ntp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
      ntp_server.bind(ip_port)
      ​
      while True:
          data,addr = ntp_server.recvfrom(buffer_size)
          print("收到客户端的命令是",data.decode("utf-8"))
          if not data:
              fmt = "%Y-%m-%d %X"
          else:
              fmt = data.decode("utf-8")
          time_now = time.strftime(fmt,time.localtime())
          ntp_server.sendto(time_now.encode("utf-8"),addr)
    

      

    客户端

      
      import socket
      ​
      ip_port = ("127.0.0.1",8080)
      buffer_size = 1024
      ntp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
      ​
      while True:
          msg = input(">>>")
          ntp_client.sendto(msg.encode("utf-8"),ip_port)
          recv_msg,addr = ntp_client.recvfrom(buffer_size)
          print(recv_msg.decode("utf-8"))
    

      

    基于udp简单实现QQ聊天

    服务端

      
      from socket import *
      udp_server= socket(AF_INET,SOCK_DGRAM)
      udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
      udp_server.bind(('127.0.0.1',8080))
      print('start running...')
      ​
      while True:
          qq_msg,addr = udp_server.recvfrom(1024)
          print('来自[%s:%s]的一条消息:33[44m%s33[0m'%(addr[0],addr[1],qq_msg.decode('utf-8')))
          back_msg = input('回复消息:>>').strip()
          udp_server.sendto(back_msg.encode('utf-8'),addr)
      udp_server.close()
    

      

    客户端

      
      from socket import *
      udp_client = socket(AF_INET,SOCK_DGRAM)
      qq_name_dic = {
          'pony':('127.0.0.1',8080),
          'jack':('127.0.0.1',8080),
          'charles':('127.0.0.1',8080),
          'nick':('127.0.0.1',8080)
      }
      while True:
          print("QQ名单列表:")
          for i in qq_name_dic.keys():
              print(i)
          qq_name = input('请输入聊天对象:>>').strip()
          if qq_name not in qq_name_dic: continue
          while True:
              msg = input('请输入消息,回车发送:').strip()
              if msg=='quit':break
              if not msg or not qq_name or qq_name not in qq_name_dic:continue
              udp_client.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
              back_msg,addr = udp_client.recvfrom(1024)
              print('来自[%s:%s]的一条消息:33[41m%s33[0m'%(addr[0],addr[1],back_msg.decode('utf-8')))
      udp_client.close()
    

      

    二、tcp与udp对比

    tcp基于链接通信,数据流式协议

    • 基于链接,则需要listen(backlog),指定连接池的大小

    • 基于链接,必须先运行的服务端,然后客户端发起链接请求

    • 对于mac/linux系统:如果客户端断开了链接,那服务端的链接recv将会阻塞,通讯循环收到的是一直空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)

    • 对于windows系统:如果一端断开了链接,那另外一端的链接也跟着出错,(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)

    • 相对于upd传输速度慢

    • 流式协议 会粘包 不可以发空 send recv 不是 一 一对应

    • tcp适用于:

      • 数据一定要可靠

      • 远程执行命令

      • 下载文件

    udp无链接,数据报式协议

    • 无链接,因而无需listen(backlog),更加没有什么链接池

    • 无链接,udp的sendto不用管是否有一个正在运行的服务端可以一直发消息,只不过数据可能会丢失

    • recvfrom收的数据小于sendto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错

    • 只有sendto发送数据没有recvfrom收数据,数据丢失

    • 数据报协议 不会粘包 可以发空 sendto recvfrom 一 一 对应 数据报协议 数据不安全 有可能发送数据 > 1024 或者网络网络异常 数据没了

    • udp适用于

      • QQ

      • 查询操作 eg: ntp时间服务器 dns服务器(查域名,转ip) 能保证查询效率高,数据虽然不可靠,传输过程中可能会发生数据丢失

    三、基于socket实现文件网络传输

    简单版本

    服务端

      
      import socket
      import os
      import hashlib
      import json
      import struct
      ​
      ip_port = ("127.0.0.1",9001)
      back_log = 5
      buffer_size = 1024
      base_path = os.path.dirname(os.path.abspath(__file__))
      share_path =os.path.join(base_path,"share")
      ​
      ftp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      ftp_server.bind(ip_port)
      ftp_server.listen(back_log)
      ​
      def creat_md5(file):
          md5_value = hashlib.md5()
          with open(file,"rb") as f:
              while True:
                  data = f.read(1024)
                  if not data:
                      break
                  md5_value.update(data)
          return md5_value.hexdigest()
      ​
      ​
      while True:
          print("FTP服务器开始运行啦!")
          conn,address = ftp_server.accept()
          while True:
              try:
                  # 第一步:收命令
                  res = conn.recv(8096)  #"get a.txt"
                  if not res: continue
                  # 第二步:解析命令, 提取相应的命令参数
                  cmds = res.decode("utf-8").split()
                  file_name = cmds[1]
                  if cmds[0] == "get":
      ​
                      file_path = os.path.join(share_path,file_name)
                      file_md5 = creat_md5(file_path)
                      file_size = os.path.getsize(file_path)
                      #第三步:以读的方式打开文件,读取文件内容 发送给客户端,
                      # 1、先自制报头,传递文件的相关信息
                      header_dic = {
                          "filename":file_name,
                          "filemd5":file_md5,
                          "filesize":file_size
                          }
                      header_json = json.dumps(header_dic).encode("utf-8")
                      header_length = len(header_json)
                      header_struct = struct.pack("i",header_length)
                      # 2、发送报头的长度
                      conn.send(header_struct)
                      # 3、发送报头,传递文件的各种信息
                      conn.send(header_json)
                      # 4、打开文件,读取内容,一行一行的发送读取的内容给客户端
                      with open(file_path,"rb") as f:
                          for line in f:
                              conn.send(line)
      ​
              except Exception as e:
                  print(e)
                  break
          conn.close()
    

      

    客户端

      
      import socket
      import os
      import struct
      import json
      import time
      ​
      ip_port = ("127.0.0.1", 9001)
      buffer_size = 1024
      base_path = os.path.dirname(os.path.abspath(__file__))
      download_path = os.path.join(base_path,"download")
      ​
      ftp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      ftp_client.connect(ip_port)
      ​
      while True:
          # 第一步:写命令,发送命令给服务端
          cmd = input("请输入命令: ")
          if not cmd: continue
          if cmd == "quit": break
          ftp_client.send(cmd.encode("utf-8"))
          # 第二步:收取自制报头的长度
          header_struct = ftp_client.recv(4)
          header_length = struct.unpack("i", header_struct)[0]
          print("报头长度",header_length)
          # 第三步:收取自制报头的信息
          header_json = ftp_client.recv(header_length).decode("utf-8")
          header_dic = json.loads(header_json)
          print("报头字典",header_dic)
          # 第四步:根据报头信息拼出文件的各种信息
          file_name = header_dic["filename"]
          file_md5 = header_dic["filemd5"]
          file_size = header_dic["filesize"]
          file_download_path = os.path.join(download_path,file_name)
          # 第五步:以写的方式打开一个新文件,接收服务端发来的文件内容写入客户的新文件
          with open(file_download_path,"wb") as f:
              data_size = 0
              start_time = time.perf_counter()
              while data_size < file_size:
                  line = ftp_client.recv(buffer_size)
                  f.write(line)
                  data_size = data_size + len(line)
                  # print("已经写入数据",data_size)
                  download_percent = int((data_size/file_size)*100)
                  # print("百分比",download_percent)
                  a = "*" * download_percent
                  # print(a)
                  b = "." * (100 - download_percent)
                  # print(b)
                  c = (data_size/file_size)*100
                  during_time = time.perf_counter() - start_time
                  print("
    {:3.0f}%[{}-{}]共计用时:{:.3f}s".format(c,a,b,during_time),end="")
                  # sys.stdout.flush()
              print("
    " + "执行结束")
      ftp_client.close()
    

      

    基于类写的文件传输

    服务端

      
      import socket
      import os
      import struct
      import pickle
      ​
      ​
      class TCPServer:
          address_family = socket.AF_INET
          socket_type = socket.SOCK_STREAM
          listen_count = 5
          max_recv_bytes = 8192
          coding = 'utf-8'
          allow_reuse_address = False
          # 下载的文件存放路径
          down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
          # 上传的文件存放路径
          upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'upload')
      ​
          def __init__(self,server_address,bind_and_listen=True):
              self.server_address = server_address
              self.socket = socket.socket(self.address_family,self.socket_type)
      ​
              if bind_and_listen:
                  try:
                      self.server_bind()
                      self.server_listen()
                  except Exception:
                      self.server_close()
      ​
          def server_bind(self):
              if self.allow_reuse_address: #重用ip和端口
                  self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
              self.socket.bind(self.server_address)
      ​
          def server_listen(self):
              self.socket.listen(self.listen_count)
      ​
          def server_close(self):
              self.socket.close()
      ​
          def server_accept(self):
              return self.socket.accept()
      ​
          def conn_close(self,conn):
              conn.close()
      ​
          def run(self):
              print('starting...')
              while True:
                  self.conn,self.client_addr = self.server_accept()
                  print(self.client_addr)
                  while True:
                      try:
                          res = self.conn.recv(self.max_recv_bytes)
                          if not res:continue
                          cmds = res.decode(self.coding).split()
                          if hasattr(self,cmds[0]):
                              func = getattr(self,cmds[0])
                              func(cmds)
                      except Exception:
                          break
                  self.conn_close(self.conn)
      ​
          def get(self,cmds):
              """ 下载
              1.找到下载的文件
              2.发送 header_size
              3.发送 header_bytes file_size
              4.读文件 rb 发送 send(line)
              5.若文件不存在,发送0 client提示:文件不存在
              :param cmds: 下载的文件 eg:['get','a.txt']
              :return:
              """
              filename = cmds[1]
              file_path = os.path.join(self.down_filepath, filename)
              if os.path.isfile(file_path):
                  header = {
                      'filename': filename,
                      'md5': 'xxxxxx',
                      'file_size': os.path.getsize(file_path)
                  }
                  header_bytes = pickle.dumps(header) #直接用pickle转为bytes,不用json+encode转为bytes
                  self.conn.send(struct.pack('i', len(header_bytes)))
                  self.conn.send(header_bytes)
                  with open(file_path, 'rb') as f:
                      for line in f:
                          self.conn.send(line)
              else:
                  self.conn.send(struct.pack('i', 0))
      ​
          def put(self,cmds):
              """ 上传
              1.接收4个bytes  得到文件的 header_size
              2.根据 header_size  得到 header_bytes  header_dic
              3.根据 header_dic  得到 file_size
              3.以写的形式 打开文件 f.write()
              :param cmds: 下载的文件 eg:['put','a.txt']
              :return:
              """
              obj = self.conn.recv(4)
              header_size = struct.unpack('i', obj)[0]
              header_bytes = self.conn.recv(header_size)
              header_dic = pickle.loads(header_bytes)
              print(header_dic)
              file_size = header_dic['file_size']
              filename = header_dic['filename']
      ​
              with open('%s/%s' % (self.upload_filepath, filename), 'wb') as f:
                  recv_size = 0
                  while recv_size < file_size:
                      res = self.conn.recv(self.max_recv_bytes)
                      f.write(res)
                      recv_size += len(res)
      ​
      ​
      tcp_server = TCPServer(('127.0.0.1',8080))
      tcp_server.run()
      tcp_server.server_close()
    

      

    客户端

    import socket
    import struct
    import pickle
    import os
    
    
    class FTPClient:
        address_family = socket.AF_INET
        socket_type = socket.SOCK_STREAM
        # 下载的文件存放路径
        down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'download')
        # 上传的文件存放路径
        upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'share')
        coding = 'utf-8'
        max_recv_bytes = 8192
    
        def __init__(self, server_address, connect=True):
            self.server_address = server_address
            self.socket = socket.socket(self.address_family, self.socket_type)
            if connect:
                try:
                    self.client_connect()
                except Exception:
                    self.client_close()
    
        def client_connect(self):
            self.socket.connect(self.server_address)
    
        def client_close(self):
            self.socket.close()
    
        def run(self):
            while True:
                # get a.txt 下载   put a.txt 上传
                msg = input(">>>:").strip()
                if not msg: continue
                self.socket.send(msg.encode(self.coding))
                cmds = msg.split()
                if hasattr(self,cmds[0]):
                    func = getattr(self,cmds[0])
                    func(cmds)
    
        def get(self, cmds):
            """ 下载
            1.得到 header_size
            2.得到 header_types header_dic
            3.得到 file_size file_name
            4.以写的形式 打开文件
            :param cmds: 下载的内容 eg: cmds = ['get','a.txt']
            :return:
            """
            obj = self.socket.recv(4)
            header_size = struct.unpack('i', obj)[0]
            if header_size == 0:
                print('文件不存在')
            else:
                header_types = self.socket.recv(header_size)
                header_dic = pickle.loads(header_types)
                print(header_dic)
                file_size = header_dic['file_size']
                filename = header_dic['filename']
    
                with open('%s/%s' % (self.down_filepath, filename), 'wb') as f:
                    recv_size = 0
                    while recv_size < file_size:
                        res = self.socket.recv(self.max_recv_bytes)
                        f.write(res)
                        recv_size += len(res)
                        print('总大小:%s 已下载:%s' % (file_size, recv_size))
                    else:
                        print('下载成功!')
    
        def put(self, cmds):
            """ 上传
            1.查看上传的文件是否存在
            2.上传文件 header_size
            3.上传文件 header_bytes
            4.以读的形式 打开文件 send(line)
            :param cmds: 上传的内容 eg: cmds = ['put','a.txt']
            :return:
            """
            filename = cmds[1]
            file_path = os.path.join(self.upload_filepath, filename)
            if os.path.isfile(file_path):
                file_size = os.path.getsize(file_path)
                header = {
                    'filename': os.path.basename(filename),
                    'md5': 'xxxxxx',
                    'file_size': file_size
                }
                header_bytes = pickle.dumps(header)
                self.socket.send(struct.pack('i', len(header_bytes)))
                self.socket.send(header_bytes)
    
                with open(file_path, 'rb') as f:
                    send_bytes = b''
                    for line in f:
                        self.socket.send(line)
                        send_bytes += line
                        print('总大小:%s 已上传:%s' % (file_size, len(send_bytes)))
                    else:
                        print('上传成功!')
            else:
                print('文件不存在')
    
    
    ftp_client = FTPClient(('127.0.0.1',8080))
    ftp_client.run()
    ftp_client.client_close()
    

      

  • 相关阅读:
    定理环境
    tcolorbox 宏包简明教程
    【专访】南科大数学系何炳生教授——四十年上下求索
    研究生导师为什么喜欢问学生家境?
    高德纳谈《具体数学》的诞生
    剑桥大学
    线性代数
    APPCAN的mas服务报错
    github删除仓库
    Angular2入门教程-2 实现TodoList App
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/9827725.html
Copyright © 2011-2022 走看看