1 UDP协议

UDP是面向无连接的协议,使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

2 UDP通信流程

我们先来了解一下,python的socket的通讯流程:

服务端:

  1. 创建Socket对象
  2. 绑定IP地址Address和端口Port,使用bind方法,IPv4地址为一个二元组('IP',Port),一个UDP端口只能被绑定一次
  3. 接受数据,recvfrom方法,使用缓冲区接受数据
  4. 发送数据,sendto方法,类型为bytes
  5. 关闭连接

客户端:

  1. 创建Socket对象
  2. 连接服务端。connect方法(可选)
  3. 发送数据,sendto/send方法,类型为bytes
  4. 接受数据,recvfrom/recv方法,使用缓冲区接受数据
  5. 关闭连接

我们可以看到UDP不需要维护一个连接,所以比较简单

3 UDP编程

        使用udp编程和使用tcp编程用于相似的步骤,而因为udp的特性,它的服务端不需要监听端口,并且客户端也不需要事先连接服务端。根据上图,以及建立服务端的流程,我门来捋一下服务端的逻辑到代码的步骤:

3.1 构建服务端

  1. 创建服务端
socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# socke.AF_INET 指的是使用 IPv4
# socket.SOCK_STREAM 指定使用面向数据报的UDP协议
  1. 绑定IP地址和端口。
socket.bind(('127.0.0.1',999))
# 小于1024的端口只有管理员才可以指定
  1. 接受数据(阻塞)
data, client_info = sock.recvfrom(1024)
# 返回一个元组,数据和客户端的地址,因为UDP没有连接,所以只能通过提取消息的发送的源地址,才能在应答时指定对方地址
  1. 发送数据
sock.sendto('data'.encode(),('127.0.0.1',999)) # bytes格式
# 第二个参数为客户端地址
  1. 关闭连接
sock.close()

完成的代码:

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)      # 指定socket的协议,UDP使用的是SOCK_DGRAM
server.bind(('127.0.0.1', 9999)) # 绑定端口 print('UDP Server is Starting...')
data, addr = server.recvfrom(1024) # 接受(包含数据以及客户端的地址)
print('Received from {}'.format(addr))
server.sendto('hello,{}'.format(addr).encode('utf-8'), addr) # 应答,格式为(应答的数据,客户端的IP和Port元组)

为什么要使用recvfrom/sendto?

  1. UDP无连接的特性,当服务端收到一条消息时,不会为它维护一个socket的,那么如何应答呢?
  2. UDP报文中包含对方的IP和Port信息,使用recvfrom,就会返回对方发送的数据和对方的地址
  3. sendto由于没有socket的特性,所以应答时也需要传递client的地址和端口

3.2 构建客户端

  1. 创建客户端
socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# socke.AF_INET 指的是使用 IPv4
# socket.SOCK_STREAM 指定使用面向数据报的UDP协议
  1. 添加服务端地址信息(可选)。
socket.connect(('127.0.0.1',999))
# UDP不会创建连接,所以这里仅仅是在socket上添加了本段/对端的地址而已,并不会发起连接
  1. 接受数据(阻塞)
data, client_info = sock.recv(1024)
# 返回一个元组,数据和客户端的地址,因为UDP没有连接,所以只能通过提取消息的发送的源地址,才能在应答时指定对方地址
  1. 发送数据
sock.sendto('data'.encode(),('127.0.0.1',999)) # bytes格式
# 第二个参数为客户端地址
  1. 关闭连接
sock.close()

为什么connect是可选的?

  1. 当执行connect时,由于UDP的特性,并不会为我们创建连接,这里仅仅是在socket上添加了对端的地址而已,并不会发起连接
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print(client) # <socket.socket fd=140, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0>
client.connect(('127.0.0.1', 9999))
print(client) # <socket.socket fd=140, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 51859), raddr=('127.0.0.1', 9999)>
  1. 如果不执行connect,那么在使用send发生时,就无法知道对端的IP地址,那么只能使用sendto来指定了。
  2. 为什么接收时使用recv,因为是client,只会有server应答消息,所以就不需要来区分了。
  3. 如果指定了connect,sendto已久可以发给任意终端,但recv只能接受connect指定的对端,发来的消息。

完整的代码:

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)            # 指定socket的协议,UDP使用的是SOCK_DGRAM
client.sendto('hello world'.encode('utf-8'), ('127.0.0.1', 9999)) # 发送数据,格式为(发送的数据,服务端的IP和Port元组)
print(client.recv(1024).decode('utf-8')) # 同样使用recv来接受服务端的应答数据

UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。 

3.3 常用方法

服务器端套接字:

函数 描述
s.bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。

客户端套接字:

函数 描述
s.connect() 初始化UDP连接对象的,本段/对端地址。
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数:

函数 描述
s.recv() 接收TCP/UDP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send() 发送TCP/UDP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close() 关闭套接字
s.fileno() 返回套接字的文件描述符。
s.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile() 创建一个与该套接字相关连的文件

4 聊天室

下面来模仿上一篇TCP版本的聊天室的结构来创建一个UDP版本的聊天室

服务端:

import socket
import threading
import datetime
import logging FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatUDPServer: def __init__(self, ip, port):
self.ip = ip
self.port = port
self.event = threading.Event()
self.clients = {}
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) def start(self):
self.sock.bind((self.ip, self.port))
threading.Thread(target=self.recv, name='start').start() def recv(self):
while not self.event.is_set():
# 待清理的列表
clean = set() # 远程主机关闭连接时,这里会触发异常。不知道为啥
try:
data, client_addr = self.sock.recvfrom(1024)
except ConnectionResetError:
continue if data.upper() == 'quit' or data == b'':
self.clients.pop(client_addr)
logging.info(client_addr, 'is down')
continue # 心跳包,内容越小越好
if data.lower() == b'@im@':
self.clients[client_addr] = datetime.datetime.now().timestamp()
continue logging.info('{}:{} {}'.format(*client_addr, data.decode()))
self.clients[client_addr] = datetime.datetime.now().timestamp()
msg = "{}:{} {}".format(*client_addr, data.decode()).encode()
current = datetime.datetime.now().timestamp()
for client, date in self.clients.items():
# 如果10s内没有发送心跳包,则进行清理
if current - date > 10:
clean.add(client)
else:
self.sock.sendto(msg, client) # 清理超时连接
for client in clean:
self.clients.pop(client) def stop(self):
self.event.set()
self.sock.close() if __name__ == '__main__':
cus = ChatUDPServer('127.0.0.1', 9999)
cus.start() while True:
cmd = input('>>>>: ').strip()
if cmd.lower() == 'quit':
cus.stop()
break
else:
print(threading.enumerate())

客户端:

import socket
import threading
import logging FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatUDPClient: """
self.ip: 服务端地址
self.port:服务端端口
self.socket:创建一个socket对象,用于socket通信
self.event:创建一个事件对象,用于控制链接循环
""" def __init__(self, ip, port):
self.ip = ip
self.port = port
self.socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
self.event = threading.Event() def connect(self):
self.socket.connect((self.ip, self.port))
threading.Thread(target=self.recv, name='recv',daemon=True).start()
threading.Thread(target=self._heart,name='heart',daemon=True).start() def _heart(self):
while not self.event.wait(5):
data = '@im@'
self.send(data) def recv(self):
while not self.event.is_set(): # 某些服务端强制关闭时,会出b'',这里进行判断
try:
data = self.socket.recv(1024)
if data == b'':
self.event.set()
logging.info('{}:{} is down'.format(self.ip, self.port))
break
logging.info(data.decode()) # 有些服务端在关闭时不会触发b'',这里会直接提示异常,这里进行捕捉
except (ConnectionResetError,OSError):
self.event.set()
logging.info('{}:{} is down'.format(self.ip, self.port)) def send(self, msg):
self.socket.send(msg.encode()) def stop(self):
self.send('quit')
self.socket.close() if __name__ == '__main__':
ctc = ChatUDPClient('127.0.0.1', 9999)
ctc.connect() while True:
info = input('>>>>:').strip()
if not info: continue
if info.lower() == 'quit':
logging.info('bye bye')
ctc.stop()
break
if not ctc.event.is_set():
ctc.send(info)
else:
logging.info('Server is down...')
break

5 UDP协议应用

UDP是无连接协议,它基于以下假设:

  • 网络足够好
  • 消息不会丢包
  • 包不会乱序

但是,即使在局域网,也不能保证不丢包,而且包的到达不一定有序。

应用场景:

  1. 视频音频传输,一般来说,丢些包,问题不大,最多丢些图像,听不清话语。
  2. 海量采集数据,例如传感器发来的数据,丢几十、几百条数据也没有关系。
  3. DNS协议,数据内容小,一个包就能查到结果,不存在乱序,丢包时重新请求解析即可。

一般来说,UDP性能优于TCP,但是可靠性要求高的场合还是要选择TCP协议。

37 - 网络编程-UDP编程的更多相关文章

  1. Socket网络编程-UDP编程

    Socket网络编程-UDP编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.UDP编程概述 1>.UDP服务端编程流程 创建socket对象.socket.SOCK_ ...

  2. 五十六、linux 编程——UDP 编程模型

    56.1 UDP 编程模型 56.1.1 编程模型 UDP 协议称为用户数据报文协议,可靠性比 TCP 低,但执行效率高 56.1.2 API (1)发送数据 函数参数: sockfs:套接字文件描述 ...

  3. UNIX网络编程——UDP编程模型

    使用UDP编写的一些常见得应用程序有:DNS(域名系统),NFS(网络文件系统)和SNMP(简单网络管理协议). 客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目 ...

  4. 网络编程——UDP编程

    一个简单的聊天代码:运行结果: 在这个程序之中,由于recvfrom函数拥塞函数,没有数据时会一直阻塞,所以客户端和服务器端只能通过一回一答的方式进行信息传递.严格的讲UDP没有明确的客户端和服务端, ...

  5. 五十五 网络编程 UDP编程

    TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包.但是,能不 ...

  6. 五十八、linux 编程——UDP 编程 广播

    58.1 广播介绍 58.1.1 介绍 广播实现一对多的通讯 它通过向广播地址发送数据报文实现的 58.1.2 套接字选项 套接字选项用于修饰套接字以及其底层通讯协议的各种行为.函数 setsocko ...

  7. 五十七、linux 编程——UDP 编程 域名解析

    57.1 介绍 57.1.1 域名解析 57.1.2 域名解析函数 gethostent 可以获取多组,gethostbyname 只可以获取一组 /etc/hosts 文件设置了域名和 IP 的绑定 ...

  8. [C# 网络编程系列]专题六:UDP编程

    转自:http://www.cnblogs.com/zhili/archive/2012/09/01/2659167.html 引用: 前一个专题简单介绍了TCP编程的一些知识,UDP与TCP地位相当 ...

  9. 网络编程之UDP编程

    网络编程之UDP编程 UDP协议是一种不可靠的网络协议,它在通信的2端各建立一个Socket,但是这个Socket之间并没有虚拟链路,这2个Socket只是发送和接受数据的对象,Java提供了Data ...

随机推荐

  1. python的N个小功能(文本字段对应数值,经纬度计算距离,两个时间点计算时间间隔)

    案例1 >>> import pandas as pd >>> df=pd.DataFrame({'A':[1,2,3],'B':[1,2,3],'C':[1,2, ...

  2. 使用android资源

    1.我们可以命名的资源种类有多少? 答: res/anim/ XML文件,它们被编译进逐帧动画(frame by frame animation)或补间动画(tweened animation)对象 ...

  3. P2891 [USACO07OPEN]吃饭Dining(最大流+拆点)

    题目描述 Cows are such finicky eaters. Each cow has a preference for certain foods and drinks, and she w ...

  4. 【BZOJ2780】【SPOJ】Sevenk Love Oimaster(后缀自动机)

    [BZOJ2780][SPOJ]Sevenk Love Oimaster(后缀自动机) 题面 BZOJ 洛谷 题解 裸的广义后缀自动机??? 建立广义后缀自动机建立出来之后算一下每个节点被几个串给包括 ...

  5. MapReduce(四) 典型编程场景(二)

    一.MapJoin-DistributedCache 应用 1.mapreduce join 介绍 在各种实际业务场景中,按照某个关键字对两份数据进行连接是非常常见的.如果两份数据 都比较小,那么可以 ...

  6. poj4052 Hrinity

    pdf题面:传送门 题目大意:给定一些单词和一个句子,问有多少个单词在句子中出现过,如果一个但单词包含另一个单词,并且两个单词都出现过,那么只算最外层的单词(包含另一个单词的单词). 分析:这道题如果 ...

  7. 【mysql】测试方案整理

    http://www.cnblogs.com/callhe/ https://www.digitalocean.com/community/tutorials/how-to-measure-mysql ...

  8. Google protocol buffer的配置和使用(Linux&&Windows)

    最近自己的服务器做到序列化这一步了,在网上看了下,序列化的工具有boost 和google的protocol buffer, protocol buffer的效率和使用程度更高效一些,就自己琢磨下把他 ...

  9. ubuntu如何杀死进程

    一.得到所有进程 先用命令查询出所有进程 ps -ef 二.杀死进程 我们使用ps -ef命令之后,就会得到一些列进程信息,有进程pid什么的,如果你要杀死莫个进程的话,直接使用命令   kill   ...

  10. 《Apache HttpClient 4.3开发指南》

    转载自:http://blog.csdn.net/chszs/article/details/16854747 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chs ...