我是靠谱客的博主 机灵白云,这篇文章主要介绍python -- 网络编程(UDP编程)UDP服务端编程UDP协议应用,现在分享给大家,希望可以做个参考。

UDP编程

  • UDP服务端编程
    • UDP服务端编程流程
    • UDP客户端编程流程
    • UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用
    • 练习 --UDP版群聊
      • UDP版群聊服务端代码
      • UDP版群聊客户端代码
    • 代码改进
      • 服务端代码改进
      • 心跳机制
      • 客户端代码改进
  • UDP协议应用
    • 应用场景

测试命令

复制代码
1
2
3
4
5
6
7
windows 查找udp是否启动端口 > netstat -anp udp | find "9999" > net stat -anbp udp | findstr 9999 linux下发给服务端数据 $ echo "123abc" | nc -u 127.0.0.1 9999

UDP服务端编程

UDP服务端编程流程

在这里插入图片描述

  • 创建socket对象。type=socket.Sock_DGRAM
  • 绑定IP和Port,bing()方法
  • 传输数据
    • 接受数据,socket.recvform(bufsize[flags]),获得一个二元组(string,address)
    • 发送数据,socket.sendto(string, address)发给某地址某信息
  • 释放资源
复制代码
1
2
3
4
5
6
7
8
9
10
11
import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bing(('0.0.0.0', 9999)) # 立即绑定一个udp端口 data = server.recv(1024) # 阻塞等待数据 data = server.recvfrom(1024) # 阻塞等待数据(value, (ip, port)) server.sendto(b'hello', ('192.168.142.1', 10000)) server.close()

UDP客户端编程流程

  • 创建socket对象。type=socket.Sock_DGRAM
  • 发送数据,socket.sendto(string, address)发给某地址某信息
  • 接受数据,socket.recvform(bufsize[flags]),获得一个二元组(string,address)
  • 释放资源
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
import socket client = socket.socket(type=socket.SOCK_DGRAM) raddr = ('192.168.142.1', 9999) client.connect(raddr) client.sendto(b'hello', raddr) client.send(b'hello') data = client.recvfrom(1024) # 阻塞等待数据(value, (ip, port)) data = client.recv(1024) # 阻塞等待数据 client.close()
  • 注意:UDP是无连接协议,所以可以只有任何一端
    例如客户端数据发往服务端,服务端存在与否无所谓

UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用

  • UDP的socket对象创建后,是没有占用本地地址和端口的
方法说明
bind方法可以指定本地地址和端口laddr,会立即占用
connect方法可以立即占用本地地址和端口laddr,填充远程地址和端口raddr
sendto方法可以立即占用本地地址和端口laddr,并把数据发往指定远端。
只有有了本地绑定端口,sendto就可以向任何远端发送数据
send方法需要和connect方法配合, 可以使用已经从本地端口把数据发往raddr指定的远端
recv方法要求一定要在占用了本地端口后,返回接受的数据
recvfrom方法要求一定要占用了本地端口后,返回接受的数据和对端地址的二元祖

练习 --UDP版群聊

UDP版群聊服务端代码

  • 服务端类的基本架构
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
class ChatUDPServer: def __init__(self, ip='127.0.0.1', port=9999): self.addr = (ip, port) self.sock = socket.socket(type=socket.SOCK_DGRAM) def start(self): self.sock.bind(self.addr) # 立即绑定 self.sock.recvfrom(1024) # 阻塞接受数据 def stop(self): self.sock.close()
  • 完整版版代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import socket import threading import datetime import logging FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatUDPServer: def __init__(self, ip='127.0.0.1', port=9999): self.addr = (ip, port) self.sock = socket.socket(type=socket.SOCK_DGRAM) self.clients = set() # 记录客户端 self.event = threading.Event() def start(self): self.sock.bind(self.addr) # 立即绑定 # 启动线程 threading.Thread(target=self.recv, name='recv').start() def recv(self): while not self.event.is_set(): data , raddr = self.sock.recvfrom(1024) # 阻塞接受数据 if data.strip() == b'quit': # 有可能发来的数据不在clients中 if raddr in self.clients: self.clients.remove(raddr) logging.info('{} leaving'.format(raddr)) continue self.clients.add(raddr) msg = '{}. from {}:{}'.format(data.decode(), *raddr) logging.info(msg) msg = msg.encode() for c in self.clients: self.sock.sendto(msg, c) # 不保证对方能够收到 def stop(self): for c in self.clients: self.sock.sendto(b'bye', c) self.sock.close() self.event.set() def main(): cs = ChatUDPServer() cs.start() while True: cmd = input('>>>') if cmd.strip() == 'quit': cs.stop() break logging.info(threading.enumerate()) logging.info(cs.clients) if __name__ == '__main__': main()

UDP版群聊客户端代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import socket import threading import datetime import logging FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatUdpClient: def __init__(self, rip='127.0.0.1', rport=9999): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.raddr = (rip, rport) self.event = threading.Event() def start(self): self.sock.connect(self.raddr) # 占用本地地址和端口,设置远端地址和端口 threading.Thread(target=self.recv, name='recv').start() def recv(self): while not self.event.is_set(): data , raddr = self.sock.recvfrom(1024) msg = '{}. from {}:{}'.format(data.decode(), *raddr) logging.info(msg) def send(self, msg:str): self.sock.sendto(msg.encode(), self.raddr) def stop(self): self.event.set() self.send('quit') # 通知服务端退出 self.sock.close() def main(): cc1 = ChatUdpClient() cc2 = ChatUdpClient() cc1.start() cc2.start() print(cc1.sock) print(cc2.sock) while True: cmd = input('Input your words >>') if cmd.strip() == 'quit': cc1.stop() cc2.stop() break cc1.send(cmd) cc2.send(cmd) if __name__ == '__main__': main()

上面的例子并不完善,如果客户端断开了,服务端不知道。每一个服务端还需要对所有客户端发送数据,包括已经断开的客户端。
问题:服务端如何知道客户端断开了呢?

代码改进

服务端代码改进

  • 增加心跳heartbeat机制或ack机制。这些机制同样可以用在TCP通信的时候
  • 心跳,就是一端定时发往另一端的信息,一般每次数据越少越好。心跳时间间隔约定好就行
  • ack即响应,一端收到另一端的消息后返回的确认信息

心跳机制

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

在服务器端代码中使用第一种心跳机制改进

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import socket import threading import datetime import logging FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatUDPServer: def __init__(self, ip='127.0.0.1', port=9999, intreval=10): self.addr = (ip, port) self.sock = socket.socket(type=socket.SOCK_DGRAM) self.clients = {} # 记录客户端, 改为字典 self.event = threading.Event() self.interval = intreval # 默认10秒,超时就要移除对应的客户端 def start(self): self.sock.bind(self.addr) # 立即绑定 # 启动线程 threading.Thread(target=self.recv, name='recv').start() def recv(self): while not self.event.is_set(): localset = set() # 清理超市 data , raddr = self.sock.recvfrom(1024) # 阻塞接受数据 current = datetime.datetime.now().timestamp() # float if data.strip() == b'^hb^': # 心跳信息 self.clients[raddr] = current continue elif data.strip() == b'quit': # 有可能发来的数据不在clients中 self.clients.pop(raddr, None) logging.info('{} leaving'.format(raddr)) continue self.clients[raddr] = current msg = '{}. from {}:{}'.format(data.decode(), *raddr) logging.info(msg) msg = msg.encode() for c , stamp in self.clients.items(): if current - stamp > self.interval: localset.add(c) else: self.sock.sendto(msg, c) # 不保证对方能够收到 for c in localset: self.clients.pop(c) def stop(self): self.event.set() self.clients.clear() self.sock.close() def main(): cs = ChatUDPServer() cs.start() while True: cmd = input('>>>') if cmd.strip() == 'quit': cs.stop() break logging.info(threading.enumerate()) logging.info(cs.clients) if __name__ == '__main__': main()

客户端代码改进

增加定时发送心跳代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import socket import threading import datetime import logging FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s" logging.basicConfig(format=FORMAT, level=logging.INFO) class ChatUdpClient: def __init__(self, rip='127.0.0.1', rport=9999): self.sock = socket.socket(type=socket.SOCK_DGRAM) self.raddr = (rip, rport) self.event = threading.Event() def start(self): self.sock.connect(self.raddr) # 占用本地地址和端口,设置远端地址和端口 threading.Thread(target=self._sendhb, name='heartbeat', daemon=True).start() threading.Thread(target=self.recv, name='recv').start() def _sendhb(self): # 心跳 while not self.event.wait(5): self.send('^hb^') def recv(self): while not self.event.is_set(): data, raddr = self.sock.recvfrom(1024) msg = '{}. from {}:{}'.format(data.decode(), *raddr) logging.info(msg) def send(self, msg: str): self.sock.sendto(msg.encode(), self.raddr) def stop(self): self.event.set() self.send('quit') # 通知服务端退出 self.sock.close() def main(): cc1 = ChatUdpClient() cc2 = ChatUdpClient() cc1.start() cc2.start() print(cc1.sock) print(cc2.sock) while True: cmd = input('Input your words >>') if cmd.strip() == 'quit': cc1.stop() cc2.stop() break cc1.send(cmd) cc2.send(cmd) if __name__ == '__main__': main()

UDP协议应用

UDP是无连接协议,它基于以下假设:

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

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

应用场景

  • 视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清话语,可以重新发话语来解决
  • 海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系
  • DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析

一般来说,UDP性能优于TCP,但是可靠性要求高的场合的还是要选择TCP协议

最后

以上就是机灵白云最近收集整理的关于python -- 网络编程(UDP编程)UDP服务端编程UDP协议应用的全部内容,更多相关python内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(82)

评论列表共有 0 条评论

立即
投稿
返回
顶部