ホームページ  >  記事  >  バックエンド開発  >  ICMP プロトコルを介して ping (生のソケット) を実装する PHP の詳細な説明

ICMP プロトコルを介して ping (生のソケット) を実装する PHP の詳細な説明

藏色散人
藏色散人転載
2021-03-31 17:24:332996ブラウズ

推奨学習: 「PHP ビデオ チュートリアル

PHP は ICMP プロトコルを介して ping (生のソケット) を実装します

最近、対象ホストがオンラインかどうかを検知する機能を実装したいと思ってBaiduで調べてみたところ、特定のポートに開かれた接続を利用して対象ホストがオンラインかどうかを判断するものが多かったです。 Windows システムのポート 3389 (RDP) や *nix システムのポート 22 (SSH) など。

しかし、ターゲットホストがこれらのポートを開いていない場合、判断ミスにつながります。特定のポートが開いていないからといって、ターゲット ホストがオフラインであるとは限りません。

ほとんどのデバイスは ping に応答するため、この機能を実装するために ping を使用することを考えました。 Baidu に再度問い合わせたところ、ほとんどのチュートリアルでは exec() 関数を使用してシステム ping コマンドを呼び出していることがわかりましたが、これは明らかに非常に安全ではありません。

そこで私は最終的に、PHP が提供する生のソケットを使用し、ping を実装するための ICMP パッケージを自分で構築することにしました。

ICMP パケットを構築するには、まず ICMP パケットの構造を理解する必要があります。

ICMP プロトコルを介して ping (生のソケット) を実装する PHP の詳細な説明

ご覧のとおり、標準 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 データ パケットが構築され、ターゲット ホストに直接送信できます。準備完了~

$host = "192.168.1.1";//设置目标主机
$socket=socket_create(AF_INET, SOCK_RAW, getprotobyname(&#39;icmp&#39;));//创建原始套接字
$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(&#39;icmp&#39;));//创建原始套接字
    $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
}
?>

記事内に誤りや不完全な詳細がある場合は、お気軽に指摘し、コメント欄で議論してください。

以上がICMP プロトコルを介して ping (生のソケット) を実装する PHP の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjoyruncで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。