《UNIX网络编程 卷1:套接字联网API(第3版)》——8.11 UDP的connect函数

简介: 给connect函数重载(overload)UDP套接字的这种能力容易让人混淆。如果使用约定,令sockname是本地协议地址,peername是外地协议地址,那么更好的名字本该是setpeername。类似地,bind函数更好的名字本该是setsockname。

本节书摘来自异步社区《UNIX网络编程 卷1:套接字联网API(第3版)》一书中的第8章,第8.11节,作者:【美】W. Richard Stevens , Bill Fenner , Andrew M. Rudoff著,更多章节内容可以访问云栖社区“异步社区”公众号查看

8.11 UDP的connect函数

在8.9节结尾我们提到,除非套接字已连接,否则异步错误是不会返回到UDP套接字的。我们确实可以给UDP套接字调用connect(4.3节),然而这样做的结果却与TCP连接大相径庭:没有三路握手过程。内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号(取自传递给connect的套接字地址结构),然后立即返回到调用进程。

给connect函数重载(overload)UDP套接字的这种能力容易让人混淆。如果使用约定,令sockname是本地协议地址,peername是外地协议地址,那么更好的名字本该是setpeername。类似地,bind函数更好的名字本该是setsockname。

有了这个能力后,我们必须区分:

未连接UDP套接字(unconnected UDP socket),新创建UDP套接字默认如此;
已连接UDP套接字(connected UDP socket),对UDP套接字调用connect的结果。
对于已连接UDP套接字,与默认的未连接UDP套接字相比,发生了三个变化。

(1)我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto,而改用write或send。写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(例如IP地址和端口号)。

其实我们可以给已连接UDP套接字调用sendto,但是不能指定目的地址。sendto的第五个参数(指向指明目的地址的套接字地址结构的指针)必须为空指针,第六个参数(该套接字地址结构的大小)应该为0。POSIX规范指出当第五个参数是空指针时,第六个参数的取值就不再考虑。

(2)我们不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。目的地为这个已连接UDP套接字的本地协议地址(例如IP地址和端口号),发源地却不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字。这样就限制一个已连接UDP套接字能且仅能与一个对端交换数据报。

确切地说,一个已连接UDP套接字仅仅与一个IP地址交换数据报,因为connect到多播或广播地址是可能的。

(3)由已连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不接收任何异步错误。

图8-14就4.4BSD总结了上列第一点。

screenshot

POSIX规范指出,在未连接UDP套接字上不指定目的地址的输出操作应该返回ENOTCONN,而不是EDESTADDRREQ。

图8-15总结了我们给已连接UDP套接字归纳的三点。

screenshot

应用进程首先调用connect指定对端的IP地址和端口号,然后使用read和write与对端进程交换数据。

来自任何其他IP地址或端口的数据报(图8-15中我们用“???”表示)不投递给这个已连接套接字,因为它们要么源IP地址要么源UDP端口不与该套接字connect到的协议地址相匹配。这些数据报可能投递给同一个主机上的其他某个UDP套接字。如果没有相匹配的其他套接字,UDP将丢弃它们并生成相应的ICMP端口不可达错误。

作为小结,我们可以说UDP客户进程或服务器进程只在使用自己的UDP套接字与确定的唯一对端进行通信时,才可以调用connect。调用connect的通常是UDP客户,不过有些网络应用中的UDP服务器会与单个客户长时间通信(如TFTP),这种情况下,客户和服务器都可能调用connect。

DNS提供了另一个例子,如图8-16所示。

screenshot

通常通过在/etc/resolv.conf文件中列出服务器主机的IP地址,一个DNS客户主机就能被配置成使用一个或多个DNS服务器。如果列出的是单个服务器主机(图中最左边的方框),客户进程就可以调用connect,但是如果列出的是多个服务器主机(图中从右边数第二个方框),客户进程就不能调用connect。另外DNS服务器进程通常是处理客户请求的,因此服务器进程不能调用connect。

8.11.1 给一个UDP套接字多次调用connect
拥有一个已连接UDP套接字的进程可出于下列两个目的之一再次调用connect:

指定新的IP地址和端口号;
断开套接字。
第一个目的(即给一个已连接UDP套接字指定新的对端)不同于TCP套接字中connect的使用:对于TCP套接字,connect只能调用一次。

为了断开一个已UDP套接字连接,我们再次调用connect时把套接字地址结构的地址族成员(对于IPv4为sin_family,对于IPv6为sin6_family)设置为AF_UNSPEC。这么做可能会返回一个EAFNOSUPPORT错误(TCPv2第736页),不过没有关系。使套接字断开连接的是在已连接UDP套接字上调用connect的进程(TCPv2第787~788页)。

各种Unix变体断开套接字上连接的方式存在差异,同样的方法可能适合某些系统而不适合其他系统。举例来说,以空的套接字地址结构指针调用connect的方法仅仅适合某些系统(而在另一些系统上,要求第三个参数即套接字地址结构长度为非0)。POSIX规范和BSD手册页面在此帮助不大,只是提到必须使用一个空地址(null address),而根本没有提到出错返回值(甚至成功返回值也没有提到)。最便于移植的解决办法就是清零一个地址结构后把它的地址族成员设置为AF_UNSPEC,再把它传递给connect。

另一个存在差异的地方是断开连接前后套接字本地绑定地址的取值。AIX保留被选中的本地IP地址和端口号,即使它们起源于隐式捆绑。FreeBSD和Linux把本地IP地址设置回全0,即使早先调用过bind,端口号也保持不变。Solaris在隐式捆绑时把本地IP地址设置回全0,在显式调用过bind时保持IP地址不变。

8.11.2 性能
当应用进程在一个未连接的UDP套接字上调用sendto时,源自Berkeley的内核暂时连接该套接字,发送数据报,然后断开该连接(TCPv2第762~763页)。在一个未连接的UDP套接字上给两个数据报调用sendto函数于是涉及内核执行下列6个步骤:

连接套接字;
输出第一个数据报;
断开套接字连接;
连接套接字;
输出第二个数据报;
断开套接字连接。
另一个考虑是搜索路由表的次数。第一次临时连接需为目的IP地址搜索路由表并高速缓存这条信息。第二次临时连接注意到目的地址等于已高速缓存的路由表信息的目的地(我们假设这两个sendto调用有相同的目的地址),于是就不必再次查找路由表(TCPv2第737~738页)。

当应用进程知道自己要给同一目的地址发送多个数据报时,显式连接套接字效率更高。调用connect后调用两次write涉及内核执行如下步骤:

连接套接字;
输出第一个数据报;
输出第二个数据报。
在这种情况下,内核只复制一次含有目的IP地址和端口号的套接字地址结构,相反当调用两次sendto时,需复制两次。[Partridge和Pink 1993]指出,临时连接未连接的UDP套接字大约会耗费每个UDP传输三分之一的开销。

相关文章
|
6月前
|
人工智能 算法 安全
OpenRouter 推出百万 token 上下文 AI 模型!Quasar Alpha:提供完全免费的 API 服务,同时支持联网搜索和多模态交互
Quasar Alpha 是 OpenRouter 推出的预发布 AI 模型,具备百万级 token 上下文处理能力,在代码生成、指令遵循和低延迟响应方面表现卓越,同时支持联网搜索和多模态交互。
463 1
OpenRouter 推出百万 token 上下文 AI 模型!Quasar Alpha:提供完全免费的 API 服务,同时支持联网搜索和多模态交互
|
8月前
|
网络协议 算法 安全
Go语言的网络编程与TCP_UDP
Go语言由Google开发,旨在简单、高效和可扩展。本文深入探讨Go语言的网络编程,涵盖TCP/UDP的基本概念、核心算法(如滑动窗口、流量控制等)、最佳实践及应用场景。通过代码示例展示了TCP和UDP的实现,并讨论了其在HTTP、DNS等协议中的应用。最后,总结了Go语言网络编程的未来发展趋势与挑战,推荐了相关工具和资源。
194 5
|
11月前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
278 3
|
12月前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
314 10
|
12月前
|
人工智能 搜索推荐 API
用于企业AI搜索的Bocha Web Search API,给LLM提供联网搜索能力和长文本上下文
博查Web Search API是由博查提供的企业级互联网网页搜索API接口,允许开发者通过编程访问博查搜索引擎的搜索结果和相关信息,实现在应用程序或网站中集成搜索功能。该API支持近亿级网页内容搜索,适用于各类AI应用、RAG应用和AI Agent智能体的开发,解决数据安全、价格高昂和内容合规等问题。通过注册博查开发者账户、获取API KEY并调用API,开发者可以轻松集成搜索功能。
|
12月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
236 3
|
12月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
159 1
|
网络协议 算法 网络性能优化
C语言 网络编程(十五)套接字选项设置
`setsockopt()`函数用于设置套接字选项,如重复使用地址(`SO_REUSEADDR`)、端口(`SO_REUSEPORT`)及超时时间(`SO_RCVTIMEO`)。其参数包括套接字描述符、协议级别、选项名称、选项值及其长度。成功返回0,失败返回-1并设置`errno`。示例展示了如何创建TCP服务器并设置相关选项。配套的`getsockopt()`函数用于获取这些选项的值。
351 12
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。