概述
UDP编程
- UDP服务端编程
- UDP服务端编程流程
- UDP客户端编程流程
- UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用
- 练习 --UDP版群聊
- UDP版群聊服务端代码
- UDP版群聊客户端代码
- 代码改进
- 服务端代码改进
- 心跳机制
- 客户端代码改进
- UDP协议应用
- 应用场景
测试命令
windows 查找udp是否启动端口
> netstat -anp udp | find "9999"
> net stat -anbp udp | findstr 9999
linux下发给服务端数据
$ echo "123abc" | nc -u 127.0.0.1 9999
UDP服务端编程
UDP服务端编程流程
- 创建socket对象。
type=socket.Sock_DGRAM
- 绑定IP和Port,
bing()
方法 - 传输数据
- 接受数据,
socket.recvform(bufsize[flags])
,获得一个二元组(string,address)
- 发送数据,
socket.sendto(string, address)
发给某地址某信息
- 接受数据,
- 释放资源
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bing(('0.0.0.0', 9999)) # 立即绑定一个udp端口
data = server.recv(1024) # 阻塞等待数据
data = server.recvfrom(1024) # 阻塞等待数据(value, (ip, port))
server.sendto(b'hello', ('192.168.142.1', 10000))
server.close()
UDP客户端编程流程
- 创建socket对象。
type=socket.Sock_DGRAM
- 发送数据,
socket.sendto(string, address)
发给某地址某信息 - 接受数据,
socket.recvform(bufsize[flags])
,获得一个二元组(string,address)
- 释放资源
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
raddr = ('192.168.142.1', 9999)
client.connect(raddr)
client.sendto(b'hello', raddr)
client.send(b'hello')
data = client.recvfrom(1024) # 阻塞等待数据(value, (ip, port))
data = client.recv(1024) # 阻塞等待数据
client.close()
- 注意:UDP是无连接协议,所以可以只有任何一端
例如客户端数据发往服务端,服务端存在与否无所谓
UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用
- UDP的socket对象创建后,是没有占用本地地址和端口的
方法 | 说明 |
---|---|
bind方法 | 可以指定本地地址和端口laddr,会立即占用 |
connect方法 | 可以立即占用本地地址和端口laddr,填充远程地址和端口raddr |
sendto方法 | 可以立即占用本地地址和端口laddr,并把数据发往指定远端。 只有有了本地绑定端口,sendto就可以向任何远端发送数据 |
send方法 | 需要和connect方法配合, 可以使用已经从本地端口把数据发往raddr指定的远端 |
recv方法 | 要求一定要在占用了本地端口后,返回接受的数据 |
recvfrom方法 | 要求一定要占用了本地端口后,返回接受的数据和对端地址的二元祖 |
练习 --UDP版群聊
UDP版群聊服务端代码
- 服务端类的基本架构
class ChatUDPServer:
def __init__(self, ip='127.0.0.1', port=9999):
self.addr = (ip, port)
self.sock = socket.socket(type=socket.SOCK_DGRAM)
def start(self):
self.sock.bind(self.addr) # 立即绑定
self.sock.recvfrom(1024) # 阻塞接受数据
def stop(self):
self.sock.close()
- 完整版版代码
import socket
import threading
import datetime
import logging
FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
class ChatUDPServer:
def __init__(self, ip='127.0.0.1', port=9999):
self.addr = (ip, port)
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.clients = set() # 记录客户端
self.event = threading.Event()
def start(self):
self.sock.bind(self.addr) # 立即绑定
# 启动线程
threading.Thread(target=self.recv, name='recv').start()
def recv(self):
while not self.event.is_set():
data , raddr = self.sock.recvfrom(1024) # 阻塞接受数据
if data.strip() == b'quit':
# 有可能发来的数据不在clients中
if raddr in self.clients:
self.clients.remove(raddr)
logging.info('{} leaving'.format(raddr))
continue
self.clients.add(raddr)
msg = '{}. from {}:{}'.format(data.decode(), *raddr)
logging.info(msg)
msg = msg.encode()
for c in self.clients:
self.sock.sendto(msg, c) # 不保证对方能够收到
def stop(self):
for c in self.clients:
self.sock.sendto(b'bye', c)
self.sock.close()
self.event.set()
def main():
cs = ChatUDPServer()
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()
UDP版群聊客户端代码
import socket
import threading
import datetime
import logging
FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
class ChatUdpClient:
def __init__(self, rip='127.0.0.1', rport=9999):
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.raddr = (rip, rport)
self.event = threading.Event()
def start(self):
self.sock.connect(self.raddr) # 占用本地地址和端口,设置远端地址和端口
threading.Thread(target=self.recv, name='recv').start()
def recv(self):
while not self.event.is_set():
data , raddr = self.sock.recvfrom(1024)
msg = '{}. from {}:{}'.format(data.decode(), *raddr)
logging.info(msg)
def send(self, msg:str):
self.sock.sendto(msg.encode(), self.raddr)
def stop(self):
self.event.set()
self.send('quit') # 通知服务端退出
self.sock.close()
def main():
cc1 = ChatUdpClient()
cc2 = ChatUdpClient()
cc1.start()
cc2.start()
print(cc1.sock)
print(cc2.sock)
while True:
cmd = input('Input your words >>')
if cmd.strip() == 'quit':
cc1.stop()
cc2.stop()
break
cc1.send(cmd)
cc2.send(cmd)
if __name__ == '__main__':
main()
上面的例子并不完善,如果客户端断开了,服务端不知道。每一个服务端还需要对所有客户端发送数据,包括已经断开的客户端。
问题:服务端如何知道客户端断开了呢?
代码改进
服务端代码改进
- 增加心跳heartbeat机制或ack机制。这些机制同样可以用在TCP通信的时候
- 心跳,就是一端定时发往另一端的信息,一般每次数据越少越好。心跳时间间隔约定好就行
- ack即响应,一端收到另一端的消息后返回的确认信息
心跳机制
- 一般来说是客户端定时发往服务端的,服务端并不需要ack回复客户端,只需要记录该客户端还活着就行了
- 如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息。这种实现较为复杂,用的较少
- 也可以双向都发心跳的,用的更少
在服务器端代码中使用第一种心跳机制改进
import socket
import threading
import datetime
import logging
FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
class ChatUDPServer:
def __init__(self, ip='127.0.0.1', port=9999, intreval=10):
self.addr = (ip, port)
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.clients = {} # 记录客户端, 改为字典
self.event = threading.Event()
self.interval = intreval # 默认10秒,超时就要移除对应的客户端
def start(self):
self.sock.bind(self.addr) # 立即绑定
# 启动线程
threading.Thread(target=self.recv, name='recv').start()
def recv(self):
while not self.event.is_set():
localset = set() # 清理超市
data , raddr = self.sock.recvfrom(1024) # 阻塞接受数据
current = datetime.datetime.now().timestamp() # float
if data.strip() == b'^hb^': # 心跳信息
self.clients[raddr] = current
continue
elif data.strip() == b'quit':
# 有可能发来的数据不在clients中
self.clients.pop(raddr, None)
logging.info('{} leaving'.format(raddr))
continue
self.clients[raddr] = current
msg = '{}. from {}:{}'.format(data.decode(), *raddr)
logging.info(msg)
msg = msg.encode()
for c , stamp in self.clients.items():
if current - stamp > self.interval:
localset.add(c)
else:
self.sock.sendto(msg, c) # 不保证对方能够收到
for c in localset:
self.clients.pop(c)
def stop(self):
self.event.set()
self.clients.clear()
self.sock.close()
def main():
cs = ChatUDPServer()
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 socket
import threading
import datetime
import logging
FORMAT = "%(asctime)s %(threadName) %(thread) %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
class ChatUdpClient:
def __init__(self, rip='127.0.0.1', rport=9999):
self.sock = socket.socket(type=socket.SOCK_DGRAM)
self.raddr = (rip, rport)
self.event = threading.Event()
def start(self):
self.sock.connect(self.raddr) # 占用本地地址和端口,设置远端地址和端口
threading.Thread(target=self._sendhb, name='heartbeat', daemon=True).start()
threading.Thread(target=self.recv, name='recv').start()
def _sendhb(self): # 心跳
while not self.event.wait(5):
self.send('^hb^')
def recv(self):
while not self.event.is_set():
data, raddr = self.sock.recvfrom(1024)
msg = '{}. from {}:{}'.format(data.decode(), *raddr)
logging.info(msg)
def send(self, msg: str):
self.sock.sendto(msg.encode(), self.raddr)
def stop(self):
self.event.set()
self.send('quit') # 通知服务端退出
self.sock.close()
def main():
cc1 = ChatUdpClient()
cc2 = ChatUdpClient()
cc1.start()
cc2.start()
print(cc1.sock)
print(cc2.sock)
while True:
cmd = input('Input your words >>')
if cmd.strip() == 'quit':
cc1.stop()
cc2.stop()
break
cc1.send(cmd)
cc2.send(cmd)
if __name__ == '__main__':
main()
UDP协议应用
UDP是无连接协议,它基于以下假设:
- 网络足够好
- 消息不会丢包
- 包不会乱序
但是,即使再局域网,也不能保证不丢包,而且包的达到不一定有序
应用场景
- 视频、音频传输,一般来说,丢些包,问题不大,最多丢些图像、听不清话语,可以重新发话语来解决
- 海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系
- DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析
一般来说,UDP性能优于TCP,但是可靠性要求高的场合的还是要选择TCP协议
最后
以上就是机灵白云为你收集整理的python -- 网络编程(UDP编程)UDP服务端编程UDP协议应用的全部内容,希望文章能够帮你解决python -- 网络编程(UDP编程)UDP服务端编程UDP协议应用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复