在上一篇文章中,非常详细的叙述了有连接和无连接的区别,并严格的定义了什么是有连接。然后讲解了使用 connect 函数将 UDP 套接字设置成有连接的。
本文我们继续讨论有连接和无连接 UDP 的区别。
1. 多次调用 connect 函数
上一讲我们已经知道 connect 函数可以把 UDP 套接字设置成有连接的。但是如果多次调用 connect 会怎样呢?主要有以下两个目的:
- 重新指定 peername
- 断开连接,即删除 peername(也可能会删除 sockname)
对于第一个目的来说,很简单,只要设置好正确的套接字地址,传参给 connect 即可。
对于第二个目的来说,需要将套接字地址结构体 struct sockaddr 中的 sin_family 成员设置成 AF_UNSPEC,见下面的代码.
struct sockaddr_in disconnaddr; memset(&disconnaddr, 0, sizeof(disconnaddr)); disconnaddr.sin_family = AF_UNSPEC; // 断开连接 connect(sockfd, &disconnaddr, sizeof(disconnaddr));
2. 有连接与无连接 UDP 的性能
2.1 无连接
在无连接 UDP 套接字上调用 sendto 时,Berkele 的内核会暂时连接该套接字, 发送数据报,然后断开该连接。比如下面这断代码:
// 连接两次调用 sendto sendto(sockfd, buf, 100, 0, &servaddr, sizeof(servaddr)); sendto(sockfd, buf, 200, 0, &servaddr, sizeof(servaddr));
它会执行下面 6 个步骤:
- 连接套接字
- 发送第一个数据报
- 断开套接字连接
- 连接套接字
- 发送第二个数据报
- 断开套接字连接
第一次连接需要为目的 IP 地址搜索路由表,并进行缓存。第二次连接,会直接到缓存中取。
2.2 有连接
同样的,对于 2.1 中的代码来说,有连接套接字是这样做的:
connect(sockfd, &servaddr, sizeof(servaddr)); write(sockfd, buf, 100); write(sockfd, buf, 200);
它会执行下面的步骤:
- 连接套接字
- 发送第一个数据报
- 发送第二个数据报
这种情况下,内核只复制一次含有目的 ip 和 port 的套接字地址,而使用 sendto 时,需要复制两次。文献 [1] 指出,临时连接未连接的 UDP 套接字大约会耗费每个 UDP 传输三分之一的开销。