TOC
UDP编程
TCP和UDP是有差异的,TCP是有连接协议,面向字节流的,UDP叫数据报协议,它是不需要事先连接的,也就是说UDP在数据发送的过程中,根本不需要事先建立一个通信通道,消息就直接发出去了,对方收没收到的问题,不做过多干涉;
那如果我们非得要知道对方有没有收到,还必须得在对方在应用层发回一条消息确认信息才行,这样是在应用层自己写代码来完成的,协议层面根本就不会理会对端有没有收到信息,对于UDP协议来讲,也不能保证消息达到时的顺序性,同时,在UDP的协议下,发送的消息对端能否收到,UDP也无法保证;
那么也正因为,UDP是一个无连接协议,所以它负担较小,从而带来的好处就是效率极高,但是效率高带来的问题就是安全性底,如果我们很在意数据的顺序,或者数据的正确性,UDP可能不是一个很好的选择;
如上图,对于UDP来讲,它不需要listen,不需要accpet,只需要bind就行,也不需要重新创建一个新的Socket消息通道,之后就直接可以进行消息交互了,它没有链接,也不需要等待链接,对端发来,它就接收,所以,它是非常简化的一种信息交互协议,如下;
import socket
server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM) # 监听在IPV4地址是,使用数据报协议
server.bind(("127.0.0.1",8083))
data,client_info=server.recvfrom(1024) # 使用recvfrom能够拿到对端的地址
print(data.decode())
server.sendto("Server Recv Is OK".encode(),client_info) # 使用sendto给指定address回信
server.close()
上述为一个简单的UDP Server,下面利用Mac OS命令行作为客户端,向UDP Server发送数据;
[cce@doorta ~]# echo 'hello udp'|nc -u 127.0.0.1 8083
Server Recv Is OK
# 上面为接受到的回信
UDP信息双向交互
对于Socket的方法来讲,大部分方法UDP可以和TCP共用,但是有的方法默认是无法一起使用的,比如recv,比如connect,因为UDP是一个无链接协议,它与TCP不一样,TCP消息交互是通过一个新的socket来进行的,但是UDP是直接发送的,它并不像TCP那样,使用accpet拿到一个new_socket,然后使用new_socket来进行信息交互,但是也有特殊的情况,在必要的情况下如果我们一定要使用recv或者send也是可以的;
这里分两步,第一步是send,但是如果我们一定要使用send给对端发送消息,就有一个必要的条件,这个必要条件就是在使用send之前,必须调用connect方法,使得当前socket对象链接到对端的address,然后再使用当前的链接对象发送数据到对端;
# Server端
server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
server.bind(("127.0.0.1",8083))
while True:
data,client_info=server.recvfrom(1024) # 等待客户端发送消息
print(data)
server.close()
# b'1' ('127.0.0.1', 58566)
# Client端
server_address=("127.0.0.1",8083)
client=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
client.connect(server_address) # 链接到远端UDP地址,默认情况是不需要connect的,此处进行connect主要是为了我们需要使用send方法
client.send(b"1") # 使用链接后的socket对象来发送消息
client.close()
# (b'1', ('127.0.0.1', 8083))
recv/send
那么对于服务端来讲,也可以直接给这个客户端来回消息,不论是使用sendto还是send都可以,但是服务端给客户端回消息,那就必须需要保证客户端处于recv状态;
# Server端
server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
server.bind(("127.0.0.1",8083))
while True:
data,client_info=server.recvfrom(1024) # 等待客户端发送消息
print(data,client_info)
server.connect(client_info) # 链接到客户端
server.send(data) # 使用链接后到server对象调用send方法发送消息
server.close()
# b'1' ('127.0.0.1', 63898)
# Client
server_address=("127.0.0.1",8083)
client=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
client.connect(server_address) # 链接到远端UDP地址,默认情况是不需要connect的,此处进行connect主要是为了我们需要使用send方法
client.send(b"1") # 使用链接后的socket对象来发送消息
data=client.recv(1024) # 等待服务端发送消息
print(data)
client.close()
# b'1'
其实作为一个UDP无链接协议的客户端能够接受到服务端的消息,主要原因是,客户端其实也占用在一个地址(IP+PORT),从的才导致客户端能够接收到服务端的消息,虽然UDP是一个面向无链接的协议,但是它也是端到端的,所以发送端也得有一个端口;
作为一个客户端,要往服务端发送数据,自己就首先得有一个地址(IP+PORT),虽然作为客户端来讲它是一个临时占用的一个地址(IP+PORT),但是它也占用着这个地址,那么刚好,客户端又一直阻塞在recv这儿,所以,这个地址(IP+PORT)在客户端socket close之前,是一直会被客户端占用的,所以,就可以利用这个临时的地址来给客户端发送消息了;
那么也就是说,任何一个UDP都可以作为Server无论是UDP Server端还是UDP Client端,都可以等待接收和主动发送数据;
sendto这个方法在调用时,会在底层做一件事,它在发送数据的时候,它会首先在本地注册一个随机端口,一注册之后,然后客户端利用这个随机端口,向服务端发送数据,所以说,其实作为客户端的UDP Client,也是通过一个端都向外发发出数据的,并且这个端口在close之前,是不会关闭的,如下;
server_address=("127.0.0.1",8083)
client=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
print(client)
client.sendto(b"1",server_address)
print(client) # 可以通过下述结果看到,直到调用了sendto之后,才会注册一个比较大的随机端口,在未调用sendto之前,端口为0;
client.close()
# <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('0.0.0.0', 0)>
# <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('0.0.0.0', 63820)>
再说socket的recv方法,recv方法要求,必须在本地有一个端口等待着别人发送数据过来,正好sendto给recv注册了这么一个随机端口,所以recv就直接可以使用了,这就是为什么sendto之后使用send可以;
那么为什么send不行呢,因为send一样能够解决自己的,能够在自己本地注册一个随机端口,但是它没办法解决对端问题,也就是说,有了寄件地址,没有收件地址,所以send在这儿出问题了,sendto在本地抢注了一个,并且我们也传入了一个远端端口,所以sendto将寄件地址和收件地址全部解决了,但是sendto并不会记录远端端口,sendto是临时发送,这就是为什么在sendto之后使用send没用;
但是为什么TCP可以呢,因为TCP是一个有链接协议,它永远都知道对端是谁,这就是为什么UDP可以使用getpeername方法,而无链接的UDP不行,这也就是为什么需要使用recvfrom方法的原因了,UDP这种协议,在Python里面不使用recvfrom接收端不可能知道是谁发来的消息,recvfrom和sendto一样,它会自己抢注一个本地地址端口;
- 注意:
只有在调用sendto的时候,才会在本地注册随机端口,只有注册了随机端口,才能主动接受外部的来信,就像发邮件一样,我们得有个寄件地址,UDP协议也是端到端的协议,但是需要知道的是sendto是临时发送,它不会记录对端地址;
connect
然后再说connect,对于connect来说,它的功能更强大,它不仅解决了本地端口,还解决了远端端口,也就是说,在调用connect之后,首先会抢注一个本地端口,然后传入一个远端端口,所以,我们在调用connect对象之后,对于UDP协议来讲,我不仅可以使用recv等待用户消息,还可以使用send向connect返回的socket对象向对端地址发送消息;
# Server端
server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
server.bind(("127.0.0.1",8083))
while True:
data,client_info=server.recvfrom(1024) # 等待客户端发送消息
print(data,client_info)
server.sendto(data,client_info) # 链接到客户端
server.close()
# b'1' ('127.0.0.1', 64514)
# Client端
server_address=("127.0.0.1",8083)
client=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
client.connect(server_address)
client.send(b"1")
print(client.recv(1024))
client.close()
# b'1'
群聊工具开发一
对于UDP协议来讲,它和TCP一样可以实现信息的双向交互,一样可以基于UDP协议完成一个群聊工具的开发,那么有了TCP的基础,对于UDP的群聊来讲,写起来就非常简单了;
但是需要注意的是我们上面也说了,UDP是一个无链接协议,在不链接服务端的情况下,我们也可以直接对服务端发送消息,那么对于此种情况下,客户端最好先connect一下,这样可以直接拿到本地地址,和对端地址,从而可以直接使用send或者recv来发送和接受数据,这样更加的方便,如下,就是一个UDP版本的仅实现单聊的服务端和客户端;
# 服务端
class UDPServer:
def __init__(self,bind_ip="127.0.0.1",bind_port=8083):
self.address=bind_ip,bind_port
self.server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
def start(self):
self.server.bind(self.address)
threading.Thread(target=self.recv,name="recv-thread").start()
def recv(self):
while True:
data,client_address=self.server.recvfrom(1024)
print(data.decode())
self.server.sendto(data,client_address)
server1=UDPServer()
server1.start()
也是考虑到一点,recv会阻塞(虽然此种依旧会阻塞主线程)阻塞主线程,所以我们就将recv方法,直接放到一个线程里面去,让其去阻塞线程,主进程不受影响,但是需要注意,我们的recv单独开辟的这个线程,在这种情况下,不论来多少客户端,都是这一个线程来处理,所以在高并发场景下,我们可以将其改进;
# 客户端
class UDPClient:
def __init__(self, server_ip="127.0.0.1", server_port=8083):
self.address = server_ip, server_port
self.client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
def start(self):
threading.Thread(target=self.recv, name="recv-thread").start()
def stop(self):
self.client.close()
def recv(self):
while True:
try:
data = self.client.recv(1024)
print("\n%s" % data.decode())
except Exception:
break
def send(self, message):
self.client.sendto(message, self.address)
def interaction(self):
while True:
message = input("消息:")
if message == "q":
self.stop()
break
else:
self.send(message.encode())
server1 = UDPClient()
server1.start()
server1.interaction()
对于客户端来讲,我们直接在start之初就使用connect方法链接到远端的server,后面直接调用recv或send方法来给服务端发送和接受消息;
群聊工具开发二
上述代码只能实现一个单聊,如果想实现群聊还差那么一点,就是Server那儿,由Server来进行群聊分发,但是由于UDP是一个无链接协议,所以我们不能像TCP那样,直接把链接对象给存到一个容器里面,那么针对这个容器就很关键了,用列表或者元组是不行的,因为这很可能会出现在遍历的时候,有其他线程对这个容器进行增删改查;
用字典也不行,因为UDP是无链接的,我们只能拿到一个客户端的地址,所以没办法确定一个唯一的主键,TCP使用字典的原因是每个客户端都有一个socket链接对象,并且这个每个对象还都不一样,那么对于UDP就不行了,从头到尾它只有一个socket,所以比较浪费空间,所以这个时候,我们可以考虑使用集合,正好达到去重的效果,因为UDP是无链接的,每发送一个消息都会建立一个链接,如果一个客户端多次发送消息,那么这几次的客户端信息都是一样的,这就是为什么不用列表和元组的原因;
class UDPServer:
def __init__(self,bind_ip="127.0.0.1",bind_port=8083):
self.address=bind_ip,bind_port
self.server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
self.clients=set() # 利用集合来存储当前已链接的客户端信息
self.lock=threading.Lock() # 借助Event来实现阻塞的功能
def start(self):
self.server.bind(self.address)
threading.Thread(target=self.recv,name="recv-thread").start()
def recv(self):
while True:
data,client_address=self.server.recvfrom(1024)
with self.lock:
self.clients.add(client_address)
print('收到客户端消息为:%s'%data.decode())
self.group_recv(data) # 一旦收到消息,就调用群聊方法
def group_recv(self,message):
with self.lock:
for client in self.clients: # 遍历所有客户端,挨个发送消息
self.server.sendto(message,client)
server1=UDPServer()
server1.start()
因为上述代码中,存在多线程数据共享的问题,所以在循环时和TCP一样存在线程安全的问题,因此加入了锁的概念去解决这个线程数据共享的导致的线程安全问题;
群聊工具开发三
上述代码加入了群聊功能,但是这存在一个很大的隐患,这个隐患就是这个曾经建立过链接的客户端信息容器会随着用户量的增大而导致容器元素越来越多,因为服务端是持续不间断运行的,假设现在这个容器里面有100个客户端信息,可能就有1个客户端是在等待客户端回复消息的,其他99个客户端全部都不在线了,那么我们群聊就会做99次无用功;
所以,此处对上述代码进行一个优化,加入正常断开的逻辑,一旦客户端正常断开,就从这个曾经建立过链接的客户端容器里面移除;
class UDPServer:
def __init__(self,bind_ip="127.0.0.1",bind_port=8083):
self.address=bind_ip,bind_port
self.server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
self.clients=set()
self.lock=threading.Lock()
def start(self):
self.server.bind(self.address)
threading.Thread(target=self.recv,name="recv-thread").start()
def recv(self):
while True:
data,client_address=self.server.recvfrom(1024)
if data.decode() == "q": # 一旦收到客户端主动断开,就将其从self.clients中移除
with self.lock: # 因为当前线程中,存在多线程问题,所以在修改self.clients之前,首先对其加锁,避免线程安全问题
if client_address in self.clients: # 如果当前线程在self.clients里面,那么就剔除
self.clients.remove(client_address)
continue
with self.lock:
self.clients.add(client_address)
print('收到客户端消息为:%s'%data.decode())
self.group_recv(data)
def group_recv(self,message):
with self.lock:
for client in self.clients:
self.server.sendto(message,client)
def stop(self):
with self.lock:
self.clients.clear()
self.server.close()
server1=UDPServer()
server1.start()
群聊工具开发四
上述代码加上了正常退出的逻辑,一旦正常退出就将其从self.clients中移除,那么这是正常断开,那么不正常断开的呢,客户端直接关闭了程序,这样,作为服务端就没法捕获了,UDP和TCP不一样,TCP遇到这样的情况,会给服务端发送一个空串,而UDP不会,所以这个时候,我们还得想办法解决这个异常断开时,如何去清理self.clients;
那么此时我们可以加入超时的概念,记录最后一次的通讯时,一旦最后一次通信时间与线程相差多久,那么就从self.clients中移除这个客户端,那么此时,我们再使用set()就不怎么合适了,因为我们还需要记录时间,所以就将set改为dict,客户端作为key,最后一次通讯的时间作为value;
class UDPServer:
def __init__(self,bind_ip="127.0.0.1",bind_port=8083,timeout=10,interval=1.5):
self.address=bind_ip,bind_port
self.server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
self.timeout=timeout # 加入客户端最大超时时长
self.interval=interval # 加入客户端最大超时时长
self.clients=dict()
self.lock=threading.Lock() # 解决线程安全问题
self.event=threading.Event() # 使用event来实现阻塞的功能
def start(self):
self.server.bind(self.address)
threading.Thread(target=self.recv,name="recv-thread").start()
threading.Thread(target=self.clear_expire,name="clear_expire").start() # 开启一个专有线程,用于定时清除过期客户端
def recv(self):
while True:
data,client_address=self.server.recvfrom(1024)
if data.decode() == "q": # 一旦收到客户端主动断开,就将其从self.clients中移除
with self.lock:
if client_address in self.clients:
self.clients.pop(client_address)
continue
with self.lock:
self.clients[client_address]=datetime.datetime.now().timestamp()
print('收到客户端消息为:%s'%data.decode())
self.group_recv(data,client_address)
def group_recv(self,message,current_client):
with self.lock:
for client in self.clients.keys():
if client !=current_client: # 加入逻辑,判断,如果client为当前线程,就不进行发送,也就是说,自己发的消息自己不收
self.server.sendto(message,client)
def clear_expire(self): # 加入客户端超时清除的
while not self.event.wait(self.interval):
data=self.clients.copy() # 将数据深拷贝一份,因为等号引用类型默认是浅拷贝
for key,value in data.items():
if datetime.datetime.now().timestamp()-value > self.timeout:
with self.lock: # 在正式移除之前,先对self.client加锁
if key in self.clients: # 因为当前线程中存在多线程数据共享问题,避免pop的时候,客户端早已不在self.client里面,从而抛出异常
self.clients.pop(key)
print("%s:%s已过期"%key)
def stop(self):
with self.lock:
self.clients.clear()
self.server.close()
server1=UDPServer()
server1.start()
可以看到上述代码,从两个方向着手,首先开启一个专有线程,以异步的方式在后台运行,每隔2秒钟遍历一下self.clients,如果存在已过期的客户端,就从里面删除,此外,每次客户端发小信息,首先判断一下,如果这个客户端主动断开,那么就将其从客户端中移除,由此两步完成客户端正常断开和超时断开两个功能;
C/S心跳
对于上述UDP版本的Server端,在写的过程中,发现很多问题,主要是这种无链接协议给我们带来很多无法确定的因素,比如像,我们不知道需要给多少应该发送的客户端发送群聊信息,所以我们采取的客户端信息记录的手段,但是记录下来之后,我们得知道有哪些客户端已经断开了,那么针对这些已经断开的客户端我们就必要发送了,并且也没必要再继续记录了,因为会这个记录的容器会越来越大;
所以为了解决这个问题,我们采取了超时的方案,超过一个时长,就主观认为客户端已经断开了,其实这种方案,在这种无链接协议当中,或者说没有办法明确的知道对端何时离开时,都是用超时的办法来解决的,甚至很多协议也是如此实现的,因为主动不说再见,我们就只能主观的认为它确实是断开了;
所以,上面一直都是解决客户端正常断开和超时断开的问题,确实也已经解决了,但是如果客户端不主动告知服务端,客户端就直接关闭了应用程序,但是也没有达到预定的超时时间,这种场景下,依然存在问题,所以此处我们就需要接入心跳检测机制或者ACK机制,在双方建立一个心跳检测机制,每隔长的时间周期告知对端,自己还存活,从而去解决客户端异常断开的问题;
那么接入心跳就有几个方案了,方案一,客户端给服务器主动发送心跳,这种方案如果有上千个客户端每个一秒或者多少秒给服务器发送心跳,这个压力也不小,但是目前来看是压力最小的一种,这种方案服务端可以选择回复一下确认信息,也可以选择不回应;
方案二服务端主动发送心跳给客户端,那么此种方案,就需要加一个ACK了,当服务端发送了心跳之后,当客户端收到这个心跳回复一个确认信息;
方案三,双方互发,针对目前的应用常见下,目前来看没有必要,有一千个客户端轮训给服务端发送,服务器还要给这一千个客户端也发送,服务器只有一个,因为如果这样它的压力会越来越大,浪费带宽,浪费CPU时间;
方案一的实现
如下,就是一个实现心跳的服务端,使用的是方案一,服务端开辟一个新的socket作为心跳信息的专用通道,然后让客户端每隔一个时间周期主动给服务端发送心跳信息,那么当服务端收到这个心跳信息之后,就将此客户端的最后发送消息的时间,更新为当前时,使其不会超时,从而导致从容器中清除;
这里需要注意的是,这个心跳信息的信息内容,我们需要定制化,专用化,最好用一些让人难以猜测到的特殊字符,比如md5码,等等,主要是为了避免恶意攻击等;
class UDPServer:
def __init__(self,bind_ip="127.0.0.1",bind_port=8083,heartbeat_bind_ip="127.0.0.1",heartbeat_bind_port=8084,timeout=10,interval=1.5):
self.address=bind_ip,bind_port
self.heartbeat_address=heartbeat_bind_ip,heartbeat_bind_port
self.heartbeat_server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
self.server=socket.socket(family=socket.AF_INET,type=socket.SOCK_DGRAM)
self.timeout=timeout
self.interval=interval
self.clients=dict()
self.lock=threading.Lock() # 解决线程安全问题
self.event=threading.Event() # 使用event来实现阻塞的功能
def start(self):
self.server.bind(self.address)
threading.Thread(target=self.recv,name="recv-thread").start()
threading.Thread(target=self.clear_expire,name="clear_expire").start()
threading.Thread(target=self.heartbeat,name="heartbeat").start()
def recv(self):
while True:
data,client_address=self.server.recvfrom(1024)
if data.decode() == "q":
with self.lock:
if client_address in self.clients:
self.clients.pop(client_address)
continue
with self.lock:
self.clients[client_address]=datetime.datetime.now().timestamp()
print('收到客户端消息为:%s'%data.decode())
self.group_recv(data,client_address)
def group_recv(self,message,current_client):
with self.lock:
for client in self.clients.keys():
if client !=current_client:
self.server.sendto(message,client)
def clear_expire(self):
while not self.event.wait(self.interval):
data=self.clients.copy()
for key,value in data.items():
if datetime.datetime.now().timestamp()-value > self.timeout:
with self.lock:
if key in self.clients:
self.clients.pop(key)
print("%s:%s已过期"%key)
def heartbeat(self): # 实现心跳的方法
self.heartbeat_server.bind(self.heartbeat_address) # 开启一个新的UDP,作为心跳专用通道
while True:
data,client_info=self.heartbeat_server.recvfrom(1024)
if data.decode() == "^heartbeat$": # 一旦接受到此字符串,就将发送这条消息的客户端的最后一次时间更新为当前时间
with self.lock:
self.clients[client_info]=datetime.datetime.now().timestamp()
def stop(self):
with self.lock:
self.clients.clear()
self.server.close()
server1=UDPServer()
server1.start()
如下为实现心跳的一个客户端,作为客户端来讲,每隔一个周期,主动向服务端通告一条心跳信息,需要注意的是,这个心跳信息,我们需要定制化,比如和服务端协商一个特殊字符,然后将这个特殊字符作为心跳信息,同时借助Event来达到阻塞的效果;
class UDPClient:
def __init__(self, server_ip="127.0.0.1",heartbeat_ip="127.0.0.1",heartbeat_port=8084, server_port=8083):
self.address = server_ip, server_port
self.heartbeat_address = heartbeat_ip, heartbeat_port
self.client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
self.event=threading.Event()
self.interval=random.random()*10 # 利用随机数来获取发送心跳的间隔时间,这样就避免在同一个时间,所有人都往服务器发送心跳,从而导致高并发
def start(self):
threading.Thread(target=self.recv, name="recv-thread").start()
threading.Thread(target=self.heartbeat, name="heartbeat-thread").start()
def stop(self):
self.client.close()
def recv(self):
while True:
try:
data = self.client.recv(1024)
print("\n%s" % data.decode())
except Exception:
break
def heartbeat(self): # 心跳专用方法,每隔self.interval一个周期,向服务端发送心跳信息
while not self.event.wait(self.interval):
self.client.sendto("^heartbeat$".encode(),self.heartbeat_address)
def send(self, message):
self.client.sendto(message, self.address)
def interaction(self):
while True:
message = input("消息:")
self.send(message.encode())
if message == "q":
self.stop()
break
server1 = UDPClient()
server1.start()
server1.interaction()
UDP应用场景
UDP协议是无链接协议,当初在使用这种协议时,都是基于几点,第一,网络足够好,第二,消息不会丢包,第三,包不会乱序,因为是局域网嘛,但是在局域网中用也不能不丢包,这还要看,我们这个局域网的拓扑,偶尔也会丢包,而且包也不一定有序;
也就是说,我们是否能够容忍丢包,要是不能容忍还是得用TCP,那广域网就不用想了,上了互联网丢包就更严重了,实际上现在我们访问国外的网站,有的时候,速度慢,其实我们会发现大量的包都丢完了,都是重发的,所以感觉会特别慢,所以在目前这个场景下,如果我们对数据的完整性,安全性要求比较高的情况下,我们还是推荐使用TCP,如果我们传输音频、视频这些文件,目前还是会采用UDP协议,因为我们不在于丢那么一帧两帧的数据;
TCP和UDP协议没有绝对的谁好谁坏,要根据具体的场景去判断应该使用哪一个协议,TCP应用于可靠传输,工做效率、实时性要求不高,可是服务器性能要求相对较高的场景。好比文件传输、接收邮件、远程登陆等,那么对于UDP来讲,它一般应用于工做效率和实时性要求高,可是不要求可靠传输的场景,如早期QQ、视频、语音电话、广播通讯等;