前言

UDP协议是除TCP以外,最为常见的协议,一般用于DNS解析、P2P通信等,目前比较火的QUIC协议也是基于UDP的。

UDP协议是一种无连接的、不可靠的协议,不需要维护状态,处理速度较快,但是需要使用者自己解决丢包、重传、排序等问题。

由于UDP协议在NAT打洞上的成功率高于TCP协议,而且UDP协议在同等网络质量下可以获得更高的通信速率,因此绝大多数的P2P程序都是使用了UDP协议。

使用python进行UDP通信和进行TCP通信差别不是太大。

创建UDP服务端

1. 1. 创建socket对象

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

这一步与在TCP中的唯一差别,在于将socket.SOCK_STREAM改成了socket.SOCK_DGRAM,表示使用UDP协议。

2. 2. 绑定监听端口

s.bind(('0.0.0.0', 8000))

这步操作与TCP通信是一致的,唯一的差别是UDP中不需要进行listen操作就可以直接使用了。这是因为UDP没有连接,不像TCP需要进行三次握手。

创建UDP客户端

由于UDP不需要进行连接操作,因此只需要进行创建socket对象后,就可以直接发送数据了。

客户端和服务端收发数据

1. 1. 接收数据

服务端由于进行了bind操作,因此可以直接接收数据。

buff, addr = s.recvfrom(4096)

参数含义与TCP的recv函数一致。返回值是(buff, addr)二元组,buff是接收到的数据,addr是发送方的(ip, port)二元组。

但是客户端如果直接调用recvfrom会报错,因为没有绑定任何端口。例如Windows会报如下错误:

socket.error: [Errno 10022]

客户端只需要先向服务端发送一次数据,底层就会临时绑定一个端口,此时调用recvfrom就正常了。

2. 2. 发送数据

s.sendto(buff, (ip, port))

由于没有进行连接,发送数据需要指定服务端的地址和端口。从这里可以看出,一个UDP的socket对象,是可以向多个服务端发送数据的;而在TCP中一旦建立连接,通信双方就已经确定了。

3. 3. 关闭socket对象

s.close()

这步是和TCP一致的,差别在于TCP会关闭连接,而UDP只是释放资源。

简单的例子

1. 服务端代码

def create_server(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(('0.0.0.0', port))
    while True:
        buffer, addr = s.recvfrom(4096)
        print('Recv %r from %s:%d' % (buffer, addr[0], addr[1]))
        if buffer == b'exit':
            break
        s.sendto(buffer, addr)
    s.close()

2. 客户端代码

def create_client(port):
    addr = ('127.0.0.1', port)
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.sendto(b'Hello python!!!', addr)
    buffer, _ = s.recvfrom(4096)
    print('Recv %r from server' % buffer)
    s.sendto(b'exit', addr)
    s.close()

3. 结果显示如下

服务端:

Recv 'Hello python!!!' from 127.0.0.1:56618
Recv 'exit' from 127.0.0.1:56618

客户端:

Recv 'Hello python!!!' from server

使用有连接的方式通信

事实上,UDP也可以使用和TCP相同的方式进行通信。服务端代码一致,客户端代码修改为:

def create_client_with_connection(port):
    addr = ('127.0.0.1', port)
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(addr)
    s.send(b'Hello python!!!')
    buffer = s.recv(4096)
    print('Recv %r from server' % buffer)
    s.send(b'exit')
    s.close()

效果完全一致。需要注意的是:这里的connect并不是真的连接,只是底层做了套接字与端口的绑定。由于此时服务端并不知道客户端的地址,因此无法直接向客户端发送数据。而客户端在connect之后直接调用recv也会一直处于阻塞状态,因此还是需要先调用一次send,才能真正建立与服务端的通信。

这种用法比较适合那些客户端只与固定服务端通信的场景,可以让代码更简洁。

drunkdream.cn 版权所有 粤ICP备17153329号 all right reserved,powered by Gitbook该文件修订时间: 2021-01-07 18:27:03

results matching ""

    No results matching ""