不知道平时大家是怎么访问或者说是打开 Google、百度这些网站的,但我掐指一算,你会用下面几种方式。
1 收藏好网站,要打开就直接点击。
2 用域名访问,在地址栏输入域名,回车访问。
以上两种都是我们 99% 的人访问网站的方式,但难道就没有其他方式了吗?
很多猿们肯定还会说,我还可以用 IP 访问呀,你忘了?
3 用 IP 访问,在地址栏输入 IP,回车访问。
那如何获取到这个 IP 呢?其实也很简单,可以到 www.ip.cn
这个网站里输入域名来查询对应的 IP,当然也可以去其他网站中查询。也可以使用 Ping 命令
来帮助我们获取域名对应的 IP。只要我们 ping 一下域名就可以了。
但是你以为就只有这几种方式了吗?No,No,No!
后面的才是我要说的重点,也是我写这篇文章的目的,假如只是前面的几个点,其实没有什么好说的,99% 的人都知道。都知道的事情我何必分享呢?我要分享的当然是大家不知道的。
今天我就来泄露一下天机
4 用数字来访问,在地址栏输入协议://数字
,回车访问。如 https://2899904127
不知大家有去试试,当我们输入协议和数字并回车后,它自动换成 IP,然后再换成域名。
更厉害的是,这个数字可以用十进制
表示,也可以用八进制
表示,还可以用十六进制
表示,我相信用其他进制也可以,但常用的进制就这些,也没那个必要,谁那么无聊会用这种方法来访问一个网站呢?要真那样,设计域名就变得毫无意义了。
但作为技术人,我们不仅要知道可以这样访问网站,还要知道其中的奥秘,也就是原理,今天就来揭秘一下。
按照协议规定,在 IPv4 中,IP 地址用二进制表示,每个 IP 地址长 32 Bit,也是 4 个字节。为了方便,人们使用「 点分十进制表示法 」表示,即每个字节的二进制数被转成十进制数
每个字节中间使用符号『 . 』分开,即 172.217.6.127
。
也就是说,我们熟知的 IP 地址字符串,本质上由二进制数按点分规则
转换而来,那既然这 IP 是一个数,那它就可以用任何一种进制来表示
!
先来说十进制,也就是 IP 地址字符串如何转成十进制数字?
我们先来看看前面 IP 地址字符串(172.217.6.127)是怎么由二进制数转换而来的
它是把每一个字节(8 bit)转换成十进制数,关键就在每一个字节,而每一个字节最多能存 2^8 = 256 个数。我们去掉『 . 』,前面 IP 就变成 172
217
6
127
,也就是说它是一个 256 进制数 ,但别混淆是:127 在 256 进制中,其实就是相当于十进制的个位数,它还只是一位数而已,还没满 256,不产生进位,它是一个整体,不是多位数,其它的 172、217 在 256 进制中就相当于一位数,172
217
6
127
,在 256 进制中就四位数是而已。
而我们知道,其他进制转十进制数都非常简单,套用公式就可以。
假如一个 N 进制 X 位数是:abc……yz,那就是
所以 IP 172 217 6 127 转换成十进制数就是
172 * 256^3 + 217 * 256^2 + 6 * 256^1 + 127 * 256^0 = 2899904127
看到这里,相信都知道为什么我们输入协议加上数字也可以访问网站了吧。
Talk Is Cheap,Show Me Code
理解了,咋们来看代码,了解一下转换过程
public long ipToLong(String ipStr){
//用 . 分割 IP 字符串,取出其中的数字
//参数传得是正则表达式, . 有特殊意义,不能直接传,需要转义,或者用 [.] 匹配
String[] arrayIP = ipStr.split("\\.");
long sip1 = Integer.parseInt(arrayIP[0]);
long sip2 = Integer.parseInt(arrayIP[1]);
long sip3 = Integer.parseInt(arrayIP[2]);
long sip4 = Integer.parseInt(arrayIP[3]);
long r1 = sip1 * 256 * 256 * 256;
long r2 = sip2 * 256 * 256;
long r3 = sip3 * 256;
long r4 = sip4;
long result = r1 + r2 + r3 + r4;
return result;
}
这样的算法通俗易懂,但不高效,空间也占得多。 咋们优化一下,看看下面的
public long ipToLong(String ipStr) {
long result = 0;
String[] ipAddressInArray = ipStr.split("\\.");
for (int i = 3; i >= 0; i--) {
long ip = Long.parseLong(ipAddressInArray[3 - i]);
// 等同 A * 256^3 + B * 256^2 + C * 256^1 + D * 256^0,运用位移更高效
//1. 172 <<24 (左移 24 位相当于乘于 256^3 即 2^24,以下类似)
//2. 217 <<16
//3. 6 <<8
//4. 127 <<0
// 不同数位加相 | 相当于不同数位相加( 0 | X = X) ->1200 | 00XX = 12XX
result |= ip <<(i * 8);
}
return result;
}
是不是整个人都好了。 当然还有其他的写法,就不一一列举了。
另外,我们当然也可以先把 IP 字符串转成二进制数,再转成十进制树,但这样更麻烦,还是上面的方式来得更直接。
那数字转 IP 地址字符串呢?
有前面的基础,逆向思考,下面代码应该很好理解
public String longToIp(long ip) {
return ((ip >>24) &0xFF) + "."+
((ip >>16) &0xFF) + "." +
((ip >>8) &0xFF) + "." +
(ip &0xFF);
}
有人可能疑惑为什么要 &0xFF
?其实就是 &11111111
,目的就是为了与出一个字节(8 bit),因为点分法是按字节(8 bit)
拆分IP的,前面说了。
图文解释可以清楚一点,先要知道,在二进制运算中,0 &X = 0,1 &X = X
,0 与任何数都得 0,1 与任何数都得任何数。
最开始是这样的,十进制数字转成二进制数字,然后拆分为四部分,如下图
为了取到从左边起的第一个字节,左移 24 后,只剩下 10101100
再 &0xFF
即 &11111111
,即 得到 10101100,转换成十进制后就是 172
说到这,可能还不是很明白,看完下面就明白了。
再看,为了取到从左边起的第二个字节,左移 16,但得到 10101100 11011001,包含了左起第一个字节。
但是我要取的是第二个字节 11011001,所以就再 &0xFF
即 &11111111
,也就是
一 &,前面的字节就没了,这样,就取到了 11011001,再转换成十进制数后就是 217。
以此类推,就先位移,再分别 &,就取出每个字节,转成成十进制数,在添加上 . 就成了 IP 地址字符串了。不知我说明白没有!
至于 IP 字符串转其他进制的数字,相信看完前面的说明,不管是要 IP 字符串转成不同进制的数字还是从数字转成 IP 字符串,大家都能类推出来。