UDP服务端编程流程
创建socket对象。socket.SOCK_DGRAM绑定IP和Port,bind()方法传输数据
接收数据,socket.recvfrom(bufsize[,flags]),获得一个二元组(string, address)发送数据,socket.sendto(string, address)发给某地址某信息 释放资源
import socket
server
= socket
.socket
(type=socket
.SOCK_DGRAM
)
server
.bind
('0.0.0.0', 9999)
data
= server
.recv
(1024)
data
= server
.recvfrom
(1024)
server
.sendto
(b
'7', ('192.168.0.2', 10000))
server
.close
()
UDP客户端编程流程
创建socket对象。socket.SOCK_DGRAM发送数据,socket.sendto(string, address)发送某地址某信息接收数据,socket.recvfrom(bufsize[,flags]),获得一个二元组(string, address)释放资源
import socket
client
= socket
.socket
(type=socket
.SOCK_DGRAM
)
raddr
= ('192.168.2.2', 9999)
client
.connect
(raddr
)
client
.sendto
(b
'8', raddr
)
client
.send
(b
'9')
data
= client
.recvfrom
(1024)
data
= client
.recv
(1024)
client
.close
()
注意:UDP是无连接协议,所以可以只有任何一端,例如客户端数据发往服务端,服务端存在与否无所谓。
UDP编程中bind、connect、send、sendto、recv、recvfrom方法使用 UDP的socket对象创建后,是没有占用本地地址和端口的。
UDP版群聊
服务端代码
class ChatServer:
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 logging
import datetime
import threading
Format
= '%(asctime)s - %(threadName)s - %(thread)d - %(message)s)'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatUDPServer:
def __init__(self
, rip
='127.0.0.1', port
=9999):
self
.sock
= socket
.socket
(type=socket
.SOCK_DGRAM
)
self
.addr
= rip
, port
self
.clients
= set()
sefl
.event
= threading
.Event
()
def start(self
):
self
.sock
.bind
(self
.addr
)
theading
.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)
if data
.strip
() == b
'quit' or data
.strip
() == b
'':
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
:
c
.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('>>>').strip
()
if cmd
== 'quit' or cmd
== '':
cs
.stop
()
break
if __name__
== '__main__':
main
()
UDP群聊客户端代码
import threading
import socket
import logging
Format
= '%(asctime)s - %(threadName)s - %(thread)d - %(message)s)'
logging
.basicConfig
(format=Format
, level
=logging
.INFO
)
class ChatUDPClient:
def __init__(self
, ip
='127.0.0.1', rport
=9999):
self
.sock
= socket
.socket
(type=socket
.SOCK_DGRAMK
)
self
.raddr
= ip
, 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
, *raddr
)
logging
.info
(msg
)
def send(self
):
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
()
while True:
cmd
= input('>>>').strip
()
if cmd
== 'quit' or cmd
== '':
cc1
.stop
()
cc2
.stop
()
break
cc1
.send
(cmd
)
cc2
.send
(cmd
)
if __name__
== '__main__':
main
()
File "<ipython-input-1-083d1559fc4b>", line 30
def stop(self):
^
SyntaxError: unexpected EOF while parsing
心跳机制
一般来说是客户端定时发往客户端的,服务端并不需要ack回复客户端,只需要记录该客户端还活着就行了。如果是服务端定时发往客户端的,一般需要客户端ack响应来表示活着,如果没有收到ack的客户端,服务端移除其信息。这种实现较为复杂,用的较少。也可以双向都发心跳,用的更少。
UDP协议应用
UDP是无连接协议,它基于以下假设:
网络足够好消息不会丢包包不会乱序
但是,即使在局域网,也不能保证不丢包,而且包的到达不一定有序。
应用场景
视频、音频传输,一般来说,丢些包,问题不大,最多丢写图像、听不清话语,可以重新话语来解决。 海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。 DNS协议,数据内容小,一个包就能查询到结果,不存在乱序,丢包,重新请求解析。
一般来说,UDP性能优于TCP,但是可靠性要求高的场合还是要选择TCP协议。