博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Python之路(第三十二篇) 网络编程:udp套接字、简单文件传输
阅读量:6084 次
发布时间:2019-06-20

本文共 15147 字,大约阅读时间需要 50 分钟。

一、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]的一条消息:\033[44m%s\033[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]的一条消息:\033[41m%s\033[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("\r{:3.0f}%[{}-{}]共计用时:{:.3f}s".format(c,a,b,during_time),end="")              # sys.stdout.flush()          print("\n" + "执行结束")  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 socketimport structimport pickleimport osclass 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()

  

转载于:https://www.cnblogs.com/Nicholas0707/p/9827725.html

你可能感兴趣的文章
一点IT"边缘化"的人的思考
查看>>
WPF 降低.net framework到4.0
查看>>
搭建一个通用的脚手架
查看>>
开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
查看>>
开源磁盘加密软件VeraCrypt教程
查看>>
本地vs云:大数据厮杀的最终幸存者会是谁?
查看>>
阿里云公共镜像、自定义镜像、共享镜像和镜像市场的区别 ...
查看>>
shadowtunnel v1.7 发布:新增上级负载均衡支持独立密码
查看>>
Java线程:什么是线程
查看>>
mysql5.7 创建一个超级管理员
查看>>
【框架整合】Maven-SpringMVC3.X+Spring3.X+MyBatis3-日志、JSON解析、表关联查询等均已配置好...
查看>>
要想成为高级Java程序员需要具备哪些知识呢?
查看>>
带着问题去学习--Nginx配置解析(一)
查看>>
onix-文件系统
查看>>
java.io.Serializable浅析
查看>>
我的友情链接
查看>>
多线程之线程池任务管理通用模板
查看>>
CSS3让长单词与URL地址自动换行——word-wrap属性
查看>>
CodeForces 580B Kefa and Company
查看>>
开发规范浅谈
查看>>