Socket网络编程-UDP编程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.UDP编程概述
1>.UDP服务端编程流程
创建socket对象。socket.SOCK_DGRAM
绑定IP和Port,bind()方法
传输数据
接收数据,socket.recvfrom(bufsize[, flags]),获得一个二元组(string, address)
发送数据,socket.sendto(string, address) 发给某地址某信息
释放资源
2>.UDP客户端编程流程
创建socket对象。socket.SOCK_DGRAM
发送数据,socket.sendto(string, address)发给某地址某信息
接收数据,socket.recvfrom(bufsize[, flags]),获得一个二元组(string, address)
释放资源
3>.UDP编程中常用的方法
bind方法
可以指定本地地址和端口laddr,会立即占用
connect方法
可以立即占用本地地址和端口laddr,填充远端地址和端口raddr
sendto方法
可以立即占用本地地址和端口laddr,并把数据发往指定远端。只有有了本地绑定端口,sendto就可以向任何远端发送数据
send方法
需要和connect方法配合,可以使用已经从本地端口把数据发往raddr指定的远端
recv方法
要求一定要在占用了本地端口后,返回接收的数据
recvfrom方法
要求一定要占用了本地端口后,返回接收的数据和对端地址的二元组
4>.心跳机制
增加心跳heartbeat机制或ack机制。这些机制同样可以用在TCP通信的时候。 心跳,就是一端定时发往另一端的信息,一般每次数据越少越好。心跳时间间隔约定好就行。 ack即响应,一端收到另一端的消息后返回的确认信息。 心跳机制实现策略: 1.一般来说是客户端定时发往服务端的,服务端并不需要ack回复客户端,只需要记录该客户端还活 着就行了。当然服务端也可响应客户端 2.如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到某客户端的ack响应,服务端移除其信息。这种实现较为复杂,用的较少 3.也可以双向都发心跳的,用的更少
二.UDP版群聊案例
1>.UDP版群聊服务端代码
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import socket 7 import threading 8 import datetime 9 import logging 10 11 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" 12 logging.basicConfig(format=FORMAT, level=logging.INFO) 13 14 15 #在服务器端代码中使用第一种心跳机制改进 16 class ChatUDPServer: 17 def __init__(self, ip='127.0.0.1', port=6688, interval=10): 18 self.addr = (ip, port) 19 self.sock = socket.socket(type=socket.SOCK_DGRAM) 20 self.clients = {} # 记录客户端,改为字典 21 self.event = threading.Event() 22 self.interval = interval # 默认10秒,超时就要移除对应的客户端 23 24 def start(self): 25 self.sock.bind(self.addr) # 立即绑定 26 # 启动线程 27 threading.Thread(target=self.recv, name='recv').start() 28 29 def recv(self): 30 removed = set() # 超时的 31 while not self.event.is_set(): 32 data, raddr = self.sock.recvfrom(1024) # 阻塞接收数据 33 current = datetime.datetime.now().timestamp() # float 34 if data.strip() == b'^hb^': # 心跳信息 35 print('^^^^^^^^hb', raddr) 36 self.clients[raddr] = current 37 continue 38 elif data.strip() == b'quit': 39 #有可能发来数据的不在clients中 40 self.clients.pop(raddr, None) 41 logging.info('{} leaving'.format(raddr)) 42 continue 43 44 #有信息来就更新时间 45 #什么时候比较心跳时间呢? 发送信息的时候,反正要遍历一遍 46 self.clients[raddr] = current 47 msg = '{}. from {}:{}'.format(data.decode(), *raddr) 48 logging.info(msg) 49 msg = msg.encode() 50 51 for c, stamp in self.clients.items(): 52 if current - stamp > self.interval: 53 removed.add(c) 54 else: 55 self.sock.sendto(msg, c) # 不保证对方能够收到 56 57 for c in removed: 58 self.clients.pop(c) 59 removed.clear() 60 61 def stop(self): 62 self.event.set() 63 self.clients.clear() 64 self.sock.close() 65 66 def main(): 67 server = ChatUDPServer() 68 server.start() 69 70 while True: 71 cmd = input(">>> ") 72 if cmd.strip() == 'quit': 73 server.stop() 74 break 75 logging.info(threading.enumerate()) 76 logging.info(server.clients) 77 78 if __name__ == '__main__': 79 main()
2>.UDP版群聊客户端代码
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import socket 8 import logging 9 10 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" 11 logging.basicConfig(format=FORMAT, level=logging.INFO) 12 13 14 15 #增加定时发送心跳代码 16 class ChatUdpClient: 17 def __init__(self, rip='127.0.0.1', rport=6688): 18 self.sock = socket.socket(type=socket.SOCK_DGRAM) 19 self.raddr = (rip, rport) 20 self.event = threading.Event() 21 22 def start(self): 23 self.sock.connect(self.raddr) # 占用本地地址和端口,设置远端地址和端口 24 threading.Thread(target=self._sendhb, name='heartbeat', daemon=True).start() 25 threading.Thread(target=self.recv, name='recv').start() 26 27 def _sendhb(self): # 心跳 28 while not self.event.wait(5): 29 self.send('^hb^') 30 31 def recv(self): 32 while not self.event.is_set(): 33 data, raddr = self.sock.recvfrom(1024) 34 msg = '{}. from {}:{}'.format(data.decode(), *raddr) 35 logging.info(msg) 36 37 def send(self, msg:str): 38 self.sock.send(msg.encode()) 39 # self.sock.sendto(msg.encode(), self.raddr) 40 41 def stop(self): 42 self.event.set() 43 self.send('quit') #通知服务端退出 44 self.sock.close() 45 46 47 def main(): 48 client1 = ChatUdpClient() 49 client2 = ChatUdpClient() 50 client1.start() 51 client2.start() 52 print(client1.sock) 53 print(client2.sock) 54 55 while True: 56 cmd = input('Input your words >> ') 57 if cmd.strip() == 'quit': 58 client1.stop() 59 client2.stop() 60 break 61 client1.send(cmd) 62 client2.send(cmd) 63 64 65 if __name__ == '__main__': 66 main()
三.UDP协议应用
UDP是无连接协议,它基于以下假设:
网络足够好
消息不会丢包
包不会乱序
但是,即使是在局域网,也不能保证不丢包,而且包的到达不一定有序。
应用场景:
视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清话语,可以重新发话语来解 决。
海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。 DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析。
一般来说,UDP性能优于TCP,但是可靠性要求高的场合的还是要选择TCP协议。 DNS使用的就是UDP协议和TCP协议。