深入理解UDP


UDP基本特性

我们说UDP提供无连接的服务,因为UDP客户与服务器之间不必存在任何长期的关系。举例来说,一个UDP客户可以创建一个套接字并发送一个数据报给一个给定的服务器,然后立即用同一个套接字发送另一个数据包给另外一个服务器。同样地,一个UDP服务器可以用同一个UDP套接字从若干个不同的客户接收数据报,每个客户一个数据报。

客户不与服务器建立连接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地的地址作为参数。类似的,服务器不接受来自客户的连接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。

服务器进程未运行

这是问题的情景,用户有时会出现在服务器没有启动的时候,去点击切换LED大屏输入源

在不启动服务器的前提下启动客户。如果我们这么做后在客户上键入一行文本,那么什么也不发生。首先我们注意到,在客户主机能够往服务器主机发送那个UDP数据报之前,需要一次ARP请求和应答的交换。可以知道客户数据报发出,服务器主机响应的是一个“port unreachable”(端口不可达)ICMP消息。不过这个ICMP错误不返回给客户进程。

我们称这个ICMP错误为异步错误。该错误由sendto引起,但是sendto本身却成功返回。UDP输出操作成功返回仅仅表示在接口输出队列(数据链路层的输出队列)中具有存放所形成IP数据报的空间。

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

有了这个能力后,对于已连接UDP套接字,与默认的未连接UDP套接字相比,发生了三个变化。

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

  2. 我们不必使用recvfrom以获悉数据包的发送者,而改用read、recv或recvmsg。在一个已连接UDP套接字上,由内核输入操作返回的数据报只有那些来自connect所指定协议地址的数据报。这样就限制一个已连接UDP套接字能且仅能与一个对端交换数据报。

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

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

具体流程是:

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

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

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

如果给一个UDP套接字多次调用connect

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

  • 指定新的IP地址和端口号
  • 断开套接字

第一个目的不同于TCP套接字中connect的的使用:对于TCP套接字,connect只能调用一次。

性能

当应用进程在一个未连接的UDP套接字上调用sendto时,源自Berkeley的内核暂时连接该套接字,发送数据报,然后断开该连接。在一个未连接的UDP套接字上给两个数据报调用sendto函数涉及内核执行下列6个步骤:

  • 连接套接字
  • 输出第一个数据报
  • 断开套接字连接
  • 连接套接字
  • 输出第二个数据报
  • 断开套接字连接

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

  • 连接套接字
  • 输出第一个数据报
  • 输出第二个数据报

在这种情况下,内核只复制一次含有目的IP地址和端口号的套接字地址结构,相反当调用两次sendto时,需复制两次。

关于TCP输出

当某个应用进程调用write时,内核从该应用进程的缓冲区中复制所有数据到所写套接字的发送缓冲区。如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区已有其他数据),该应用进程将被投入睡眠。内核将不从write系统调用返回,直到应用进程缓冲区中的所有数据都复制到套接字发送缓冲区。因此,从写一个TCP套接字的write调用成功返回仅仅表示我们可以重新使用原理的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据。

对端TCP必须确认收到的数据,伴随来自对端的ACK的不断到达,本端TCP至此才能从套机字发送缓冲区中丢弃已确认的数据。TCP必须为已发送的数据保留一个副本,直到它被对端确认为止。


文章作者: 冬瓜冬瓜排骨汤
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 冬瓜冬瓜排骨汤 !
  目录