1. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/echo_udp/basic
。
2. UDP 数据报丢失
UDP 是不可靠的,它不像 TCP 能够保证数据一定到达对端接收缓冲区。上一节我们写的 basic/echo 程序就会遇到这样的问题:客户端无法确认发送的数据是否到达对端接收缓冲区。
实际上,每个 UDP 套接字都有一个接收缓冲区,所有到达该套接字的数据报按顺序进入缓冲区。当进程调用 recvfrom 时,缓冲区中的下一个数据报以 FIFO(先进先出)顺序返回给进程。
回到我们的 basic/echo 客户端,它的伪代码像下面这样:
while(1) { gets(buf);// 从标准输入读入数据 sendto(sockfd, buf, servaddr); // 发送给服务器 recvfrom(sockfd, buf, NULL); // 接收数据 puts(buf); }
如果客户端在 sendto 的时候,udp 报文没有顺利到达对方,那么客户端将永远阻塞在 recvfrom 上。
3. 实验
在 sun 主机上启动客户端,给一个 ip 地址不存在或者端口不存在的服务器发信息。
- 主机存在,服务器未启动(端口不存在)
$ ./echo -h mars
图1 主机存在,服务器未启动
可以看到,tcpdump 产生了一个 ICMP 报文:udp 端口不可达的信息。可惜的是,此错误并没有报告给应用程序。sendto 的返回值根本检测不到这种情况。
- 主机不存在(flower 还没有开机)
$ ./echo -h flower
图2 flower 还没开机
服务器不存在,tcpdump 发送 ARP 请求报文,此错误也不会报告给应用进程~
上面两种情况,导致客户端阻塞在了 recvfrom 上,永远没有继续执行的机会。
当然,这个实验并没有真正的模拟数据报丢失的情况,要想在服务器启动的情况下模拟数据报丢失是很难的,我们不妨假设服务器启动了,但是到服务器的某个中间路由器正好宕机了,那么这种情况就非常像前面的两个实验了。
4. 如何解决问题?
有一些可用的解决方案,比如给 recvfrom 设置超时。如果超过一定的时间,recvfrom 还没有返回,基本上就可以认为数据报丢失,重新回到 sendto 继续执行。
还有一种方案是使用多线程,我们将发送和接收放到两个不同的线程中去执行。
还有其它更多的方案,比如给 udp 增加可靠性,模拟 tcp 协议,给 udp 程序添加两个特性:超时和和重传、序列号。
这些话题我们现在保留,以后继续讨论。
5. 总结
- UDP 数据报会丢失
- UDP 是不可靠的