php 基于ICMP协议实现一个ping命令
php 基于ICMP协议实现一个ping命令
- 网络协议是什么
- ICMP 协议
- 什么是ICMP?
- ICMP 的主要功能
- ICMP 在 IPv4 和 IPv6 的封装
- Wireshark抓包
- ICMP 请求包分析
- PHP构建 ICMP 数据包
- php中的 pack & unpack 函数
- 字节和字符
- pack
- unpack
- ICMP计算校验和步骤
- 总结
网络协议是什么
网络协议(network protocol)是为了进行网络中的数据交换而建立的规则、标准、或约定。协议规定了通信实体之间所交换消息的格式、意义、顺序以及针对收到信息或发生事情所采取的行动。
上面的解释可能不够通俗易懂,所以可以打个通俗的比方,比如有两个人 一个中国人,和一个小日子过得不错的日本人如下:
他们现在要交流,就必须讲一门双方都懂的语言。如果大家都不会讲对方的民族语言,那么可以选择双方都懂的第三方的语言来交流,比如“讲英语”
。那么这时候“英语”
实际上就成为一种“网络协议”
。
把网络协议比做通用语言只是一种形象比喻,实际上协议本身比自然语言要简单的多,但是却比自然语言更严谨。协议规定了一种交流信息的格式或者说规范
同一种“规范”遵守的人多了,就成为一种事实上的“标准”。比如TCP/IP协议
就成为了一种最流行的网络协议。
ICMP 协议
什么是ICMP?
ICMP 的全称是 Internet Control Message Protocol(互联网控制协议)
,它是一种互联网套件,它用于IP 协议中发送控制消息
。也就是说,ICMP 是依靠 IP 协议来完成信息发送的
,它是 IP 的主要部分,但是从体系结构上来讲,它位于 IP 之上,因为 ICMP 报文是承载在 IP 分组中的,就和 TCP 与 UDP 报文段作为 IP 有效载荷被承载那样。这也就是说,当主机收到一个指明上层协议为 ICMP 的 IP 数据报时,它会分解出该数据报的内容给 ICMP,就像分解数据报的内容给 TCP 和 UDP 一样。
ICMP 协议和 TCP、UDP 等协议不同,它不用于传输数据,只是用来发送消息
。因为 IP 协议现在有两类版本:IPv4 和 IPv6
,所以 ICMP 也有两个版本:ICMPv4 和 ICMPv6
。
ICMP 的主要功能
对于 ICMP 的功能,主要分为两个
- ICMP 的第一个功能是确认 IP 包是否能够成功到达目标地址,当两个设备通过互联网相连时,任意一个设备发送给另一个设备的 IP 包如果没有到达,就会生成 ICMP 数据包发送给设备共享。
- ICMP 的第二个功能是进行网络诊断,经常使用 ICMP 数据包的两个终端程序是
ping
和traceroute
,traceroute
程序用于显示两台互联网设备之间可能的路径并测量数据包在 IP 网络上的时延。ping 程序是 traceroute 的简化版本,我们经常使用 ping 命令来测试两台设备之间是否互联,ping
通常用来测试两台主机之间的连接速度,并准确报告数据包到达目的地并返回后所花费的时间。
现在我们知道了,如果在 IP 通信过程中由于某个 IP 包由于某种原因未能到达目标主机,那么这个具体的原因将由 ICMP 进行通知,下面是一个 ICMP 的通知示意图
上面我们只是画出了路由器 2 给主机 A 发送了一个 ICMP 数据包,而没有画出具体的通知类型,但实际情况是,上面发送的是目标不可达类型(Destination unreachable),ICMP 也是具有不同的通知类型的,下面我们汇总了 ICMP 数据包的具体通知类型。
通知类型(十进制数) | 具体内容 |
---|---|
0 | 回送应答(Echo Reply) |
3 | 目标不可达(Destination Unreachable) |
4 | 原点抑制(Source Quench) |
5 | 重定向或改变路由(Redirect) |
8 | 回送请求(Echo Request) |
9 | 路由器公告(Router Advertisement) |
10 | 路由器请求(Router Solicitation) |
11 | ICMP 超时(Time Exceeded) |
17 | 地址子网请求(Address Mask Request) |
18 | 地址子网应答(Address Mask Reply) |
上表显示的 ICMP 通知类型主要分为两类:有关 IP 数据报传递的 ICMP 报文,这类报文也叫做差错报文(error message)
,以及有关信息采集和配置的 ICMP 报文,这类报文也被称为查询 query 或者信息类报文
。
信息类报文包括回送请求和回送应答(类型 8 和 类型 0 )
,路由器公告和路由器请求(类型 9 和 类型 0 )。最常见的差错报文类型包括目标不可达(类型 3 )、重定向(类型 5)、超时(类型 11)。
ICMP 在 IPv4 和 IPv6 的封装
我们知道,ICMP 是承载在 IP 内部的,而且 IPv4 和 IPv6 的封装位置不同:
ICMP 在 IPv4 协议中的封装
ICMP 在 IPv6 协议中的封装
上面两张图显示了 ICMPV4 和 ICMPv6
的报文格式。ICMP头部的 4 个字节在所有的报文中都是一样的。但是其余部分在不同的报文中却不一样。
要构建一个ICMP包,首先我们要了解ICMP包的结构。
可以看到,一个标准的ICMP包由8(bit)位类型,8(bit)位代码,16(bit)位校验和,16(bit)位ID,16(bit)位序列号和数据组成。ICMPv4 和 ICMPv6 的类型和代码字段是不同
的。咱们本次主要讲解 ICMPv4 数据包
Wireshark抓包
通过上面小结我们了解了ICMP 包的结构,咱们现场抓个ICMP 包 分析一下,这里我使用的抓包工具是 Wireshark
学习网络协议这个工具必不可少。
第一步 当你打开wireshark会出现网卡列表(如下图),想要捕获哪个网卡的数据包直接双击就可以了,简单方便(这里我使用的是无线网卡进行演示)。
第二步 通过 wireshark 的流量过滤栏过滤掉不需要的数据包,只展示 ICMP 数据包
第三步 打开 windows cmd 命令行工具,使用ping 命令 ping 百度
第四步分析ICMP 数据包
查看上图, wireshark 列表有8条数据,第一条数据 我们通过源IP地址与目标IP地址分析,应该是我这边发送给百度的请求包,因为我本地ip 就是 192.168.0.101
,而且 wireshark 还很贴心的告诉我们这个是一个ping 请求 request
第二条数据一看就是百度的响应包了 reply
,看后面的都是一个请求包对应一个响应包,一共4组,正好对应 cmd ping 命令有4次响应回复
ICMP 请求包分析
点开数据包后可以发现,上面有几个折叠的信息,越靠近上面的就越靠近物理层,越靠近下面的则越靠近应用层
分析 ICMP 包结构和相对应意思
上图中 ICMP包由8位类型,8位代码,16位校验和,16位ID,16位序列号和数据组成。接下来,我们就通过PHP构建一个这样的数据包。
PHP构建 ICMP 数据包
$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);//数据
}
没接触过 chr()
函数的同学可能不太了解php提供的这个函数,这个函数的作用是将十进制编码转换成 ASCII
码。
不了解 ASCll 码的同学可以 访问 ASCll 码表 查看十进制编码和 ASCll 码的对应关系
第一行 chr(8)
代表ICMP包头 类型字段 占8 bit位
1字节,chr(0)
代表ICMP包头 代码字段 占8 bit位
1字节
$package = chr(8).chr(0);//模式 8 0
第二行 chr(0).chr(0)
代表ICMP包头 校验和字段 占16 bit位
2字节,由于要先构建出整个ICMP 包才能计算校验和所以我们需要先把校验和置 0 后面计算出校验和在替换
$package = chr(0).chr(0);//置零校验和
第三行和第四行 任意都填 chr(0).chr(1)
也没关系 不要超过 4字节就好, "R"."C"
代表 ICMP ID 字段,chr(0).chr(1)
代表 ICMP Sequence 字段
$package .= "R"."C";//ID 这里随便填
$package .= chr(0).chr(1);//序列号 一样 随便填
第五行 填充ICMP data 包体 ,这里我填充满 64 位,data 包体也就是占8字节
for($i=strlen($package);$i<64;$i++){//填充满64位$package .= chr(0);//数据
}
构建好ICMP 数据包后 我们来计算校验和
php中的 pack & unpack 函数
在计算ICMP 校验和之前我们需要先了解两个重要的php函数 pack
和 unpack
。在网络编程,读写图像,word/excel文件等场景,这两个函数几乎必不可少,掌握这两个函数是迈向高级PHP编程的基础。
学习 pack
和 unpack
需要一些前置知识的铺垫所以我们先介绍 字节
和 字符
的区别
字节和字符
PHP的优势是简单易用,熟练运用 字符串 和 数组 相关函数就能抗住一般的需求。日常工作中多用到字符串,所以PHP开发对字符都比较熟悉,但字符的伴生概念:字节,不少PHP开发并不熟悉。
字节和 字符 有什么联系和区别呢?简单来说 字节是计算机存储和操作的最小单位
,字符是人们阅读的最小单位` ;
-
字节(Byte):字节是通过网络传输信息的单位。字节是计算机信息技术用于计量存储容量和传输容量的一种计量单位,1个字节等于8位二进制,它是一个8位的二进制数,是一个很具体的存储空间。
-
字符:人们使用的记号,抽象意义上的一个符号。就比如说:'字’, ‘0’, ‘A’, ‘.’ 等等。
总结一下就是:字节是 存储(物理)概念 ,字符是 逻辑概念 ;字节代表 数据 (内涵和本质),字符代表其 含义 ; 字符由字节组成 。
需要注意的一点是,相同的字符使用不同的编码占用的字节是不一样的
举个例子说明:
例子1:“中国”包含2个字符,GBK编码表示需要4个字节,UTF-8编码需要6个字节。
var_dump('UTF-8编码占用字节:'.strlen(mb_convert_encoding('中国','UTF-8')));
var_dump('GBK编码占用字节:'.strlen(mb_convert_encoding('中国','GBK')));
例子2:数字“1234567890”,包含10个字符,用 int32 类型表示只需4个字节;
十进制 | 二进制 |
---|---|
1234567890 | 01001001 10010110 00000010 11010010 |
pack
pack(string $format, mixed ...$args): string
函数的作用是将参数值转换成二进制字符串,
字面上理解二进制字符串
应该是像 01010101
这样的东西,其实并不是这样 01010101
是二进制码
二进制字符串就是用二进制码来表示字符以及由多个字符构成的字符串。
通常我们采用美国标准信息交换码ASCII
来表示字符
$format 的参数代表了以何种方式将对应的 $args 的参数值转换成[二进制]字符串
也就是ASCII码字符串
。
代码 | 描述 |
---|---|
a | 以 NUL 字节填充字符串 |
A | 以 SPACE(空格) 填充字符串 |
h | 十六进制字符串,低位在前 |
H | 十六进制字符串,高位在前 |
c | 有符号字符 |
C | 无符号字符 |
s | 有符号短整型(16位,主机字节序) |
S | 无符号短整型(16位,主机字节序) |
n | 无符号短整型(16位,大端字节序) |
v | 无符号短整型(16位,小端字节序) |
i | 有符号整型(机器相关大小字节序) |
I | 无符号整型(机器相关大小字节序) |
l | 有符号长整型(32位,主机字节序) |
L | 无符号长整型(32位,主机字节序) |
N | 无符号长整型(32位,大端字节序) |
V | 无符号长整型(32位,小端字节序) |
q | 有符号长长整型(64位,主机字节序) |
Q | 无符号长长整型(64位,主机字节序) |
J | 无符号长长整型(64位,大端字节序) |
P | 无符号长长整型(64位,小端字节序) |
f | 单精度浮点型(机器相关大小) |
g | 单精度浮点型(机器相关大小,小端字节序) |
G | 单精度浮点型(机器相关大小,大端字节序) |
d | 双精度浮点型(机器相关大小) |
e | 双精度浮点型(机器相关大小,小端字节序) |
E | 双精度浮点型(机器相关大小,大端字节序) |
x | NUL 字节 |
X | 回退一字节 |
Z | 以 NUL 字节填充字符串空白 |
@ | NUL 填充到绝对位置 |
上面表格这么多参数看下来,我第一次是真心懵逼了,大部分说明都很好理解,但是其中的主机、大端、小端等字节序是什么鬼呢?接下的内容比较枯燥,但必须理解才行,坚持吧。
字节序是什么?
在不同的计算机体系结构中,对于数据(比特、字节、字)等的存储和传输机制有所不同,因而引发了计算机领域中一个潜在但是又很重要的问题,即通信双方交流的信息单元应该以什么样的顺序进行传送。如果达不成一致的规则,计算机的通信与存储将会无法进行。目前在各种体系的计算机中通常采用的字节存储机制主要有两种:大端(Big-endian)和小端(Little-endian)。这里所说的大端和小端即是字节序。
端的起源
“endian” 这个词出自 Jonathan Swift 在1726 年写的讽刺小说《格列佛游记》(Gulliver’s Travels)。小人国的内战源于吃水煮鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开,由此曾发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。
“Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。” —— 《格列夫游记》 第一卷第4章
如何理解端这里先来定义两个概念:
-
MSB(Most Significant Bit/Byte)
:最重要的位或字节。它通常用来表明在一个 bit 序列(如一个 byte是8个 bit 组成的一个序列)或一个 byte 序列(如一个汉字是两个 byte 组成的一个序列)中对整个序列取值影响最大的那个 bit/byte
。(即最高位字节) -
LSB(Little Significant Bit/Byte)
:最不重要的位或字节。它通常用来表明在一个 bit 序列(如一个 byte是8个 bit 组成的一个序列)或一个 byte 序列(如一个汉字是两个 byte 组成的一个序列)中对整个序列取值影响最小的那个bit/byte
。(即最低位字节)
比如一个十六进制的整数 0x12345678 里面:
进制 | MSB(高字节) | LSB(低字节) | ||
---|---|---|---|---|
16进制 | 0x12 | 0x34 | 0x56 | 0x78 |
2进制 | 00010010 | 00110100 | 01010110 | 01111000 |
0x12 就是 MSB(高字节),0x78 就是 LSB(低字节)。而对于 0x78 这个字节而言,它的二进制是 01111000,那么最左边那个 0 就是 MSB(高位),最右边那个 0 就是 LSB(低位)。
虚拟内存中高地址和低地址
在内存中,多字节对象都是被存储为连续的字节序列。假设将int整型在内存中的起始地址(首个字节存储位置)为0x1000,那么另外三个字节就存储在0x1001~0x1003。不管存储的字节顺序是怎样的,内存地址的分配都是从小到大的增长。其中0x1000称为低地址端,0x1003称为高地址端。
大端 和 小端 区别:
- 大端(Big-endian):规定 MSB 在
存储
时放在低地址,在传输
时放在流的开始;
LSB在存储
时放在高地址,在传输
时放在流的末尾。(即高位字节在前,低位字节在后。)
简单描述大端:高字节存放在低地址,低字节存放在高地址
- 小端(Little-endian):规定 MSB 在存储时放在高地址,在传输时放在流的末尾;LSB
在存储时放在低地址,在传输时放在流的开始。(即低位字节在前,高位字节在后。)
简单描述小端:低字节存放在低地址,高字节存放在高地址
内存地址(低——>高) | 字节存储顺序 | ||
---|---|---|---|
大端(Big Endian) | 0x1000 0x1001 0x1002 0x1003 | 0x07 0x5B 0xCD 0x15 | 高字节存放在低地址 低字节存放在高地址 |
小端(Litter Endian) | 0x1000 0x1001 0x1002 0x1003 | 0x15 0xCD 0x5B 0x07 | 低字节存放在低地址 高字节存放在高地址 |
网络字节序和主机字节序
- 大小端一般是由CPU架构决定的。
- 网络字节序(Network Order):TCP/IP各层协议将字节序定义为Big Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
- 主机字节序(Host Order):整数在内存中保存的顺序,它遵循Little Endian规则(不一定,要看主机的CPU架构)。所以当两台主机之间要通过TCP/IP协议进行通信的时候就需要调用相应的函数
进行主机序列(Little Endian)和网络序(Big Endian)的转换
。
主机字节序在网络传输时会转化成网络字节序
1、网络协议指定了通讯字节序—大端(大端字节序也叫网络字节序)
2、只有在多字节
数据作为整体处理时才需要考虑字节序,单字节没有字节序问题,比如ASCII 码字符就没有字节序
3、运行在同一台计算机上的进程相互通信时,一般不用考虑字节序
为什么不统一使用一种字节序?
- 计算机电路先处理低位字节,效率比较高。因为计算就是从低位开始的,所以计算机内部很多都是小端字节序。
- 格式规范是为人类编写的,大端字节序更符合人类习惯。
讲了这么多,现在回归正题,根据php文档的描述pack
函数,在 $format 使用诸如 “a,A,h,H” 代码的时候,后面可以加一个长度,这个长度代表了对对应的 $args 参数的前多少字节来进行转换,如果长度为2,则对 $args 参数前2个字节进行转换,后面忽略。如果长度为”*“,则对对应的 $args 参数所有字节进行转换
举个例子
//$format 的参数代码不同,转换出来的数据也不同,这里我们先使用 代码:”a” 来测试
代码:”a”
//官方的说明是:以 NUL 字节填充字符串
//简单的来说就是将 $args 当做字符来转换(可以对照ASCII表),
//那为什么描述上说是 “用NUL来填充“?后面会讲到的。为了方便,后面出现的转换结果都用16进制来表示$bin = pack("a","w");//将 "w" 这个字符转换成二进制字符串,直接对照ASCII码表就晓得了
// 对应的二进制字符串的16进制就是:0x77带长度的转换:
pack("a3","abcde");// 根据文档说明,我们知道只要转换 "abcde" 的前3个字节// 对照ASCII表得知其转换结果是:0x61 0x62 0x63带长度但是参数值字节长度不够的转换:pack("a8","abcde");// 根据文档说明,我们要把 "abcde" 字符串来当做8个字节来转换// 但是参数值的长度不够怎么办,根据文档说明// PHP会把不够的字节转换成NUL(解答了上面的疑问)// 所以对照ASCII表得知其最终转换结果是:// 0x61 0x62 0x63 0x64 0x65 0x00 0x00 0x00
unpack
unpack(string $format, string $string, int $offset = 0): array|false
是用来解包经过pack打包的数据包,如果成功,则返回数组。其中格式化字符和执行pack时一一对应,unpack其实只是将多个字节压缩成一个字节
ICMP计算校验和步骤
- 将校验和字段置为0。
- 将每两个字节(16位)相加(二进制求和)直到最后得出结果,若出现最后还剩一个字节继续与前面结果相加。
- (溢出)将高16位与低16位相加,
直到高16位为0为止
。 - 将最后的结果(二进制)取反。得出校验和
$tmp = unpack("n*",$package);//把数据16位一组放进数组里$sum = array_sum($tmp);//求和// 判断高16位是否为0,不为0继续相加
while($sum >> 16){// 取高16位 + 低16位
$sum = ($sum >> 16) + ($sum & 0xFFFF);//结果右移十六位 加上结果与0xFFFF做AND运算
}$sum = ~ $sum;//做NOT运算
$checksum = pack("n*", $sum);//打包成2字节
$package[2] = $checksum[0];
$package[3] = $checksum[1];//填充校验和
第一步:unpack()
函数把字节数据16位一组放进数组里,为什么是16位,具体看计算校验和的第二条规则,我们打印 unpack()
函数返回的数据得到一个数组,数组的第一个元素2048 其实就是 chr(8).chr(0)
,我们转化一下进制就明白了
首先把2048转化成二进制数100000000000
由于2048是按16位解码的所以我们要把对应的二进制高位补0到16位
十进制:2048
转化————————————
二进制:00001000 00000000
二进制——》十进制
00001000——》8
00000000——》0
数组的第3个元素转化规则一样这里就不再赘述
$tmp = unpack("n*",$package);//把数据16位一组放进数组里
var_dump($tmp);
//打印出来的数据
array(32) {[1] =>int(2048)[2] =>int(0)[3] =>int(21059)[4] =>int(1)...[32] =>int(0)
}
第二步: 把unpace 解码出来的10进制数相加(二进制求和)直到最后得出结果
$sum = array_sum($tmp);//求和
var_dump($sum);23108
第三步: 将高16位与低16位相加,直到高16位为0为止
在讲解第三步之前,我们需要一点前置知识做铺垫
二进制位移运算操作(二进制位移分为逻辑位移和算数位移,下面讲的是逻辑位移)
右移位例子:2 = 4 >> 1(右移一位)
十进制:
- val=4
- res=2
二进制:
-
val=00000100
-
res=00000010
通过逻辑右移例子发现二进,制逻辑右移一位被舍弃,移位后左边补0
逻辑右移
特点:高位补0,低位移出。
左移位例子:8 = 4 << 1(左移一位)
十进制:
- val=4
- res=8
二进制:
-
val=00000100
-
res=00001000
二进制逻辑左移一位被舍弃,移位后右边补0
逻辑左移
特点:低位补0,高位移出。
按位与操作
( 0 = 0000) = ( 0 = 0000) & ( 5 = 0101)
( 1 = 0001) = ( 1 = 0001) & ( 5 = 0101)
( 0 = 0000) = ( 2 = 0010) & ( 5 = 0101)
( 4 = 0100) = ( 4 = 0100) & ( 5 = 0101)
( 0 = 0000) = ( 8 = 1000) & ( 5 = 0101)
$a & $b And(按位与) 把 $a 和 $b 中都为 1 的位设为 1,否则为0。
$sum = ($sum >> 16) + ($sum & 0xFFFF);//结果右移十六位 加上结果与0xFFFF做AND运算
十进制 二进制
23107 => 00000000 00000000 01011010 01000100($sum >> 16) 得到高16位高16 低16
00000000 00000000 01011010 01000100
>> 16
00000000 00000000 00000000 00000000
-------------------------------------
00000000 00000000
这里我解释一下为什么32位右移16位可以获得高16位,
我们知道二进制右移高位补0,原来的高位会移动到低位,
这样我们就获得的原来的高位。($sum & 0xFFFF) 得到低16位高位 低位
00000000 00000000 01011010 01000100
&
00000000 00000000 11111111 11111111
-------------------------------------
01011010 01000100
第四步(二进制)取反
在讲解取反前我们先来看个例子
$a = 8;
var_dump(~$a); //打印8的二进制取反int(-9) //结果
结果跟我所想的不一样啊,因为在PHP中文手册中说: ~ $a Not(按位取反) 将 $a 中为 0 的位设为 1,反之亦然。 我只是简单的理解为:0变1,1变0。原来按位取反以十进制输出并不是简单理解成这样。
要解释上面的运算时怎么回事,首先我们需要知道二进制中的原码,反码,补码
有符号数:
对于有符号数而言,符号的正,负机器是无法识别的,但由于
"正,负"
恰好是两种截然不同的状态,如果用"0"
表示"正"
,用"1"
表示"负"
这样符号也被数字化了,并且规定将它放在有效数字的前面,即组成了有符号数。所以,在二进制中使用最高位(第一位)来表示符号,最高位为0
,表示正数
,最高位为1
,表示负数
。
无符号数:
无符号数是针对二进制来讲,无符号数的表数范围是
非负数
。全部二进制均代表数值(所以位都用于表示数的大小),没有符号位,即第一个"0" 或 “1” 不表示正负
有符号数的性质:
- 二进制的最高位时符号位:0表示正数,1表示负数
- 正数的原码,反码,补码都一样
- 负数的反码 = 它的原码符号位不变,其它位取反 即0变1 ,1变0
- 负数的补码 = 它的反码 +1
- 0的反码,补码都是0
- 在计算机运算的时候,都是以补码的方式运行
我们计算过 (~8) = -9那么这个-9是怎么得来的呢?8的原码、反码、补码都是 :00000000 00000000 00000000 00001000(原码)
00000000 00000000 00000000 00001000(反码)
00000000 00000000 00000000 00001000(补码)
(按位取反,对补码进行按位取反,符号位也会改变)
11111111 11111111 11111111 11110111(补码) //第一位是符号位,1代表负号,表示这是一个负数;记住运算和运算结果都是用补码表示的,这是某个数的补码,我们还需要推导反码和原码负数的补码 = 它的反码 +1 ,现在我们求反码=补码-1,即:
11111111 11111111 11111111 11110110 (反码)
负数的反码 = 它的原码符号位不变,其它位取反 即0变1 ,1变0,我们需要转变回原码所以也需要相同的操作
10000000 00000000 00000000 00001001(原码)所以结果是:-9-8 按位取反10000000 00000000 00000000 00001000(原码)
11111111 11111111 11111111 11110111 (反码)
11111111 11111111 11111111 11111000 (补码)
(按位取反后,对补码进行按位取反,符号位也会改变)
00000000 00000000 00000000 00000111(补码)
00000000 00000000 00000000 00000111(反码)
00000000 00000000 00000000 00000111(原码)
所以结果是:7
看完上面的例子,下面代码的结果就显而易见了。(这块地方我理解属实耗费了点时间,所以基础很重要!)
$sum = ~ $sum;//做NOT运算
第四步 把得出的校验和用pack函数打包成2字节,为什么打包成2字节,因为校验和占ICMP包16位,也就是2字节,我们需要把前面置0的校验和重新填充。
$checksum = pack("n*", $sum);//打包成2字节
$package[2] = $checksum[0];
$package[3] = $checksum[1];//填充校验和
这样,一个标准的ICMP数据包就构建好了,可以直接发送给目标主机了。
$tmp = unpack("n*",$package);//把数据16位一组放进数组里$sum = array_sum($tmp);//求和// 判断高16位是否为0,不为0继续相加
while($sum >> 16){// 取高16位 + 低16位
$sum = ($sum >> 16) + ($sum & 0xFFFF);//结果右移十六位 加上结果与0xFFFF做AND运算
}$sum = ~ $sum;//做NOT运算
$checksum = pack("n*", $sum);//打包成2字节
$package[2] = $checksum[0];
$package[3] = $checksum[1];//填充校验和
$host = "www.baidu.com";//设置目标主机
$socket=socket_create(AF_INET, SOCK_RAW, getprotobyname('icmp'));//创建原始套接字
$start = microtime();//记录开始时间
// socket_sendto 函数的作用,向套接字发送消息,无论它是否已建立连接
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 ip头部 第九个字节就是 ttl
$seq = $recv[28];//序列号 ip头20字节 icmp头8个字节,所以28是icmp序列号
$duration = round(($end - $start) * 1000,3);//计算耗费的时间
echo "{$length} bytes from {$host}: icmp_seq={$seq} ttl={$ttl} time={$duration}ms".PHP_EOL;//输出结果
我们尝试来ping 一下百度
如图返回了一串结果,64 bytes 表示百度返回的icmp响应包大小,其实整个响应包的大小应该是84 bytes,因为ICMP 协议依赖IP协议完成任务,所以ICMP报文中要封装IP头部,我们需要抽离出单独的ICMP报文,就要减去前20字节的IP头部。
14.215.177.39 表示百度响应请求的ip ,icmp_seq 序列号用于关联请求报文和应答报文(对应请求和应答报文的序列号相同),占用2字节,下一个ttl
需要重点解释一下,最后一个time是一个ICMP包请求到响应的消耗时间。
TTL
在 IP 数据包中有一个叫做 TTL(Time To Live, 生存周期)
,它的值在每经过路由器一跳之后都会减 1,IP 数据包减为 0 时会被丢弃。此时,IP 路由器会发送一个 ICMP 超时消息(ICMP TIme Exceeded Message, 错误号 0)发送给主机,通知该包已经被丢弃。
设置生存周期的主要目的就是为了防止路由器控制遇到问题发生循环状况时,避免 IP 包无休止的在网络上转发,如下图所示
我们也可以用cmd ping 命令测试一下,把ttl 设置为1 ,当穿过第一个路由器,ttl -1 = 0,就会被丢弃。
TTL特点:
没经过一台三层设备会减1,当减到0的时候,三层设备就会丢弃该数据包
三层设备包括 交换机和路由器或者防火墙 ,三层表示的是网络OSI 参考模型的第三层网络层
总结
这次通学习php如果构建一个完整ICMP报文,引出了很多其它知识,比如字符,字节序,二进制的高位低位,有符号无符号等等;这些基础知识很重要,学习它们将对后面我们实现websock协议,MQTT协议提供重要帮助。
还有很重要的几个php 函数,pack,unpack,chr
有了这些函数的帮助我们很容易的处理二进制字节流解析与转换,如果还是对这些函数不是很理解我提供一些文章供大家学习参考。
参考地址
- PHP: 深入pack/unpack (https://www.cnblogs.com/66w66/p/13624016.html)
- PHP中的pack和unpack函数(https://segmentfault.com/a/1190000018264262?utm_source=sf-similar-article)
- PHP: chr和pack、unpack那些事(http://www.javashuo.com/article/p-orppnlys-e.html)
如果文章描述有错误,也请指正谢谢!
相关文章:
php 基于ICMP协议实现一个ping命令
php 基于ICMP协议实现一个ping命令 网络协议是什么ICMP 协议什么是ICMP?ICMP 的主要功能ICMP 在 IPv4 和 IPv6 的封装Wireshark抓包ICMP 请求包分析PHP构建 ICMP 数据包php中的 pack & unpack 函数字节和字符packunpackICMP计算校验和步骤总结网络协议是什么 网络协议&…...
Java基本数据类型
1.概述 佛说,大千世界,无奇不有。在这个世界里,物种的多样性,遍地开花,同样,在Java的世界里,也有着异曲同工之妙,Java秉承面向对象的特性,必然少不了区分对象的类型&…...
English Learning - L2 语音作业打卡 Day2 2023.2.22 周三
English Learning - L2 语音作业打卡 Day2 2023.2.22 周三💌 发音小贴士:💌 当日目标音发音规则/技巧:🍭 Part 1【热身练习】🍭 Part2【练习内容】🍭【练习感受】🍓元音[ ɑː ]&…...
45. 跳跃游戏 II
题目: 45. 跳跃游戏 II难度中等1974收藏分享切换为英文接收动态反馈给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 num…...
应届生Java面试50题线程篇(含解析)
什么是线程? 答:线程是操作系统能够进行运算调度的最小单位,是程序执行流的最小单元。在Java中,可以通过实现Runnable接口或继承Thread类来创建线程。 创建线程的方式有哪些?各自的优缺点是什么? 继承 Thread 类&…...
【数据库】第七章 数据库设计
第七章数据库设计 数据库设计概述 数据库设计的基本步骤 需求分析概念结构设计逻辑结构设计物理结构设计数据库实施数据库运行和维护 需求分析 收集需求,理解需求 收集各个角色的需求 概念数据库设计 建立概念模型 ,E-R图/IDEF1x图 消除冲突&…...
Burp Suite 常用模块简介
Burp Suite 常用模块分为 目标站点(target)模块 代理(proxy)模块 攻击(Intruder)模块 重放(Repeater) 模块 Target模块是对站点资源的收集,与站点各资源包发出和相应包的记录 Proxy模块是核心模块,可以拦截数据包发送往浏览器,进行修改后再…...
QML Item和Rectangle详解
1.Item和Rectangle Item类型是Qt Quick中所有可视项的基本类型。 Qt Quick中的所有可视项都继承Item。尽管Item对象没有视觉外观,但它定义了视觉项中常见的所有属性,例如x和y位置、宽度和高度、锚定和键处理支持。 Rectangle继承自Item,多…...
常见前端基础面试题(HTML,CSS,JS)(六)
GET 和 POST 的区别 从 http 协议的角度来说,GET 和 POST 它们都只是请求行中的第一个单词,除了语义不同,其实没有本质的区别。 之所以在实际开发中会产生各种区别,主要是因为浏览器的默认行为造成的。 受浏览器的影响…...
深度学习 李沐报错
3.6. softmax回归的从零开始实现 — 动手学深度学习 2.0.0 documentation softmax从0开始实现 函数执行需要加main指定 改成这样 if __name__"__main__":print(evaluate_accuracy(net, test_iter)) 不然会这样出错 RuntimeError: An attempt has been m…...
【JAVA程序设计】(C00104)基于Springboot的家庭理财管理系统——有文档
基于Springboot的家庭理财管理系统项目简介项目获取开发环境项目技术运行截图运行视频项目简介 基于Springboot开发的家庭理财管理系统设计与实现共分为三个角色:系统管理员、家庭管理员、家庭用户 管理员角色包含以下功能: 用户管理、修改密码、角色管…...
【第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件)】
第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件) 1.AOP概述: (1)什么是AOP: ①面向切面编程(…...
面试官: 你知道 JWT、JWE、JWS 、JWK嘛?
想起了 之前做过的 很多 登录授权 的项目 它相比原先的session、cookie来说,更快更安全,跨域也不再是问题,更关键的是更加优雅 ,所以今天总结了一篇文章来介绍他 JWT 指JSON Web Token,如果在项目中通过 jjwt 来支持 J…...
基于企业微信应用消息的每日早安推送
基于企业微信应用消息的每日早安推送 第一步:注册企业微信 企业微信注册地址:https://work.weixin.qq.com/wework_admin/register_wx 按照正常流程填写信息即可,个人也可以注册企业微信,不需要公司 注册完成后,登录…...
【数字IC基础】黑盒验证、白盒验证、 灰盒验证
文章目录 一、黑盒验证二、白盒验证三、灰盒验证一、黑盒验证 1、黑盒验证:大多数基于仿真的验证环境都是黑盒验证;2、不需要知道设计的内部结构和特性,只需要在输入端口打激励,观察输出即可;3、验证工程师学习设计的规格,然后编写验证环境中的 drivers, monitors, check…...
管理的本质是达成目标
“没有目标,其实就没有管理学存在的意义。要有效地使用管理学的智慧,首先要建立清晰的目标。” - 《宁向东的管理学课》 起源 最近开始刷很久之前就在得到上买了的已经起灰了的课程,看到这句话觉得很有道理。 思考 这里面有一个很重要的词…...
【数字IC基础】IC(Integrated Circuit,集成电路)常用缩写
文章目录 1、集成电路:2、数字IC设计相关步骤:3、数字设计相关概念:4、验证相关:5、语言类:6、IC设计相关工具:7、存储器相关:8、总线协议类:9、文件格式类:10、标准和规范:11、其它:1、集成电路: 缩写全称中文翻译LSILarge-scale intergrated circuit大规模集成电…...
JavaScript 高级1 :面向对象
JavaScript 高级1 :面向对象 Date: January 16, 2023 Text: 面向对象、ES6中类和对象、类的继承、面向对象案例 目标: 能够说出什么是面向对象 能够说出类和对象的关系 能够使用 class 创建自定义类型 能够说出什么是继承 面向对象编程介绍 面向过…...
C语言结构体对齐
1. 结构体对齐 要点 变量只能存储在他的长度的整数倍地址上结构体整体对齐跟他的最长的字段整数倍对齐 栗子1 struct Example1 {char a; //1个字节int c; //4个字节short b; //2个字节 };std::cout << sizeof(Example1 ) << std::endl; // 12 std::cout &…...
Bootstrap系列之导航
Bootstrap导航 可以在 ul 元素上添加 .nav类,在每个 li 选项上添加 .nav-item 类,在每个链接上添加 .nav-link 类: 基本的导航 <div class"container mt-3"><h2>导航</h2><p>简单的水平导航:</p><ul class&…...
Java EE|TCP/IP协议栈之应用层协议DNS详解
文章目录一、对DNS的感性认识简介特点一些常见疑问二、DNSDNS域名结构域名的分级三、域名服务器四、域名解析过程参考一、对DNS的感性认识 简介 DNS,即Domain Name System,是域名系统的简称。它是Internet上解决网上机器命名的一种系统。 TCP/IP中的IP地址是由四…...
【MyBatis】作用域生命周期(四)
🚗MyBatis学习第四站~ 🚩起始站:MyBatis概述&环境搭建(一) 🚩本文已收录至专栏:数据库学习之旅 👍希望您能有所收获 一.引入 为了使用方便,我们经常能看到各种教程都将MyBatis抽离为工具类…...
腾讯一面—Android 系统启动流程详解
正文AMS 是 Android 中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似,它本身也是一个 Binder 的实现类,应用进程能通过 Binder 机制调用…...
【Python知识点桂电版】02组合数据类型
一、序列序列简介序列是指一种包含多项数据的数据结构,分为不可变序列和可变序列。可变序列可修改序列内的元素如列表,二不可变序列一旦建立就不能修改其中的元素,字符串和元组属于不可变序列。列表和元组的创建列表:列表名 [元素…...
LeetCode100_100. 相同的树
LeetCode100_100. 相同的树 一、描述 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 示例 1: 输入:p [1,2,3], q […...
javaEE 初阶 — 网络层中 IP 协议 的报文结构
文章目录IP 协议报文4位版本号4位首部长度8位服务类型16位总长度(字节数)8位生存时间(TTL)与 8位协议16位首部校验和32位源 IP 地址与32位目标 IP 地址动态分配的 IP 地址NAT 网络地址转换IPv6IP 协议报文 4位版本号 这里的 IP 协…...
iOS swift UICollectionView
文章目录1.纯代码自定义UICollectionViewCell2.禁止滑动(弹簧效果)3.UICollectionView的长按拖动2.在一个控制器中放两个UICollectionView或者UITableView,代理方法要怎么写1.纯代码自定义UICollectionViewCell import UIKitclass NewDeviceBottomColle…...
计算机三级数据库 填空题汇总
计算机三级 数据库 IDEF0需求建模方法由箭头和(活动/方框/矩形)两种元素构成。、从安全性角度考虑,防火墙技术是用来保证数据库应用系统的(网络)环境安全的。在UML的状态机图中,状态之间的转移是由&#x…...
【Java学习】初识Java
JavaSEJava初识1. Java简介2.Java环境的安装与配置3. 开发第一个Java程序Java初识 学前疑问:(带着疑问去学习,在学习中自行探索答案) Java是什么?能做什么?发展前景如何?需要学习哪些内容&…...
LabVIEW网络服务安全
LabVIEW网络服务安全如何保护Web服务?当许多人考虑安全性时,他们会考虑加密、用户ID和密码。用户ID和密码用于授权(告诉目标谁在发出请求)。加密保护客户端和服务器之间的通信流量,以便未经授权的个人无法拦截和读取发…...
上海做网站的多吗/谷歌 chrome 浏览器
smali文件格式 每个smali文件都由若干条语句组成,所有的语句都遵循着一套语法规则。在smali 文件的头3 行描述了当前类的一些信息,格式如下: [java] view plaincopy.class < 访问权限> [ 修饰关键字] < 类名> .super < 父类名…...
张家界做网站/百度sem代运营
🍡三个不相信 1.不相信翻译 PMP考题的中文翻译常常词不达意,当你产生怀疑时不要犹豫,看看上面的英文原文,你会立刻感到柳暗花明。 2.不相信经验 东西方在语言文字、文化、习惯、工作方式上都存在较大差异,所以你的工作经验很可能与出题人有很多不同,你需要入乡随…...
廊坊模板网站建设/百度竞价推广出价技巧
本系统为牛旦教育IT课堂在微头条上发布的内容,为便于查阅,特辑录于此,都是常用SQL基本用法。前两篇连接:(一):SQL点滴(查询篇):数据库基础查询案例实战(二):SQL点滴(排序篇):数据常规…...
做网站首选九零后网络/市场营销专业就业方向
老许今日份知识分享来了。 处理数量较大的数据时,一般分为数据获取、数据筛选,以及结果展示几个步骤。在 Excel 中,我们可以利用数据透视表(Pivot Table)方便快捷的实现这些工作。 首先手把手的教你 如何在 Excel 中手动构建一个基本的数据…...
广州越秀区天气预报15天查询/乐陵seo优化
在前面随笔《基于Metronic的Bootstrap开发框架--工作流模块功能介绍》和《基于Metronic的Bootstrap开发框架--工作流模块功能介绍(2)》中介绍了Bootstrap开发框架的工作模块功能,前面文章也提及,通过代码生成工具直接生成对应的Cr…...
django网站开发源码/持啊传媒企业推广
推荐博客:付铭 day-01 HTML 1、HTML 基本语法 html标签 单标签 <img /> 、<img> 双标签 <html> </html> 属性 属于标签 <img src"图片的地址"><table width"100" height"200"></table> 1…...