在php文档的笔记中搞了段php ping的代码php ping ipv4 by socket,顺便学习了ipv4 ICMP 报文和ipv4包结构,对ping的过程和php socket的使用有了初步的认识。本文记录下自己遇到的一些情况和代码分享。
copy的代码遇到的问题
ping 域名时,会把域名dns查找时间也计算进去
在本地测试ok,扔服务器上测试,失败率很高
而直接用bash ping的话,丢包率很低,用这段代码的话,丢包率很高,且时高时低。实在是搞不明白原因是啥,只能去看看ip报文看看是代码哪写得不对了。
在后续测试中发现:
打印错误输出的话,经常是:
Not ICMP response
Bad identification number
Bad sequence number
举个例子用后文的测试代码测试得到:
Not ICMP response
69 0 0 28 122 207 0 0 255 1 12 251 169 254 128 12 # 发包的ip10 0 0 12 8 0 113 190 # 类型 50 66 83 255
报文
错误信息
可以看到;
这里的包类型是8,是请求报文,还有169 254 128 12,是发包的ip,正常应该是我们ping的ip地址,也就是说我们收到了一个ping的请求包。
因为数据与会包的对不上,直接被算成失败,如果计算成丢包的话,丢包率会特别高。
而且下次ping的时候会收到上次ping的回包,得到了一次响应时间很低,但标识码和序列号错误的会包,此时由被视为丢包。
结果就是,一个错误的会包会导致后续的ping的雪崩式错误。
优化版本
改进的地方
域名解析时间单独计算
检测回包地址,丢弃非预期的回包
非预期的回包丢弃,在有效时间内继续等待回包
支持多次ping和统计
demo
require_once 'xping.php'; $back = xping::init() ->setDebug(false) ->setRet($ret) ->setError($error) ->doit('baidu.com',5,2000,1000,32); var_dump(__FILE__.' line:'.__LINE__,$back,$ret,$error);exit;
out
//debug info 68.974018096924 63.398838043213 62.793016433716 62.896966934204 62.525987625122 string(64) "\xping.php line:*"// $backbool(true) // $retarray(4) { ["host"]=> string(9) "baidu.com" ["dns_ms"]=> float(0.0179290771484375) ["ip"]=> string(14) "220.181.38.251" ["ping_ret"]=> array(7) { ["avg"]=> float(64.12) ["min"]=> float(62.52598762512207) ["max"]=> float(68.97401809692383) ["loss"]=> float(0) ["sc"]=> int(5) ["rc"]=> int(5) ["str"]=> string(65) "send=5 recive=5 loss=0.00 % min=62.53ms max=68.97ms avg=64.12ms " } } //$errorstring(8) "No Error"
$ret 部分说明
host
ping domainip
ping domain’s ipdns_ms
Domain name resolution timeping_ret
rc
recive countsc
send count
代码
测试代码
<?php/// start ping.inc.php ///$g_icmp_error = "No Error";// timeout in msfunction ping($host, $timeout){ $port = 0; $datasize = 64; global $g_icmp_error; $g_icmp_error = "No Error"; $ident = [ord('J'), ord('C')]; $seq = [rand(0, 255), rand(0, 255)]; $packet = ''; $packet .= chr(8); // type = 8 : request $packet .= chr(0); // code = 0 $packet .= chr(0); // checksum init $packet .= chr(0); // checksum init $packet .= chr($ident[0]); // identifier $packet .= chr($ident[1]); // identifier $packet .= chr($seq[0]); // seq $packet .= chr($seq[1]); // seq for ($i = 0; $i < $datasize; $i++) { $packet .= chr(0); } $chk = icmpChecksum($packet); $packet[2] = $chk[0]; // checksum init $packet[3] = $chk[1]; // checksum init $sock = socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp')); $time_start = microtime(1); socket_sendto($sock, $packet, strlen($packet), 0, $host, $port); $read = [$sock]; $write = null; $except = null; $select = socket_select($read, $write, $except, 0, $timeout * 1000); if ($select === null) { $g_icmp_error = "Select Error"; socket_close($sock); return -1; } elseif ($select === 0) { $g_icmp_error = "Timeout"; socket_close($sock); return -1; } $recv = ''; $time_stop = microtime(1); socket_recvfrom($sock, $recv, 65535, 0, $host, $port); $recv = unpack('C*', $recv); if ($recv[10] !== 1) // ICMP proto = 1 { $g_icmp_error = "Not ICMP packet"; socket_close($sock); return -1; } if ($recv[21] !== 0) // ICMP response = 0 { $g_icmp_error = "Not ICMP response ".implode(' ', $recv); socket_close($sock); return -1; } if ($ident[0] !== $recv[25] || $ident[1] !== $recv[26]) { $g_icmp_error = "Bad identification number ".implode(' ', $recv); socket_close($sock); return -1; } if ($seq[0] !== $recv[27] || $seq[1] !== $recv[28]) { $g_icmp_error = "Bad sequence number ".implode(' ', $recv); socket_close($sock); return -1; } $ms = ($time_stop - $time_start) * 1000; if ($ms < 0) { $g_icmp_error = "Response too long"; $ms = -1; } socket_close($sock); return $ms; }function icmpChecksum($data){ $bit = unpack('n*', $data); $sum = array_sum($bit); if (strlen($data) % 2) { $temp = unpack('C*', $data[strlen($data) - 1]); $sum += $temp[1]; } $sum = ($sum >> 16) + ($sum & 0xffff); $sum += ($sum >> 16); return pack('n*', ~$sum); }function getLastIcmpError(){ global $g_icmp_error; return $g_icmp_error; }/// end ping.inc.php ///// demoecho ping('1.0.0.3', 2000);echo $g_icmp_error.PHP_EOL;echo ping('1.0.0.3', 2000);echo $g_icmp_error.PHP_EOL;echo ping('1.0.0.4', 2000);echo $g_icmp_error.PHP_EOL;
后续
geerlingguy/Ping测试了这个项目种的socket ping,发现用socket->content后再socket_send和socket_read,不会出现收到请求包。难道是socket_recvfrom和socket_sendto有问题?还是socket_select呢?一脸懵逼。
再后续:
socket_create 确实也可以作为服务端接受数据,通过阅读文档得知,socket_recvfrom接收数据时,可以获取来源ip,此时可以直接进行判断,非预期的会包直接丢弃。check socket_recfrom host
相关
推荐本站淘宝优惠价购买喜欢的宝贝:
本文链接:https://zblog.hqyman.cn/post/10279.html 非本站原创文章欢迎转载,原创文章需保留本站地址!
休息一下~~