Python之路(第三十二篇) 网络编程:udp套接字简单文件传输

Posted Nicholas--Altshuler

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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系统:如果一端断开了链接,那另外一端的链接也跟着出错,recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)

  • 对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着出错,recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就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()

  

以上是关于Python之路(第三十二篇) 网络编程:udp套接字简单文件传输的主要内容,如果未能解决你的问题,请参考以下文章

开始写游戏 --- 第三十二篇

第三十二篇iOS 10开发

第三十二篇 vue

第三十二篇-NavigationView导航抽屉的使用

第三十二篇直播项目开发

第三十二篇 玩转数据结构——AVL树