Socket网络编程-UDP编程

            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协议。
原文地址:https://www.cnblogs.com/yinzhengjie/p/11974525.html