前面我们已经知道,UDP 编程中的调用 sendto 和 recvfrom 产生的错误是不会返回的。主要原因在于套接字是无连接的。
要想让它们返回错误,只有让套接字变成有连接的。
1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/echo_udp/conn
。
2. UDP 也可以是有连接的
我们可以在 UDP 套接字上调用 connect,但它于 TCP 连接却不同,在 UDP 套接字上调用 connect 没有三次握手的过程。本质上,connect 函数是一个重载的函数,针对不同的套接字,它会做不同的事情。
- 无连接的 UDP 套接字(unconnected UDP socket),新创建的 UDP 套接字默认如此;
- 有连接的 UDP 套接字(connected UDP socket),对 UDP 套接字调用 connect.
3. 有连接 VS 无连接
借助这篇文章,我们再次讨论,什么是有连接,什么是无连接。
3.1 sockname 与 peername
在网络编程中,对于有连接的协议来说,有两个基本的概念,即 sockname 与 peername,这是什么意思呢?
sockname 表示本地(local)套接字地址,而 peername 表示外地(foreign)套接字地址。有两个函数 getsockname 与 getpeername 可以用来获取有连接协议的本地套接字地址与外地套接字地址。
连接,是一种抽象的概念,它相当于 sockname + peername. 因此,我们说:
有连接 = {sockname, peername} = {(ip1:port1), (ip2:port2)}
3.2 再看 bind 与 connect
很遗憾,存在 getsockname 与 getpeername 这两个函数,却不见 setsockname 与 setpeername 这样的函数,没错,看小标题你已经猜到了。
bind 函数本质上就是 setsockname,而 connect 函数本质上就是 setpeername 函数。unp 在介绍有连接 UDP 的时候就提到过:
bind 函数更好的名字是 setsockname,而 connect 函数更好的名字是 setpeername
3.3 有连接与无连接
现在,我们可以大体上已经把握了有连接与无连接的区别,即有连接就是 sockname + peername,而无连接,总是缺少其中的 peername。
4. 让 UDP 套接字成为有连接
作为 UDP 客户端来说,默认情况下它是无连接的,要想让其变成有连接,只要给它指定 peername 即可,使用 setpeername 函数,哦不!应该是 connect 函数。
// 伪代码 int sockfd; struct sockaddr_in servaddr; servaddr = resolve(ip, port); sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 调用 connect,设置 peername connect(sockfd, servaddr);
5. 注意事项
有连接的 UDP 套接字相比无连接的 UDP 发生了三点变化:
- 1) 不能再使用 sendto 函数指定目的 IP 和 port,这个参数需要指定成 NULL,或者干脆使用 write 函数。
- 2) 不需要再使用 recvfrom 来获取数据报的发送者了,应该改用 read 或 recv 等函数。
- 3) 有连接的 UDP 套接字引发错误,会返回给它所在的进程。无连接的 UDP 套接字不会。
对于第三点,内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地)。
6. 实验
- 正常情况
图1 正常情况
- 主机存在,服务未启动
图2 主机存在,服务未启动
- 主机不存在
图3 主机不存在(未开机),阻塞在 recvfrom 上了
6. 总结
- 掌握有连接和无连接的概念
- 知道如何让 UDP 变成有连接