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