原生socket使用ICMP协议实现ping单个或多个目标时发生窜包的解决方法

问题描述

原生socket使用ICMP协议实现ping功能,网上代码很多了,我参考的是这本:王艳平,张越.Windows网络与通信程序设计[M].北京人民邮电出版社,2006。

代码逻辑也很清晰,先构造ICMP包,把当前时间填入timestamp字段,调用sendto发到指定IP,再调用recvfrom接收,然后用当前时间减去接收到的包的timestamp字段,得到ping的时间。

例子里只发了4个包,没有问题。网络通畅时,也没有问题。

我改了下代码,循环向多个ip轮流发包,然后接收。问题就出现了。

ping的耗时越来越长,开始是1000ms,后面2000ms,3000ms。但从返回包的速度来看,又明显没那么高。

原因分析

反复分析,我又在sendtorecvfrom中间加了一段代码:

if (rand() % 2)
{
	pr.second.ret = TIMEOUT;
	continue;
}

随机地让程序发出后不收,直接报超时。问题就复现了。在网络好的情况下也能复现问题。

原因其实就是recvfrom这步漏包了。假设我们发送一组序号为0 1 2 3的ICMP包,发出0之后,如果此时网络较差,recvfrom没能在超时限制内拿到包,就会返回SOCKET_ERROR,此时调用WSAGetLastError()会返回WSAETIMEDOUT

之后我们进入下一轮循环,发送1号包,再调用一次recvfrom取包,而此时收到的包是0号包。

之后我们又进入一轮循环,发送2号包,而此时网络又差了,recvfrom超时一次。

再一轮循环,发送3号包,再调用一次recvfrom取包,这时取出了1号包,假设此时2号,3号包其实都已经到了,但因为recvfrom中间超时2次,现在还在取1号包,所以:

计算出的耗时 = (当前时间-发1号包的时间) > (2次超时时间)

若超时设置为1秒,则计算出的时间一定大于2秒。

解决方法

所以,在网络较差的环境中,这种sendto一次,再recvfrom一次的做法是不正确的。sendto时的ICMP包应该填入sequence字段,然后把recvfrom包进循环,每次recvfrom得到数据,就进行解析,只有取出的sequence和当前sequence相符,才证明中间积压的包已经处理掉了。取最后一次recvfrom的时间减去最后一次收到的包的timestamp,得到正确的耗时。

顺带一提,这个问题还有其他解决方法:

  • 多进程:用CreateProcess调用系统的ping程序,用pipe取得结果。这样开销很大。
  • 多线程:开多个线程,每个线程读取自己管理的ip ping的结果,这样情况会好一些,但不能从根本上解决窜包的问题,我没有实验过,不过我猜想如果逻辑还是sento一次,recvfrom一次的话,窜包问题会再现。
  • 用第三方dll,比如ICMP.dll,再开多线程调用。这样应该是可以的,系统自带的ping似乎也是调用的ICMP.dll,我没有考证。但系统的ping没有出现窜包的问题,应该是有所规避。这个方法的缺点在于不能跨平台。而我的方法是平台无关的,linux上一样可以用,并且不需要一个ip开一个线程,这样开销太高了。

在这里插入图片描述

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值