1、UDP编程:
测试命令:
windows:
netstat -anp udp | findstr 9999
Linux: 发给服务器数据
echo '233' | nc -u 127.0.0.1 9999
2、UDP服务器端编程:
UDP服务器端编程流程:(从图中可以看到,服务器端只需要一个socket)
- 创建socket 对象,socket.SOCK_DGRAM
- 绑定IP 和 Port, bind() 方法
- 传输数据:
- 接受数据:socket.recvfrom(bufsize [, flags] ) ,获取一个二元组()
- 发送数据:socket.sendto(string, address) 发给某地址某信息
- 释放资源
1 import socket
2
3 socket = socket.socket(type=socket.SOCK_DGRAM)
4
5 ip = '127.0.0.1'
6 port = 9999
7
8 laddr = ip, port
9
10 socket.bind(laddr) # 服务器端正式启动
11
12 data, raddr = socket.recvfrom(1024) # 没有接收到,会处于阻塞状态
13
14 socket.sendto(data, raddr)
15
16 socket.close()
UDP客户端编程流程:
注意:UDP是无连接协议,多以可以只有任何一端,例如客户端数据发往服务端,服务器端存在与否无所谓。
UDP编程中bind、connect、send、sendto、recv、recfrom方法使用
UDP的socket对象创建后,是么有占用本地地址额端口的
注意:
1、UDP 创建socket后,不能直接recv,recvfrom,,只有知道了本地地址和端口,服务器端才能知道数据应该发给你
2、send 和 connect 搭配使用。
心跳机制:
加一个ack机制 和 心跳 hearbeat
心跳,就是一端定时的发往另一端的信息,一般每次数据越少越好,心跳时间间隔约定好就行。
ack 即响应,一端收到另一端的消息后返回的确认消息
心跳机制:
- 一般来说是客户端定时发往服务器端的,服务器端并不需要ACK 回复客户端,只需要记录该客户端还活着就行(比如群聊,如果客户端不活着,就没必要回复)
- 如果是服务器端定时发往客户端的,一般需要 客户端ack 响应,表示活着,,如果没有收到ack的客户端,服务器端移除其信息,这种实现比较复杂,较少使用。
- 也就是双向都发送心跳,很少使用
UDP实现群聊:
server端:
1 import socket
2 import threading
3 import logging
4 import datetime
5
6
7 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
8 logging.basicConfig(format=FORMAT, level=logging.INFO)
9
10 class ChatServer:
11 def __init__(self, ip='127.0.0.1', port=9999, interval=10):
12 self.laddr = ip, port
13 self.event = threading.Event()
14 self.sock = socket.socket(type=socket.SOCK_DGRAM)
15 self.clients = {}
16 self.interval = interval
17
18 def start(self):
19 self.sock.bind(self.laddr)
20
21 threading.Thread(target=self.recv, name='recvive').start()
22
23 def recv(self):
24 while not self.event.is_set():
25 data, raddr = self.sock.recvfrom(1024)
26 localkeys = set()
27 logging.info(data)
28
29 # 心跳信息
30 if data.strip() == b'^hb^':
31 self.clients[raddr] = datetime.datetime.now().timestamp()
32 continue
33
34 if data.strip() == b'quit':
35 # 若一个客户端刚进来,还没有加到字典中,直接pop,会报KerryError
36 # if raddr in self.clients.keys():
37 self.clients.pop(raddr, None)
38 continue
39 self.clients[raddr] = datetime.datetime.now().timestamp()
40
41 # z再次发送数据的时间,如果之后没有再次进入,这次的时间就作为下次比较时间,所以这次的时间可以认为是最新的心跳信息
42 current = datetime.datetime.now().timestamp()
43 msg = '{}--{}'.format(data.decode(), '******').encode()
44
45 # 过期的就不在发数据给客户端了,并且剔除掉
46 #z 字典不能再遍历的时候,删除内容
47 for r, t in self.clients.items():
48 if current - t > self.interval:
49 localkeys.add(r)
50 self.sock.sendto(msg, r)
51 for r in localkeys:
52 self.clients.pop(r)
53
54 def stop(self):
55 self.sock.close()
56 self.event.set()
57
58
59 def main():
60 cs = ChatServer()
61 cs.start()
62
63 while True:
64 cmd = input(">>>")
65 if cmd == 'quit':
66 cs.stop()
67 break
68 logging.info(threading.enumerate())
69
70
71 if __name__ == "__main__":
72 main()
群聊server端
client端:
1 import socket
2 import threading
3 import logging
4 import datetime
5
6
7 FORMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
8 logging.basicConfig(format=FORMAT, level=logging.INFO)
9
10 class ChatClient:
11 def __init__(self, ip='127.0.0.1', port=9999, interval=3):
12 self.raddr = ip, port
13 self.event = threading.Event()
14 self.sock = socket.socket(type=socket.SOCK_DGRAM)
15 self.interval = interval
16
17 def start(self):
18 self.sock.connect(self.raddr)
19
20 threading.Thread(target=self.recv, name='c-recv').start()
21
22 # 每隔interval 就发送一次心跳信息
23 threading.Thread(target=self.hearbeat, name='ht',daemon=True).start()
24
25 def recv(self):
26 while not self.event.is_set():
27 data, raddr = self.sock.recvfrom(1024)
28 print(data)
29 print(raddr)
30 def hearbeat(self):
31 while not self.event.wait(self.interval):
32 self.send('^hb^')
33
34
35
36 def send(self, msg):
37 self.sock.send(msg.encode())
38
39 def stop(self):
40 self.sock.close()
41 self.event.set()
42
43
44 def main():
45 cc = ChatClient()
46 cc.start()
47
48 while True:
49 cmd = input('>>')
50 if cmd == 'quit':
51 cc.stop()
52 break
53 cc.send(cmd)
54 logging.info(threading.enumerate())
55
56
57
58 if __name__ == "__main__":
59 main()
群聊client端
注意:
如果是如上图所示,是一个解释器进程,建立两个对象,但是这两个对象分别在不同的线程中跑。
如果直接运行两次 client,就是两个客户端进程
UDP 协议的应用:
UDP 是无连接的,它是基于以下假设:
网络足够好
消息不会丢包
包不会乱序
但是,即使在局域网,也不能保证不丢包,而且包到达的不一定有序。
应用场景:
视频,音频传输,一般来说丢一些包。