【udp】UDP是一个无连接的协议,connect的意义何在?

UDP也可以是“已连接”?

根据接触到了 UDP 数据报协议相关的知识,在我们的脑海里,已经深深印上了“UDP 等于无连接协议”的特性。

在没有开启服务端的情况下,我们运行一下这个程序:

image.png
image.png

看到这里你会不会觉得很奇怪:
不是说好 UDP 是“无连接”的协议吗?
不是说好 UDP 客户端只会阻塞在 recvfrom 这样的调用上吗?
怎么这里冒出一个“Connection refused”的错误呢?

从前面的例子中,你会发现,我们可以对 UDP 套接字调用 connect 函数,但是和 TCP connect 调用引起 TCP 三次握手,建立 TCP 有效连接不同,UDP connect 函数的调用,并不会引起和服务器目标端的网络交互,也就是说,并不会触发所谓的”握手“报文发送和应答。

image.png
image.png

那么对 UDP 套接字进行 connect 操作到底有什么意义呢?

其实上面的例子已经给出了答案,这主要是为了让应用程序能够接收”异步错误“的信息。

如果我们回想一下,不调用 connect 操作的客户端程序,在服务器端不开启的情况下,客户端程序是不会报错的,程序只会阻塞在 recvfrom 上,等待返回(或者超时)。

在这里,我们通过对 UDP 套接字进行 connect 操作,将 UDP 套接字建立了”上下文“,该套接字和服务器端的地址和端口产生了联系,正是这种绑定关系给了操作系统内核必要的信息,能够将操作系统内核收到的信息和对应的套接字进行关联。

我们可以展开讨论一下。

事实上,当我们调用 sendto 或者 send 操作函数时,应用程序报文被发送,我们的应用程序返回,操作系统内核接管了该报文,之后操作系统开始尝试往对应的地址和端口发送,因为对应的地址和端口不可达,一个 ICMP 报文会返回给操作系统内核,该 ICMP 报文含有目的地址和端口等信息。

如果我们不进行 connect 操作,建立(UDP 套接字——目的地址 + 端口)之间的映射关系,操作系统内核就没有办法把 ICMP 不可达的信息和 UDP 套接字进行关联,也就没有办法将 ICMP 信息通知给应用程序。

这就涉及到了网络协议设计中的数据平面和控制平面了,对于控制平面的消息,可以是带内传输,也可以是带外传输。
实际上,ICMP,根据名称就可以看出它是一种专门的控制协议,控制和指示IP层发生的事件。
ECONNREFUSED正是ICMP返回的,然而并不是所有的UDP socket都可以享用ICMP带来的错误提示,毕竟带外控制消息和协议本身的关联太松散了。
UDP socket必须显式的connect对端才可以。

如果我们进行了 connect 操作,帮助操作系统内核从容建立了(UDP 套接字——目的地址 + 端口)之间的映射关系,该映射的作用正是为了和UDP带外的ICMP控制通道捆绑在一起,使得UDP socket的接口含义更加丰满。

当收到一个 ICMP 不可达报文时,操作系统内核可以从映射表中找出是哪个 UDP 套接字拥有该目的地址和端口,别忘了套接字在操作系统内部是全局唯一的,当我们在该套接字上再次调用 recvfrom 或 recv 方法时,就可以收到操作系统内核返回的”Connection Refused“的信息。

一般来说,服务器端不会主动发起 connect 操作,因为一旦如此,服务器端就只能响应一个客户端了。
当然,有时候也不排除这样的情形,一旦一个客户端和服务器端发送 UDP 报文之后,该服务器端就要服务于这个唯一的客户端。

一般来说,客户端通过 connect 绑定服务端的地址和端口,对 UDP 而言,可以有一定程度的性能提升。
这是为什么呢?

因为如果不使用 connect 方式,每次发送报文都会需要这样的过程:
连接套接字→发送报文→断开套接字→连接套接字→发送报文→断开套接字 →………

而如果使用 connect 方式,就会变成下面这样:
连接套接字→发送报文→发送报文→……→最后断开套接字

我们知道,连接套接字是需要一定开销的,比如需要查找路由表信息。
所以,UDP 客户端程序通过 connect 可以获得一定的性能提升。

我对 UDP 套接字调用 connect 方法进行了深入的分析。
之所以对 UDP 使用 connect,绑定本地地址和端口,是为了让我们的程序可以快速获取异步错误信息的通知,同时也可以获得一定性能上的提升。

不进行connect操作,UDP套接字与服务端的地址和端口就没有产生关系,那recvfrom是怎么收到对应的报文呢?
UDP的connect操作,会引发内核的ICMP报文发送?如果不是,ICMP是在什么时机下发送的?

1.是通过sendto函数来绑定服务端地址的,之后再通过recvfrom引用到之前的socket,这样收到的报文就是指定的服务地址和端口了;
2.不是connect导致ICMP报文,而是对应的地址和端口不可达时,一个 ICMP 报文会返回。connect只是将这个信息传递变得可能了。

为什么UDP的sendto方法会有一个"连接"过程的性能损耗,直接按照目标地址发过去不就可以了吗?
操作系统会先用ICMP协议探一探目标地址是否存在,然后再用UDP协议发送具体的数据,不知道理解的对不?

我不觉得会发ICMP来探一探。ICMP是用的时候才触发的。
这里我想表达的是操作系统协议栈在每次sendto的时候都会需要一个地址初始化的过程,如果这个过程省略掉了,是可以得到一点点性能的提升的。当然,其实这个是没有那么大的。

udp 连接套接字 这个是什么过程? 断开套接字这又是什么过程呢?

没有断开,这里都是一个系统调用,告诉了一些系统内核信息而已。

通常在服务器端不开启的情况下,UDP客户端程序是不会报错的,程序只会阻塞在 recvfrom 上,等待返回(或者超时)。
UDP的connect并不是真正的conncect操作,它只是给UDP 套接字建立了“上下文”。

在客户端的代码中,在send之前进行了connect。为什么send函数返回是成功,而recv就返回失败呢?send的时候不应该也能知道icmp报文返回错误而返回错误么?

从函数设计的角度来说,在UDP通信中, sendto的目的是将报文通过网络传送给对端,并不考虑是否能发送成功,仅仅考虑的是把报文通过缓冲区发送出去;
而recvfrom则是一个阻塞调用,它是需要知道是否成功的,包括超时,包括ICMP报文返回错误。

不调用connect时, 难道recvfrom不是收到后再填充from的地址的吗,还可以指定从某个服务端收数据?

这是对UDP而言的,通过sendto函数客户端指定了发送的服务端地址,再通过recvfrom就可以从之前指定的服务端地址来接收数据了。
这里,没有说可以任意从某个服务端收数据的,而是从之前指定的服务端收数据的。

UDP 连接和断开套接字的过程是怎样的?

UDP 连接套接字不是发起连接请求的过程,而是记录目的地址和端口到套接字的映射关系。
断开套接字则相反,将删除原来记录的映射关系。

在 UDP 中不进行 connect,为什么客户端会收到信息?
有人说,如果按照我在文章中的说法,UDP 只有 connect 才建立 socket 和 IP 地址的映射,那么如果不进行 connect,收到信息后内核又如何把数据交给对应的 socket?

这个问题非常有意思。我刚刚看到这个问题的时候,心里也在想,是啊,我是不是说错了?

其实呢,这对应了两个不同的 API 场景:

第一个场景就是我这里讨论的 connect 场景,在这个场景里,我们讨论的是 ICMP 报文和 socket 之间的定位。
我们知道,ICMP 报文发送的是一个不可达的信息,不可达的信息是通过目的地址和端口来区分的,如果没有 connect 操作,目的地址和端口就没有办法和 socket 套接字进行对应,所以,即使收到了 ICMP 报文,内核也没有办法通知到对应的应用程序,告诉它连接地址不可达。

image.png

那么为什么在不 connect 的情况下,我们的客户端又可以收到服务器回显的信息了?

这就涉及到了第二个场景,也就是报文发送的场景。
注意服务器端程序,先通过 recvfrom 函数调用获取了客户端的地址和端口信息,这当然是可以的,因为 UDP 报文里面包含了这部分信息。
然后我们看到服务器端又通过调用 sendto 函数,把客户端的地址和端口信息告诉了内核协议栈,可以肯定的是,之后发送的 UDP 报文就带上了客户端的地址和端口信息,通过客户端的地址和端口信息,可以找到对应的套接字和应用程序,完成数据的收发。

参考

UDP编程有必要调用connect吗?
https://zhuanlan.zhihu.com/p/380109394

UDP socket也可以使用connect系统调用
https://www.cnblogs.com/jingzhishen/p/5545787.html

关 udp socket 连接问题分析
https://www.jianshu.com/p/6ab38d2eefc2

UDP也可以是“已连接”?
https://time.geekbang.org/column/article/129807

你可能感兴趣的:(【udp】UDP是一个无连接的协议,connect的意义何在?)