目录
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 logging
FORMAT = "%(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]=current
continue
elif data == "quit":
#有可能数据不存在clients中
self.clients.pop(raddr,None)
logging.info("{} leaving".format(raddr))
continue
print(11111)
#有信息来就更新时间
#什么时候比叫心跳时间尼?发送信息的时候,反正要遍历一遍
self.clients[raddr]=current
msg = "{} 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()
break
logging.info(threading.enumerate())
logging.info(cs.clients)
if __name__ == '__main__':
main()
客户端代码改进【增加心跳机制】 ?
import threading
import socket
import logging
FORMAT = "%(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()
break
cc1.start()
# cc2.start()
if __name__ == '__main__':
main()
UDP协议的应用
UDP是无连接协议,它基于以下假设:网络足够号,消息不会丢包,包不会乱序 但是,即使是在局域网,也不能保证不丢包,而且包的到达不一定有序
应用场景,视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清华语,可以重新发话语来解决。海量数据,例如:传感器的数据,丢几十、几百也没有关系。DNS,数据内容下,一个包就能查询到结果,不存在丢包、乱序、重新请求解析。
一般来说,UDP的性能由于TCP,但是可靠性要求高的场合还是要选择TCP协议
|