您好,登錄后才能下訂單哦!
小編給大家分享一下PHP通過ICMP協(xié)議實現(xiàn)ping的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
PHP通過ICMP協(xié)議實現(xiàn)ping(原始套接字)
最近想實現(xiàn)一個檢測目標(biāo)主機(jī)是否在線的功能,用百度查了查,多是使用打開到某個端口的連接來判斷目標(biāo)主機(jī)是否在線的。如Windows系統(tǒng)3389端口(RDP)和*nix系統(tǒng)的22端口(SSH)。
但這樣會出現(xiàn)一個問題,目標(biāo)主機(jī)如果沒有開放這些端口,則會導(dǎo)致判斷上的錯誤。某個端口不開放并不代表目標(biāo)主機(jī)離線。
由于大多數(shù)設(shè)備都會回應(yīng)ping,由此想到了使用ping來實現(xiàn)這個功能。再次查詢百度,發(fā)現(xiàn)大多數(shù)教程都使用exec()函數(shù)調(diào)用系統(tǒng)ping命令來實現(xiàn),這顯然很不安全。
所以最終決定使用PHP提供的原始套接字,自己構(gòu)建ICMP包來實現(xiàn)ping。
要構(gòu)建一個ICMP包,首先我們要了解ICMP包的結(jié)構(gòu)。
可以看到,一個標(biāo)準(zhǔn)的ICMP包由8位類型,8位代碼,16位校驗和,16位ID,16位序列號和數(shù)據(jù)組成。接下來,我們就通過PHP構(gòu)建一個這樣的數(shù)據(jù)包。
$package = chr(8).chr(0);//模式 8 0 $package .= chr(0).chr(0);//置零校驗和 $package .= "R"."C";//ID 這里是我隨便填的 $package .= chr(0).chr(1);//序列號 一樣 隨便填的 for($i=strlen($package);$i<64;$i++){//填充滿64位 $package .= chr(0);//數(shù)據(jù) }
接下來計算校驗和。
$tmp = unpack("n*",$package);//把數(shù)據(jù)16位一組放進(jìn)數(shù)組里 $sum = array_sum($tmp);//求和 $sum = ($sum >> 16) + ($sum & 0xFFFF);//結(jié)果右移十六位 加上結(jié)果與0xFFFF做AND運(yùn)算 $sum = $sum + ($sum >> 16);//結(jié)果加上結(jié)果右移十六位 $sum = ~ $sum;//做NOT運(yùn)算 $checksum = pack("n*", $sum);//打包成2字節(jié)
把校驗和填充進(jìn)數(shù)據(jù)包。
$package[2] = $checksum[0]; $package[3] = $checksum[1];//填充校驗和
這樣,一個標(biāo)準(zhǔn)的ICMP數(shù)據(jù)包就構(gòu)建好了,可以直接發(fā)送給目標(biāo)主機(jī)了。Ready to go~
$host = "192.168.1.1";//設(shè)置目標(biāo)主機(jī) $socket=socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));//創(chuàng)建原始套接字 $start = microtime();//記錄開始時間 socket_sendto($socket, $package, strlen($package), 0, $host, 0);//發(fā)送數(shù)據(jù)包 $read = array($socket);//初始化socket $select = socket_select($read, $write, $except, 5); if ($select === FALSE){ $icmpError = "socket_select()方法發(fā)生錯誤,原因:".socket_strerror(socket_last_error()); socket_close($socket); }else if($select === 0){ $icmpError = "請求超時"; socket_close($socket); } if($icmpError !== NULL){ echo $icmpError; exit(); } socket_recvfrom($socket, $recv, 65535, 0, $host, $port);//接受回傳數(shù)據(jù) /*回傳數(shù)據(jù)處理*/ $end = microtime();//記錄結(jié)束時間 $recv = unpack("C*", $recv); $length = count($recv) - 20;//包長度 減去20字節(jié)IP報頭 $ttl = $recv[9];//ttl $seq = $recv[28];//序列號 $duration = round(($end - $start) * 1000,3);//計算耗費(fèi)的時間 echo "{$length} bytes from {$host}: icmp_seq={$seq} ttl={$ttl} time={$duration}ms".PHP_EOL;//輸出結(jié)果
輕敲運(yùn)行,一次ping請求就完成了。不出意外的話,結(jié)果應(yīng)該如下顯示。
64 bytes from 192.168.1.1: icmp_seq=1 ttl=128 time=0.589ms
最后,我將這些代碼打包成了一個函數(shù)。把它加入你的代碼里,需要調(diào)用的時候,使用ping(string $host, int $retry)即可。
<?php function ping($host, $retry = 1){ $g_icmp_error = NULL; $write = NULL; $except = NULL;//初始化所需變量 $package = chr(8).chr(0);//模式 8 0 $package .= chr(0).chr(0);//置零校驗和 $package .= "R"."C";//ID $package .= chr(0).chr(1);//序列號 for($i=strlen($package);$i<64;$i++){ $package .= chr(0); } $tmp = unpack("n*",$package);//把數(shù)據(jù)16位一組放進(jìn)數(shù)組里 $sum = array_sum($tmp);//求和 $sum = ($sum >> 16) + ($sum & 0xFFFF);//結(jié)果右移十六位 加上結(jié)果與0xFFFF做AND運(yùn)算 $sum = $sum + ($sum >> 16);//結(jié)果加上結(jié)果右移十六位 $sum = ~ $sum;//做NOT運(yùn)算 $checksum = pack("n*", $sum);//打包成2字節(jié) $package[2] = $checksum[0]; $package[3] = $checksum[1];//填充校驗和 $socket=socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));//創(chuàng)建原始套接字 $start = microtime();//記錄開始時間 socket_sendto($socket, $package, strlen($package), 0, $host, 0);//發(fā)送數(shù)據(jù)包 $read = array($socket);//初始化socket $select = socket_select($read, $write, $except, 5); if ($select === FALSE){ $icmpError = "socket_select()方法發(fā)生錯誤,原因:".socket_strerror(socket_last_error()); socket_close($socket); }else if($select === 0){ $icmpError = "請求超時"; socket_close($socket); } if($icmpError !== NULL){ echo $icmpError; exit(); } socket_recvfrom($socket, $recv, 65535, 0, $host, $port);//接受回傳數(shù)據(jù) /*回傳數(shù)據(jù)處理*/ $end = microtime();//記錄結(jié)束時間 $recv = unpack("C*", $recv); $length = count($recv) - 20;//包長度 減去20字節(jié)IP報頭 $ttl = $recv[9];//ttl $seq = $recv[28];//序列號 $duration = round(($end - $start) * 1000,3);//計算耗費(fèi)的時間 echo "{$length} bytes from {$host}: icmp_seq={$seq} ttl={$ttl} time={$duration}ms".PHP_EOL;//輸出結(jié)果 socket_close($socket);//關(guān)閉socket } ?>
文中如果有錯誤或不詳細(xì)的地方,歡迎在評論區(qū)指出和討論。
以上是“PHP通過ICMP協(xié)議實現(xiàn)ping的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。