溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

PHP通過ICMP協(xié)議實現(xiàn)ping的示例分析

發(fā)布時間:2021-04-01 09:14:54 來源:億速云 閱讀:223 作者:小新 欄目:編程語言

小編給大家分享一下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)。

PHP通過ICMP協(xié)議實現(xiàn)ping的示例分析

可以看到,一個標(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è)資訊頻道!

向AI問一下細(xì)節(jié)

免責(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)容。

AI