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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 给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传输三分之一的开销。

相关文章
|
3天前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
16 2
|
3天前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
14 1
|
1月前
|
网络协议 算法 网络性能优化
C语言 网络编程(十五)套接字选项设置
`setsockopt()`函数用于设置套接字选项,如重复使用地址(`SO_REUSEADDR`)、端口(`SO_REUSEPORT`)及超时时间(`SO_RCVTIMEO`)。其参数包括套接字描述符、协议级别、选项名称、选项值及其长度。成功返回0,失败返回-1并设置`errno`。示例展示了如何创建TCP服务器并设置相关选项。配套的`getsockopt()`函数用于获取这些选项的值。
|
1月前
|
C语言
C语言 网络编程(八)并发的UDP服务端 以进程完成功能
这段代码展示了如何使用多进程处理 UDP 客户端和服务端通信。客户端通过发送登录请求与服务端建立连接,并与服务端新建的子进程进行数据交换。服务端则负责接收请求,验证登录信息,并创建子进程处理客户端的具体请求。子进程会创建一个新的套接字与客户端通信,实现数据收发功能。此方案有效利用了多进程的优势,提高了系统的并发处理能力。
|
1月前
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。
|
1月前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
1月前
|
网络协议 视频直播 C语言
C语言 网络编程(三)UDP 协议
UDP(用户数据报协议)是一种无需建立连接的通信协议,适用于高效率的数据传输,但不保证数据的可靠性。其特点是无连接、尽力交付且面向报文,具备较高的实时性。UDP广泛应用于视频会议、实时多媒体通信、直播及DNS查询等场景,并被许多即时通讯软件和服务(如MSN/QQ/Skype、流媒体、VoIP等)采用进行实时数据传输。UDP报文由首部和数据部分组成,首部包含源端口、目的端口、长度和校验和字段。相比TCP,UDP具有更高的传输效率和更低的资源消耗。
|
2月前
|
网络协议 Java
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
这篇文章全面讲解了基于Socket的TCP网络编程,包括Socket基本概念、TCP编程步骤、客户端和服务端的通信过程,并通过具体代码示例展示了客户端与服务端之间的数据通信。同时,还提供了多个案例分析,如客户端发送信息给服务端、客户端发送文件给服务端以及服务端保存文件并返回确认信息给客户端的场景。
一文讲明TCP网络编程、Socket套接字的讲解使用、网络编程案例
|
1月前
|
网络协议
网络协议概览:HTTP、UDP、TCP与IP
理解这些基本的网络协议对于任何网络专业人员都是至关重要的,它们不仅是网络通信的基础,也是构建更复杂网络服务和应用的基石。网络技术的不断发展可能会带来新的协议和标准,但这些基本协议的核心概念和原理将继续是理解和创新网络技术的关键。
86 0
|
2月前
|
网络协议 数据处理 C语言
网络编程进阶:UDP通信
网络编程进阶:UDP通信
114 0