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. Maven学习——1、安装与修改Maven的本地仓库路径

    1.1.下载 官网 http://maven.apache.org/download.cgi 1.2.安装配置 apache-maven-3.3.3-bin.zip 解压下载的压缩包 1.3.配置环境 ...

  2. [BinaryTree] 二叉树常考知识点

    1.二叉树第i层至多有2^(i-1)个结点(i>=1). 2.深度为k的二叉树上,至多含2^k-1个结点(k>=1) 3.n0 = n2 + 1(度) 4.满二叉树:深度为k且含有2^k- ...

  3. uoj54-bzoj3434-时空穿梭

    题意 在一个 \(n\) 维空间中,求一个点可以用一个 \(n\) 维向量 \((x_1,x_2,\dots x_n)\) 表示.现在要选出 \(c\) 个点,有三个限制: 设 \(x_i\) 表示任 ...

  4. bzoj5123 [Lydsy12月赛]线段树的匹配

    题意: 线段树是这样一种数据结构:根节点表示区间 [1, n]:对于任意一个表示区间 [l, r] 的节点,若 l < r, 则取 mid = ⌊l+r/2⌋,该节点的左儿子为 [l, mid] ...

  5. python写BMI指数菜单

    需求: # 1.创建并输出菜单, 菜单是不可变的. 所以使用元组menus = ("1, 录入", "2, 查询", "3, 删除", &q ...

  6. CF878C Tournament set 图论

    题面 题面 题解 如果2个人可以互相战胜,那么我们连一条无向边,于是最后会剩下t个联通块,其中每对联通块之间都有严格的大小关系(a.max < b.min),因此我们每插入一个点就相当于合并一段 ...

  7. [SDOI2011]黑白棋 kth - nim游戏

    题面 题面 题解 观察题目,我们可以发现,这个游戏其实就是不断再把对方挤到一边去,也就是黑子不断往左走,白子不断往右走. 因此可以发现,如果将黑白子按顺序两两配对,那么它们中间的距离会不断缩小,且每次 ...

  8. Unity3D动态加载外部MovieTexture视频

    网上大家也写了很多Unity3D中播放视频的教程,关于播放外部视频的还是比较少,所以写这篇文章,不足之处,还望读者指正. 在Unity3D中,我们一般使用播放视频的方法:将*.mov,*.mp4等格式 ...

  9. linux 递归删除目录文件

    比如删.svn文件 >find . -name ".svn" | xargs -exec rm -rf

  10. ping: unknown host 解决办法

    如果ping命令返回如下错误,那主要的可能性就是系统的DNS设置有误. [root@CentOS5 ~]# ping www.sina.com.cn ping: unknown host www.si ...