目录
UDP服务端编程流程
1、创建服务端流程
2、UDP客户端编程流程
3、UDP的socket对象创建常用方法
练习——UDP版本群聊
UDP协议的应用
UDP服务端编程流程
1、创建服务端流程
- 创建socket的对象。socket.SOCK_DGRAM
- 绑定IP和Port,bind方法
- 传输数据
- 接受数据,socket.recvform(bufsize,[,flags]),获得一个二元组(string,address)
- 发送数据,socket.sendto(string,address)发给某地址信息
- 释放资源
import socket
sercice_udp = socket.socket(type=socket.SOCK_DGRAM)sercice_udp.bind(("0.0.0.0",9999)) #绑定一个udp端口
data = sercice_udp.recv(1024) #阻塞数据等待数据
data = sercice_udp.recvfrom(10235) #阻塞等待一个数据(value,(ip,port))
sercice_udp.sendto(b"7",('192.168.1.102',10000))
sercice_udp.close()
结果状态
2、UDP客户端编程流程
- 创建socket对象。socket.SOCK_DGRAM
- 发送数据,socket.sendto(string,address)发给某地址某信息
- 接受数据,socket.recvform(bufsize,[,flags]),获得一个二原则(string,address)
- 释放资源
注意:UDP是无协议链接的,所以可以只有任何一端,例如客户端发往服务端,服务端存在与否无所谓
UDP创建socket对象后,是没有占用本地地址和端口的
import socket
client_ser = socket.socket(type=socket.SOCK_DGRAM)
raddr = ("192.168.1.102",10000)client_ser.connect(raddr)client_ser.sendto(b'8',raddr)
client_ser.send(b'9')data = client_ser.recvfrom(1024) #阻塞等待数据(value,(ip,port))
data = client_ser.recv(1024) #阻塞等待数据client_ser.close()
结果:
3、UDP的socket对象创建常用方法
方法 | 说明 |
bind() | 可以指定本地地址和端口laddr,会立即使用 |
connect() | 可以立即占用本地地址和端口,填充 |
sendto() | 可以立即占用本地地址和端口,并把数据发送指定远端。只有有了本地绑定端口,sendto就可以向任何远端发送数据 |
send() | 需要和connect方法配合,可以使用已经从本地端口把数据发往radder指定的远端 |
recv() | 要求一定要在占用了本地端口后,返回接受的数据 |
recvform() | 要求一定占用了本地端口后,返回接受的数据和对端地址的二元组 |
练习——UDP版本群聊
服务端代码改进
- 加一个ack机制和心跳hearbeat。心跳,就是一端定时发往另一端的信息,一般每次数据越少越好,心跳时间间隔约定好就行。ack即响应,一端收到另一端
心跳机制
- 一般来说客户端定时发往服务端的,服务端并不需要ack回复客户端,只需要记录该客户端还活着就行了
- 如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息,这种实现较为复杂,用的较少
- 也可以双向发心跳的,用的更少
import socket
import threading
import datetime
import loggingFORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)class CharUDPserver:def __init__(self,ip="127.0.0.1",port=9988,interval=5):self.addr = (ip,port)self.sock = socket.socket(type=socket.SOCK_DGRAM)self.clients = {} #记录客户端,字典self.event = threading.Event()self.interval = interval #默认10秒,超时就要移除对应的客户端def start(self):self.sock.bind(self.addr)#启动线程threading.Thread(target=self.recv,name="recv").start()# threading.Thread(target=self.recv,name="recv").start()def recv(self):while not self.event.is_set():loalset = set()data ,raddr = self.sock.recvfrom(1024) #阻塞接受数据data = data.decode().strip()current = datetime.datetime.now().timestamp() #float点数if data == "hb":print("^^^^^^^^^hb",raddr,data,current)self.clients[raddr]=currentcontinueelif data == "quit":#有可能数据不存在clients中self.clients.pop(raddr,None)logging.info("{} leaving".format(raddr))continueprint(11111)#有信息来就更新时间#什么时候比叫心跳时间尼?发送信息的时候,反正要遍历一遍self.clients[raddr]=currentmsg = "{} ack.form {} :{} ".format(data,*raddr)last_current = datetime.datetime.now().timestamp() #float点数logging.info(msg)msg = msg.encode()for c ,stamp in self.clients.items():print(c,stamp)if last_current - stamp >= self.interval:self.sock.sendto(msg,c)else:loalset.add(c)for i in loalset:self.clients.pop(i)print(loalset)def stop(self):for a in self.clients:self.sock.sendto(b"bey",a)self.sock.close()self.event.set()def main():cs = CharUDPserver()cs.start()while True:cmd = input(">>>>")if cmd.strip() == "quit":cs.stop()breaklogging.info(threading.enumerate())logging.info(cs.clients)if __name__ == '__main__':main()
客户端代码改进【增加心跳机制】
import threading
import socket
import loggingFORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)class ChatUDBClient():def __init__(self,rip = "127.0.0.1",rport=9988):self.addr = (rip,rport)self.socket = socket.socket(type=socket.SOCK_DGRAM)self.event = threading.Event()def start(self):self.socket.connect(self.addr) #占用本地地址和端口,设置远端地址和端口threading.Thread(target=self.recv,name="recv").start()threading.Thread(target=self._sendhb,name="heartbeat",daemon=True).start()def _sendhb(self):while not self.event.wait(2):self.send("hb")def recv(self):while not self.event.is_set():data ,raddr = self.socket.recvfrom(1024)msg = "{}。form {}:{}".format(data.decode(),*raddr)logging.info(msg)def send(self,msg:str):self.socket.sendto(msg.encode(),self.addr)def stop(self):self.send("quit") #通知服务端退出self.socket.close()self.event.set()def main():cc1 = ChatUDBClient()cc2 = ChatUDBClient()cc1.start()# cc2.start()print(cc1.start)# print(cc1.start)logging.info(threading.enumerate())while True:cmd = input("》》》")if cmd == "quit":cc1.stop()# cc2.stop()breakcc1.start()# cc2.start()if __name__ == '__main__':main()
UDP协议的应用
UDP是无连接协议,它基于以下假设:网络足够号,消息不会丢包,包不会乱序
但是,即使是在局域网,也不能保证不丢包,而且包的到达不一定有序
应用场景,视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清华语,可以重新发话语来解决。海量数据,例如:传感器的数据,丢几十、几百也没有关系。DNS,数据内容下,一个包就能查询到结果,不存在丢包、乱序、重新请求解析。
一般来说,UDP的性能由于TCP,但是可靠性要求高的场合还是要选择TCP协议