对 UDP 使用 connect
我们可以对UDP套接字调用connect,不过相比于TCP调用connect会引起三次握手,从而建立TCP有效连接,UDP调用connect并不会引起和服务器端的网络交互,也就不会触发握手机制。其实 ,调用connect更像是 setpeername 的作用。
不过,通过UDP调用connect还是有一定意义的,那就是可以让应用程序接收"异步错误"的信息。
假如没有使用connect,当服务器未打开时,客户端程序想要接收服务器端数据时,并不会报错,而是会阻塞在 recvfrom 上,等待返回(或者超时)。
如果调用了connect,就会将UDP 套接字建立 "上下文" ,让客户端套接字和服务器端的地址和端口产生联系,而这种联系给了操作系统内核必要的信息,能够将操作系统内核收到的信息和对应的套接字进行关联。
当我们调用 sendto 或者 send 操作函数时,应用程序报文被发送,我们的应用程序返回,操作系统内核接管了该报文,之后操作系统开始尝试往对应的地址和端口发送,如果此时服务器关闭,那么因为对应的地址和端口不可达,一个ICMP报文会返回给操作系统内核,该ICMP报文含有目的地址和端口等信息。
如果我们不进行connect操作,建立(UDP 套接字——目的地址 + 端口)之间的映射关系,操作系统内核就没有办法把 ICMP 不可达的信息和 UDP 套接字进行关联,也就没有办法将 ICMP 信息通知给应用程序。
如果进行了connect操作,帮助操作系统建立了(UDP 套接字——目的地址 + 端口)之间的映射关系,当收到一个 ICMP 不可达报文时,操作系统内核可以从映射表中找出是哪个 UDP 套接字映射到该目的地址和端口。这样,当我们在该套接字上再次调用 recvfrom 或 recv 方法时,就可以收到操作系统内核返回的“Connection Refused”的信息(在TCP连接中如果不开启服务端,TCP客户端的connect函数也会直接返回“Connection Refused”的报错信息)。
综上:UDP调用connect不是发起连接请求的过程,而是记录目的地址和端口到套接字的映射关系。这样内核收到ICMP报文后就能转发给对应的UDP应用。 套接字断开时,将删除原来记录的映射关系。
使用connect后的收发函数
在对UDP调用connect后,推荐这样使用收发函数:
- 使用 send 或 write 函数来发送,如果使用 sendto 需要把相关的 to 地址信息置零;
- 使用 recv 或 read 函数来接收,如果使用 recvfrom 需要把对应的 from 地址信息置零。
是否可以对一个UDP套接字进行多次connect?
对于TCP套接字,connect只能调用一次。但是,对于UDP套接字来说,可以进行多次connect调用。这主要有两个作用:
-
可以重新指定IP地址和端口号
-
可以断开一个"已经连接"的UDP套接字。
为了断开一个"已经连接"的UDP套接字,在第二次调用connect时,调用方需要把套接字地址结构的地址族成员设置为AF_UNSPEC。
socket函数中第一个参数可以指定调用者期待返回的套接字地址结构的类型。如果指定了AF_INET,那么函数就不能返回任何IPv6相关的地址信息;如果指定了AF_INET6,则就不能返回任何IPv4地址信息。
AF_UNSPEC意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。