大家好,又见面了,我是你们的朋友全栈君。
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
- windows查找udp是否启动端口:
UDP服务端编程
- UDP服务端编程流程
- 创建socket对象。socket.SOCK_DGRAM
- 绑定IP和Port,bind()方法
- 传输数据
- 接收数据,socket.recvfrom(bufsize[,flags]),获得一个二元组(string,address)
- 发送数据,socket.sendto(string,address)发给某地址某信息
- 释放资源
import logging
import sys
import socket
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)d %(message)s",stream=sys.stdout,level=logging.INFO)
server = socket.socket(type=socket.SOCK_DGRAM) #创建一个基于UDP的socket
server.bind(("127.0.0.1",3999)) #立即绑定一个udp端口
# data = server.recv(1024) #阻塞等待数据
data,radde = server.recvfrom(1024) #阻塞等待数据(value,(ip,port))
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 socket
logging.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) #connect方法会自动分配一个本地的UDP地址,和设置UDP的链接对象raddr地址
logging.info(client)
client.send(b"hello") #由于使用了connect方法,所以不指定终端也能发送
client.sendto(b"why",raddr) #也可以使用指定地址发送
data,radde = client.recvfrom(1024)
logging.info("{}-{}".format(radde,data))
client.close()
- 第二个版本,不使用connect指定目标
import logging
import sys
import socket
logging.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是无链接协议,所以可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓。
- 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 datetime
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)d %(message)s",stream=sys.stdout,level=logging.INFO)
class ChatUDPServer:
def __init__(self,ip="127.0.0.1",port=3999,timeout=10):
self.socket = socket.socket(type = socket.SOCK_DGRAM)
self.laddr = ip,port
self.event = threading.Event()
self.timeout = timeout
self.clients = {
}
def start(self):
socket = self.socket
socket.bind(self.laddr)
threading.Thread(target=self.run,name="run").start()
def run(self):
socket = self.socket
clients = self.clients
while not self.event.is_set():
try:
data,raddr = socket.recvfrom(1024)
except:
continue
utctime = datetime.datetime.now().timestamp()
if data.strip() == b"by": #如果用户自己发送by表示要退出
self.clients.pop(raddr)
continue
clients[raddr] = utctime
if data.strip() == b"^hh^": #如果是心跳,就忽略
continue
msg = "[{}] {}".format(raddr,data)
logging.info(msg)
outclient = [] #记录超时的链接地址
for cr,tm in clients.items():
if 0 <= utctime - tm <= 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()
@classmethod
def main(cls):
chserver = cls()
chserver.start()
while True:
cmd = input(">>>>")
if cmd.strip() == "quit":
chserver.stop()
threading.Event().wait(1)
break
logging.info(threading.enumerate())
logging.info(chserver.clients)
ChatUDPServer.main()
- UDP版本客户端代码
""" author:xdd date:2019-06-17 10:26 """
import logging
import sys
import socket
import threading
logging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
class UdpClient:
def __init__(self,ip="127.0.0.1",prost = 3999,heartbeattime = 5):
self.socket = socket.socket(type=socket.SOCK_DGRAM)
self.raddr = ip,prost
self.event = threading.Event()
self.heartbeattime = heartbeattime
def start(self):
self.socket.connect(self.raddr)
self.send("hello")
threading.Thread(target=self.heartbeat,name="heartbeat").start()
threading.Thread(target=self.run,name="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 = self.socket.recvfrom(1024)
except:
continue
logging.info("[ {} msg ] {}".format(raddr,data))
def send(self,msg):#发送消息
socket = self.socket
socket.send(msg.encode())
def stop(self):#停止
self.event.set()
self.socket.close()
@classmethod
def main(cls):
client = cls()
client.start()
while True:
cmd = input(">>>")
if cmd.strip() == "quit":
client.stop()
break
else:
client.send(cmd)
UdpClient.main()
- 服务端代码
- 增加心跳heartbeat机制或ack机制。这些机制同样可以用在TCP通信的时候。
- 心跳,就是一端定时发往另一端的信息,一般每次数据越少越好。心跳时间间隔约定好就行。
- ack即响应,一端收到另一端的消息后返回的确认信息。
- 心跳机制
- 一般来说是客户端定时发往服务端的,服务端并不需要ack回复客户端,只需要记录该客户端还活着就行了。
- 如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端 移除其信息。这种实现较为复杂,用的较少。
- 也可以双向都发心跳的,用的更少。
UDP协议的应用
- UDP是无连接协议,它基于以下假设:
- 网络足够好
- 消息不会丢包
- 包不会乱序
- 但是,即使是在局域网,也不能保证不丢包,而且包的到达不一定有序。
- 应用场景
- 视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清话语,可以重新发话语来解决。 海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。
- DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析。
- 一般来说,UDP性能优于TCP,但是可靠性要求高的场合的还是要选择TCP协议。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/196114.html原文链接:https://javaforall.cn