博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程之UDP编程(socket、群聊、服务端、客户端)
阅读量:4131 次
发布时间:2019-05-25

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

1. UDP编程

1.1 UDP编程流程

1.1.1 UDP服务端编程流程

  • 创建socket对象,socket.SOCK_DGRAM
  • 绑定IP和Port,bind()方法
  • 传输数据:接收数据,socket.recvfrom(bufsize[, flags]),获得一个二元组(string, address);发送数据,socket.sendto(string, address),发送给某地址某信息
  • 释放资源
import socketimport threadingevent = threading.Event()address = '0.0.0.0', 9999server = socket.socket(type=socket.SOCK_DGRAM)  # 数据报协议server.bind(address)  # 只能绑定一次while not event.is_set():    data, client_info = server.recvfrom(1024)  # 比recv安全,可以知道是谁发给你的    print(data)    # print(server.getpeername())  # 会报错OSError    msg = "{} from {}-{}".format(data.decode(), *client_info).encode()    # server.send(msg)  # 会报错,不知道发送给谁    server.sendto(msg, client_info)    print('~' * 30)event.set()server.close()

1.1.2 UDP客户端编程流程

  • 创建socket对象,socket.SOCK_DGRAM
  • 发送数据,socket.sendto(string, address)发送某地址某信息
  • 接收数据,socket.recvfrom(bufsize[, flags]),获得一个二元组(string, address)
  • 释放资源
import socketaddress = '127.0.0.1', 10001client = socket.socket(type=socket.SOCK_DGRAM)  # 数据报协议client.connect(address)  # 会解决本地address和远端地址addressprint(1, client)print(1.5, client)ms = b'111222333444555'# client.sendto(ms + b'~~~~~~~', ('127.0.0.1', 10000))#  会帮你抢一个本地地址和端口(端口是临时的),没有此句,recvfrom会报错# 可以自己玩,因为它有本地address, 它不会记录远端地址addressclient.send(ms)  # 必须和connect配合使用,什么都不解决print(2, client)data, info = client.recvfrom(1024)  # 它需要本地addressprint(data, info)# client.connect(address)  # 加了这一句,send就可以用了# while True:#     cmd = input(">>>").strip()#     ms = cmd.encode()#     # client.sendto(ms, address)#     client.send(ms)  # 此句必须和connect配合使用#     client.sendto(ms + b'~~~~~~~', ('127.0.0.1', 10000))#     client.sendto(ms + b'+++++++', ('127.0.0.1', 10001))#     data, info = client.recvfrom(1024)  # 比recv安全,可以知道是谁发给你的#     print(data)#     msg = "{} from {}".format(data.decode(), *info).encode()##     print('~' * 30)client.close()

注意:UDP是无连接协议,所有可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓

1.2 UDP编程中常用的方法

UDP编程常用方法

 

对udp编程常用方法的几点说明:send方法必须和connect方法配合使用,否则直接报错;recvfrom比recv要安全,recvfrom知道是谁发送信息给你的,它需要知道本地的address;sendto会抢一个本地的地址和端口(端口是临时的),它可以自己玩,因为它有本地的地址,它不会记录远端地址;connect会解决本地地址和远端地址。

1.3 UDP编程实现群聊

1.3.1 UDP版群聊服务端代码

import socketimport datetimeimport loggingimport threadingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatServerUdp:    # UDP群聊用的都是同一个socket,所以用字典浪费了,所有的value值都是一样的,列表可以,但是移除的话,效率低,所以考虑用集合    # 但是添加了过期删除了client的话,集合就不合适了,此时还是要用字典    def __init__(self, ip='127.0.0.1', port=9999, interval=10):  # 服务端的时间间隔一般是客户端的时间间隔的2到3倍        self.sock = socket.socket(type=socket.SOCK_DGRAM)  # 数据报文协议        self.address = ip, port        self.event = threading.Event()        # self.clients = set()        self.clients = {}        self.interval = interval    def start(self):        self.sock.bind(self.address)        threading.Thread(target=self.rec, name='rec').start()    def rec(self):        while not self.event.is_set():            data, client_info = self.sock.recvfrom(1024)            current = datetime.datetime.now().timestamp()  # float            # self.clients.add(client_info)            if data.strip() == b'^hb^':  # b'^hb^'为自己设计的                self.clients[client_info] = current                logging.info('{} hb^^^^^'.format(client_info))                continue            if data.strip() == b'quit':                # self.clients.remove(client_info)  # 注意remove相当于是按照key查找的,因为集合的值可以看做字典的key,所以比列表高效                self.clients.pop(client_info)  # 客户端主动断开连接,就把该客户的ip从字典中删除                logging.info("{} leaving".format(client_info))                continue  # 不能用break,因为总共只有一个线程,break了,while循环结束了            self.clients[client_info] = current            # 在该位子遍历字典,删除过期的clients,比较耗时,因为如果一个都没有删除,每次都要遍历字典,会很耗时,可以考虑在发送信息时,            # 遍历字典判断是否超时            logging.info(data)            msg = "{} [{}:{}] {}".format(datetime.datetime.now(), *client_info, data.decode())            keys = set()            for c, stamp in self.clients.items():  # 有线程安全问题,解决方法是加锁                if current - stamp < 0 or current - stamp > self.interval:  # 小于0应该是时间出问题了                    keys.add(c)  # 不能直接self.clients.pop(c),因为字典在遍历的过程中,其长度不能改变                else:                    self.sock.sendto(msg.encode(), c)            for c in keys:                self.clients.pop(c)    def stop(self):        self.event.set()        self.clients.clear()        self.sock.close()csu = ChatServerUdp()csu.start()while True:    cmd = input('>>>').strip()    if cmd == 'quit':        csu.stop()        break    logging.info(threading.enumerate())

1.3.2 UDP版群聊客户端代码

import socketimport loggingimport threadingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatClientUdp:    def __init__(self, ip='127.0.0.1', port=9999, interval=5):        self.sock = socket.socket(type=socket.SOCK_DGRAM)        self.r_address = ip, port        self.event = threading.Event()        self.interval = interval    def start(self):        self.sock.connect(self.r_address)        self.send('{} hello'.format(self.sock.getsockname()))        threading.Thread(target=self.heart_beat, name='heartbeat', daemon=True).start()        threading.Thread(target=self.rec, name='rec').start()    def heart_beat(self):        while not self.event.wait(self.interval):            # self.sock.send(b'^hb^')  # 发送心跳包,记录最后一次发送的时间,此句比较浪费时间,换成下面的语句            self.send('^hb^')    def rec(self):        while not self.event.is_set():            data = self.sock.recv(1024)            logging.info(data)    def send(self, msg: str):        self.sock.sendto(msg.encode(), self.r_address)    def stop(self):        self.event.set()        self.send('quit')        self.sock.close()ccu = ChatClientUdp()ccu.start()while True:    line = input('>>>').strip()    if line == 'quit':        ccu.stop()        break    ccu.send(line)    logging.info(threading.enumerate())

心跳机制:

  1. 一般来说是客户端定时发往服务端的,服务端并不需要ack回复客户端,只要记录该客户端还活着就可以了
  2. 如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息。这种实现较为复杂,用的较少。
  3. 也可以双向都发心跳的,用的更少

1.4 UDP协议应用

UDP协议是无连接协议,它基于以下假设:(User Datagram Protocol)

  • 网路足够好
  • 消息不会丢包
  • 包不会乱序

但是,即使是局域网,也不能保证不丢包,而且包的到达不一定有序。

应用场景:视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像,听不清话语,可以重新发话语来 解决。海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析。一般来说,UDP性能优于TCP,但是可靠性要求高的场合还是要选择TCP协议。

 

转载地址:http://lpfvi.baihongyu.com/

你可能感兴趣的文章
HTML 5 <progress>进度标签
查看>>
html <code> <pre>标签
查看>>
HTML iframe 用法小总结
查看>>
CSS font-smoothing
查看>>
CSS box-reflect倒影效果
查看>>
JavaScript replace() 方法
查看>>
CSS ::selection伪类选择器
查看>>
dataTransfer 对象
查看>>
jQuery 事件 - animate(),change(),stop(),finish()
查看>>
CSS display 属性
查看>>
30个最常用css选择器解析
查看>>
CSS 滤镜 -webkit-filter 的介绍和使用
查看>>
JavaScript document.cookie使用
查看>>
网页头部<meta name="Robots" 用法 及<meta>系列其他用法.
查看>>
HTML Audio/Video DOM timeupdate 事件,play()方法
查看>>
jquery中 html() text() val() innerText总结
查看>>
JavaScript--数据类型
查看>>
JavaScript toFixed() 方法
查看>>
HTML 5 canvas globalCompositeOperation 属性
查看>>
HTML 5 canvas globalAlpha 属性
查看>>