UDP编程与Socket
文章目录
- UDP编程与Socket
- UDP服务端编程
- 练习--UDP版本群聊
- UDP协议的应用
- 相关测试命令
- windows查找udp是否启动端口:
netstart -anp udp | find "9999"
netstart -anbp udp | findstr 9999
- linux下发给服务端数据
echo "123abc" | nc -u 172.0.0.1 9999
UDP服务端编程
- UDP服务端编程流程
![udp_001](https://img8.php1.cn/3cdc5/12fff/696/7b465e865fff8554.jpeg)
- 创建socket对象。socket.SOCK_DGRAM
- 绑定IP和Port,bind()方法
- 传输数据
- 接收数据,socket.recvfrom(bufsize[,flags]),获得一个二元组(string,address)
- 发送数据,socket.sendto(string,address)发给某地址某信息
- 释放资源
import logging
import sys
import socketlogging.basicConfig(format="%(asctime)s %(threadName)s %(thread)d %(message)s",stream=sys.stdout,level=logging.INFO)server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(("127.0.0.1",3999))
data,radde = server.recvfrom(1024)
logging.info("{}-{}".format(radde,data))
server.sendto("{} server msg = {}".format(server.getsockname(),data).encode(),radde)
server.close()
-
UDP客户端编写流程
- 创建socket对象。socket.SOCK_DGRAM
- 发送数据,socket_sendto(string,address)发给某地址信息
- 接收数据,socket.recvfrom(bufsize[,flags]),获得一个二元组(string,address)
- 释放资源
-
第一个版本
import logging
import sys
import socketlogging.basicConfig(format="%(asctime)s %(threadName)s %(thread)d %(message)s",stream=sys.stdout,level=logging.INFO)client = socket.socket(type=socket.SOCK_DGRAM)
raddr = "127.0.0.1",3999
client.connect(raddr)
logging.info(client)
client.send(b"hello")
client.sendto(b"why",raddr)
data,radde = client.recvfrom(1024)
logging.info("{}-{}".format(radde,data))
client.close()
![udp_002](https://img.php1.cn/3cd4a/1eebe/cd5/ed19db63ee478b98.png)
- 第二个版本,不使用connect指定目标
import logging
import sys
import socketlogging.basicConfig(format="%(asctime)s %(threadName)s %(thread)d %(message)s",stream=sys.stdout,level=logging.INFO)client = socket.socket(type=socket.SOCK_DGRAM)
raddr = "127.0.0.1",3999
client.sendto(b"hello",raddr)
logging.info(client)
data,laddr = client.recvfrom(1024)
logging.info("{}-{}".format(data,laddr))
client.close()
![udp_003](https://img8.php1.cn/3cdc5/12fff/696/01fb463a1841543e.jpeg)
- 注意:UDP是无链接协议,所以可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓。
- UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用
- UDP的socket对象创建后,是没有占用本地地址和端口的。
方法 | 说明 |
---|
bind(laddr) | 可以指定本地地址和端口laddr,会立即占用,laddr为一个元组,(ip,prot) |
connect(raddr) | 会随机分配一个本地的端口laddr,会绑定远端地址和端口raddr,raddr是个元组,(ip,prot) |
sendto(msg,raddr) | 可以立即占用本地地址和端口laddr,并把数据发往指定远端。只有有了本地绑定的端口,sendto就可以向任何远端发送数据 msg #要发送的数据。bytes类型。 raddr#远端地址和端口组成的一个元组(ip,prot) |
send(msg) | 需要和connect方法配合,可以使用已经从本地端口把数据发往raddr指定的远端 msg#需要发送的消息bytes类型 |
recv(buffersize) | 要求一定要在占用了本地端口后,返回接受的数据,buffersize指定一个缓冲区大小 |
recvfrom(buffersize) | 要求一定要占用了本地端口后,返回接收的数据和对端地址的二元组(msg,raddr) buffersize指定一个缓冲区大小。 |
练习–UDP版本群聊
"""
author:xdd
date:2019-06-17 09:20
"""
import logging
import sys
import socket
import threading
import datetimelogging.basicConfig(format&#61;"%(asctime)s %(threadName)s %(thread)d %(message)s",stream&#61;sys.stdout,level&#61;logging.INFO)class ChatUDPServer:def __init__(self,ip&#61;"127.0.0.1",port&#61;3999,timeout&#61;10):self.socket &#61; socket.socket(type &#61; socket.SOCK_DGRAM)self.laddr &#61; ip,portself.event &#61; threading.Event()self.timeout &#61; timeoutself.clients &#61; {}def start(self):socket &#61; self.socketsocket.bind(self.laddr)threading.Thread(target&#61;self.run,name&#61;"run").start()def run(self):socket &#61; self.socketclients &#61; self.clientswhile not self.event.is_set():try:data,raddr &#61; socket.recvfrom(1024)except:continueutctime &#61; datetime.datetime.now().timestamp()if data.strip() &#61;&#61; b"by": self.clients.pop(raddr)continueclients[raddr] &#61; utctimeif data.strip() &#61;&#61; b"^hh^": continuemsg &#61; "[{}] {}".format(raddr,data)logging.info(msg)outclient &#61; [] for cr,tm in clients.items():if 0 <&#61; utctime - tm <&#61; self.timeout:socket.sendto(msg.encode(),cr)else:outclient.append(cr)for cr in outclient: self.clients.pop(cr)def stop(self):try:self.event.set()self.socket.close()finally:self.clients.clear()&#64;classmethoddef main(cls):chserver &#61; cls()chserver.start()while True:cmd &#61; input(">>>>")if cmd.strip() &#61;&#61; "quit":chserver.stop()threading.Event().wait(1)breaklogging.info(threading.enumerate())logging.info(chserver.clients)ChatUDPServer.main()
"""
author:xdd
date:2019-06-17 10:26
"""import logging
import sys
import socket
import threadinglogging.basicConfig(format&#61;"%(asctime)s %(thread)d %(threadName)s %(message)s",stream&#61;sys.stdout,level&#61;logging.INFO)class UdpClient:def __init__(self,ip&#61;"127.0.0.1",prost &#61; 3999,heartbeattime &#61; 5):self.socket &#61; socket.socket(type&#61;socket.SOCK_DGRAM)self.raddr &#61; ip,prostself.event &#61; threading.Event()self.heartbeattime &#61; heartbeattimedef start(self):self.socket.connect(self.raddr)self.send("hello")threading.Thread(target&#61;self.heartbeat,name&#61;"heartbeat").start()threading.Thread(target&#61;self.run,name&#61;"client").start()def heartbeat(self):while not self.event.wait(self.heartbeattime):self.send("^hh^")logging.info("心跳结束")def run(self):while not self.event.is_set():try:data,raddr &#61; self.socket.recvfrom(1024)except:continuelogging.info("[ {} msg ] {}".format(raddr,data))def send(self,msg):socket &#61; self.socketsocket.send(msg.encode())def stop(self):self.event.set()self.socket.close()&#64;classmethoddef main(cls):client &#61; cls()client.start()while True:cmd &#61; input(">>>")if cmd.strip() &#61;&#61; "quit":client.stop()breakelse:client.send(cmd)UdpClient.main()
- 服务端代码
- 增加心跳heartbeat机制或ack机制。这些机制同样可以用在TCP通信的时候。
- 心跳&#xff0c;就是一端定时发往另一端的信息&#xff0c;一般每次数据越少越好。心跳时间间隔约定好就行。
- ack即响应&#xff0c;一端收到另一端的消息后返回的确认信息。
- 心跳机制
- 一般来说是客户端定时发往服务端的&#xff0c;服务端并不需要ack回复客户端&#xff0c;只需要记录该客户端还活着就行了。
- 如果是服务端定时发往客户端的&#xff0c;一般需要客户端ack响应来表示活着&#xff0c;如果没有收到ack的客户端&#xff0c;服务端 移除其信息。这种实现较为复杂&#xff0c;用的较少。
- 也可以双向都发心跳的&#xff0c;用的更少。
UDP协议的应用
- UDP是无连接协议&#xff0c;它基于以下假设&#xff1a;
- 网络足够好
- 消息不会丢包
- 包不会乱序
- 但是&#xff0c;即使是在局域网&#xff0c;也不能保证不丢包&#xff0c;而且包的到达不一定有序。
- 应用场景
- 视频、音频传输&#xff0c;一般来说&#xff0c;丢些包&#xff0c;问题不大&#xff0c;最多丢些图像、听不清话语&#xff0c;可以重新发话语来解决。 海量采集数据&#xff0c;例如传感器发来的数据&#xff0c;丢几十、几百条数据也没有关系。
- DNS协议&#xff0c;数据内容小&#xff0c;一个包就能查询到结果&#xff0c;不存在乱序&#xff0c;丢包&#xff0c;重新请求解析。
- 一般来说&#xff0c;UDP性能优于TCP&#xff0c;但是可靠性要求高的场合的还是要选择TCP协议。