
在网络编程中,icmp ping是诊断网络连通性和测量延迟的常用工具。开发一个自定义的ping库,不仅需要实现icmp协议细节,更关键的是要设计一套清晰、可靠的机制来处理各种网络状况,尤其是请求超时和延迟回复。
一个健壮的Ping库需要精确地发送ICMP Echo Request数据包,并监听对应的Echo Reply。然而,网络环境复杂多变,数据包可能丢失、延迟,甚至乱序。这就引出了一个核心问题:当一个Ping请求在设定的时间内未收到回复并被标记为“超时”后,如果其对应的Echo Reply数据包在稍后才到达,库应该如何处理?是完全忽略它,还是像某些标准ping工具一样,在报告超时后仍然将其打印出来?
例如,标准的ping工具可能会显示如下输出:
Request timeout for icmp_seq 2 Request timeout for icmp_seq 3 64 bytes from 80.67.169.18: icmp_seq=2 ttl=58 time=2216.104 ms 64 bytes from 80.67.169.18: icmp_seq=3 ttl=58 time=1216.559 ms
这表明序列号为2和3的请求首先被标记为超时,但它们的回复最终还是到达了。对于一个通用库而言,是否应效仿这种行为,需要仔细权衡。
我们来看一个Go语言实现的Ping库片段,它展示了基本的ICMP数据包构造、解析以及发送/接收逻辑:
// makePingRequest 构造ICMP Echo Request数据包
func makePingRequest(id, seq, pktlen int, filler []byte) []byte {
// ... 省略具体实现,主要负责设置ICMP类型、代码、校验和、ID和序列号 ...
p[0] = ICMP_ECHO_REQUEST // type
p[4] = uint8(id >> 8) // id
p[5] = uint8(id & 0xff) // id
p[6] = uint8(seq >> 8) // sequence
p[7] = uint8(seq & 0xff) // sequence
// ...
return p
}
// parsePingReply 解析ICMP Echo Reply数据包
func parsePingReply(p []byte) (id, seq, code int) {
id = int(p[24])<<8 | int(p[25])
seq = int(p[26])<<8 | int(p[27])
code = int(p[21])
return
}
// Pinguntil 持续发送ICMP Echo数据包并接收回复
func Pinguntil(destination string, count int, response chan Response, delay time.Duration) {
// ... 省略初始化和错误处理 ...
sendid := os.Getpid() & 0xffff // 使用进程ID作为Ping ID
seq := 0
for ; seq < count || count == 0; seq++ {
// ... 省略序列号循环处理 ...
sendpkt := makePingRequest(sendid, seq, pingpktlen, []byte("Go Ping"))
start := time.Now()
// 发送数据包
writesize, err := ipconn.Write(sendpkt)
if err != nil || writesize != pingpktlen {
// ... 错误处理,报告发送失败 ...
time.Sleep(delay)
continue
}
// 设置读取截止时间,实现超时机制
ipconn.SetReadDeadline(time.Now().Add(time.Second * 1)) // 1秒超时
resp := make([]byte, 1024)
for { // 循环读取回复
readsize, err := ipconn.Read(resp)
elapsed := time.Now().Sub(start)
rid, rseq, rcode := parsePingReply(resp)
if err != nil { // 读取错误或超时
response <- Response{Delay: 0, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
break // 跳出内部循环,处理下一个序列号
} else if rcode != ICMP_ECHO_REPLY || rseq != seq || rid != sendid {
// 如果不是Echo Reply,或序列号/ID不匹配,则继续读取下一个数据包
continue
} else { // 成功收到匹配的回复
response <- Response{Delay: elapsed, Error: err, Destination: raddr.IP.String(), Seq: seq, Writesize: writesize, Readsize: readsize}
break // 跳出内部循环
}
}
time.Sleep(delay - elapsed) // 控制发送间隔
}
close(response)
}在上述Pinguntil函数中,关键在于ipconn.SetReadDeadline(time.Now().Add(time.Second * 1))和内部for循环的判断逻辑。
这意味着,一旦一个请求因为超时而ipconn.Read返回错误,即使其对应的回复在之后某个时间点到达,该回复也不会被当前序列号的接收循环处理,因为它已经因超时而break了。这种行为实际上是“丢弃”了延迟到达的回复,至少对于当前序列号的报告而言。
针对超时后延迟回复的处理,Ping库通常有两种设计哲学:
严格模式(推荐):
诊断模式(复杂且通常不推荐用于通用库):
基于上述分析,对于一个通用的Ping库而言,强烈建议采用严格模式。库应该只为每个Ping请求提供一次明确的结果:要么在规定时间内成功,要么超时失败。
理由如下:
如果确实需要诊断延迟到达的数据包,可以考虑以下替代方案:
在设计ICMP Ping库时,关于如何处理超时和延迟回复,是一个关键的设计决策。虽然标准ping工具会打印出超时后到达的延迟回复,但对于一个通用编程库而言,为了提供清晰、易用的API和简化应用层逻辑,最佳实践是坚持严格模式:一旦一个请求被标记为超时,就不再报告其后续到达的延迟回复。 这确保了库消费者能够获得明确的“成功”或“失败”结果,从而构建更健壮、更易于维护的应用。
以上就是构建ICMP Ping库:超时与延迟回复的处理策略的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号