Apprentissage recommandé : "Tutoriel vidéo PHP"
PHP implémente le ping (socket brut) via le protocole ICMP
Récemment, je souhaite implémenter une fonction pour détecter si l'hôte cible est en ligne. Je l'ai vérifié sur Baidu. La plupart d'entre eux utilisent une connexion ouverte sur un certain port pour déterminer si l'hôte cible est en ligne. Tels que le port système Windows 3389 (RDP) et le port système *nix 22 (SSH).
Mais cela posera un problème. Si l'hôte cible n'ouvre pas ces ports, cela entraînera des erreurs de jugement. Ce n’est pas parce qu’un port n’est pas ouvert que l’hôte cible est hors ligne.
Étant donné que la plupart des appareils répondent au ping, j'ai pensé à utiliser ping pour implémenter cette fonction. En interrogeant à nouveau Baidu, j'ai découvert que la plupart des didacticiels utilisent la fonction exec() pour appeler la commande système ping, ce qui est évidemment très dangereux.
J'ai donc finalement décidé d'utiliser le socket brut fourni par PHP et de créer moi-même le package ICMP pour implémenter le ping.
Pour créer un paquet ICMP, nous devons d'abord comprendre la structure du paquet ICMP.
Comme vous pouvez le constater, un paquet ICMP standard se compose d'un type de 8 bits, d'un code de 8 bits, d'une somme de contrôle de 16 bits, d'un identifiant de 16 bits et d'une séquence de 16 bits. nombre et données. Ensuite, nous créerons un tel package de données via 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);//数据 }
Ensuite, calculez la somme de contrôle.
$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字节
Somme de contrôle de remplissage dans le paquet.
$package[2] = $checksum[0]; $package[3] = $checksum[1];//填充校验和
De cette manière, un paquet de données ICMP standard est construit et peut être envoyé directement à l'hôte cible. Prêt à partir ~
$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;//输出结果
Appuyez pour exécuter et une demande de ping est terminée. Si rien d’autre, le résultat devrait être celui indiqué ci-dessous.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=128 time=0.589ms
Enfin, j'ai empaqueté ce code dans une fonction. Ajoutez-le à votre code et utilisez ping(string $host, int $retry) lorsque vous devez l'appeler.
<?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 } ?>
S'il y a des erreurs ou un manque de détails dans l'article, veuillez le signaler et en discuter dans la zone de commentaires.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!