我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
标签 #科普

编码:二进制的世界

我们要聊的编码并不是「写代码(Coding)」,而是信息从一种形式被转换为另外一种形式的这个过程,即 Encoding.

二进制的世界

就像大家知道的那样,在计算机的世界中,信息必须被转换为二进制(Binary)才能被 CPU 计算、被内存或硬盘存储、在网络上传输。

相比于我们从小使用的十进制,二进制既不直观,又显得冗长,那么为什么计算机选择二进制作为内部的信息表现形式呢?

十进制意味着每个数位上有从 0 到 9 十种可能的状态,也就是十进制中的一位可以表示 10 种事物中的一种,两位则可以表示 100 种事物的一种。要不是人有十个手指,其实我们也可以使用八进制(偶尔会在计算机领域使用)或十二进制(时间和角度通常基于十二进制)。

那么我们可以考虑一下极端情况,最少可以用几进制来表示信息呢?很好猜到答案 —— 二进制,要表达一个有意义的信息,至少要能区分两种的情况中的一种吧。科学家喜欢追求一种「简洁之美」,既然我们找到了表示信息的最简形式,为什么不以它为基础来构筑计算机世界呢。

这种「信息的最小单位」被称为比特 (Bit),正因为它是信息的最小单位,比特位之间的运算规则也远比十进制简单得多(例如两个比特位相加只有 4 种情况);在工程上也得到了很多便利,例如比特的两种状态刚好被映射到信号的有无、低电压或高电压、连通或断开等等。

数字的编码

因为计算机选择了用比特作为信息的表现形式,所以各种其他的形式的信息必须被以某种方式被转换为若干个比特才能被计算机处理,这种过程就叫「编码」。

我们先从数字说起,数字是最容易被编码的信息了,因为比特可以被简单地看作二进制的数字,只需将我们平常的十进制数字换算到二进制即可。

在这种编码方式下,一个数字所需要的比特数正比于它的大小,比如用二进制表示 123 需要 7 个比特,而表示一亿则需要 27 个比特。使用这种方式编码数字存在一些局限性,例如需要大量的比特去表示一个较大的数、无法去表示小数点等。

所以还存在另一种被普遍使用的方法来编码数字,被称为「浮点数(Floating Point Number)表示法」。这种表示方法非常类似于「科学计数法」,它将一个数字拆分为三个部分来保存:正/负、指数、系数。这样的话,浮点数表示法可以在一个非常大的范围(受限于指数部分的位数)内表示包括小数在内的,具有一定精度(受限于系数部分的位数)数字。

字符串的编码

计算机中的「字符串(String)」就是人们通常所说的文字,一个字符串是由若干的「字符(Character)」构成的。而字符则取决于具体的语言,例如英语中的字符包括 26 个英文字母,而汉字则有两万余个字符。

计算机如何编码字符呢?储存它的图形显然会令问题复杂化,所以最简单的办法就是给每个字符一个编号,在计算机中储存和计算编号,直到需要展示的时候才绘制为人们所见到的图形。

最简单的一种字符编码方案就是 ASCII 了,它支持 128 个字符,包括大写和小写的 26 个英文字母、数字、英文标点和一些特殊字符。ASCII 将一个字符编码为 8 个比特,但实际只用到了 7 个比特而已(128 刚好是 2 的 7 次方)。

字符集和字符编码

为了对各种语言的字符提供支持,后来又出现了很多种将字符编号的规则,这种规则被称为「字符集(Character Set)」,例如中国官方最新的字符集标准被称为 GB18030, 包括了七万多个汉字和一些少数民族的字符。

字符集之间可能互不兼容,例如同样的一个二进制序列,在一个中文字符集中可能是一个汉字,而在其他的字符集中就可能是另外的字符,甚至是无法显示的字符。所以计算机在显示一段字符串的时候需要知道它的字符集,否则就会出现我们称之为「乱码」的情况,即字符串被以一种错误的字符集被显示,以至于无法阅读。

通常一段字符串只能使用一种字符集,一旦你选择了一种字符集,你就没有办法表示不在这个字符集中的字符。例如如果你选择了一个中文字符集,那么可能就没办法去表示韩文中的字符。为了解决这种不便,出现了一种叫「Unicode(暂译作通用字符集)」的字符集,它覆盖了世界上绝大部分语言的字符,甚至包括了经常被使用的表情符号(Emoji)。目前大部分的网页、编程语言、操作系统都使用 Unicode 来表示字符串。

和其他一些直接规定了编码方式的字符集不同,Unicode 本身只是对字符的编号,它定义了几种具体的编码方式。例如 UTF-8 最少使用 8 个比特,最多使用 48 个比特来表示一个字符,适合英文居多的场景(因为 ASCII 中的字符只需要 8 比特);UTF-32 则总是用 32 个比特表示一个字符,这样固定长度的编码方式会拥有更好的性能。

来自自然界的信息

计算机经常还被用来处理图片、音频、视频这类来自自然界的信息。我们所生活在的物理世界可以被认为是「连续」的。例如自然界中的声音,在任意一个时间点都可能会有频率和响度的变化,但在目前的计算机体系来看,这样的数据拥有无限大的信息量,没办法被编码。

于是我们使用「采样」的方式将这些信息转化为有限的「离散」的信息。例如我们每一毫秒去测量声音的频率,这样计算机就可以去编码每次测量到的频率的数字,将这段声音数字化了。

这种采样的过程是会丢失信息的,比如在两次测量之间的某一时间点的情况我们是不得而知的,只能推测它大概介于这两次结果之间。所以很好想象,丢失的信息取决于采样的频率,以越短的间隔采样,就会得到越多的信息,越接近原始的信息。

未完待续,下一篇将会介绍更多有关「编码」的内容。

为什么我无法访问一个网站

这是从我开始经营 RP 主机 以来,被问及的最多的一个问题。

本文将以诊断「为什么我无法访问一个网站」的方式,讲述 Web 服务的大致工作流程。

目录

  • URL
  • DNS
  • TCP/IP 连接
  • 加载资源
  • 渲染

URL

通常来讲,所谓访问一个网站,我们输入给浏览器的是类似于这样的一个地址:

http://jysperm.me/note/1519

用术语来讲,这是一个 URI, 一个 URI 有两个部分,一是协议,二是定位符。
比如「mailto:jysperm@gmail.com」这是一个邮箱的标识符,其中「mailto」是协议,「jysperm@gmail.com」是定位符。

再比如「urn:isbn:9787115281487」这是一本书(HTTP 权威指南)的标识符。

用来表示网页的 URI 有一个比较特殊的名字叫 URL, 它大致分成这几个部分:

  • http - 协议
  • jysperm.me - 主机名
  • /note/1519 - 路径

除此之外还可能会有类似于「?id=1519」的查询字符串。

这样的一个 URL, 便可以定位到一个网页。

DNS

首先我们要找到为我们提供服务的服务器,即 URL 中的主机名,在这里是 jysperm.me.

TCP/IP 网络中通过 IP 地址来唯一地定位一台主机,更确切地说,每一块网卡,会有一个唯一的 IP 地址。

IP 地址形如 117.121.25.186, 它本身是存在一定结构性的,比如 117.121.25.186 和 117.121.25.187,就有非常大的可能性两者的物理距离很近。

这样的结构性方便于计算机之间互相联络,但不便于人类记忆,于是我们用由字母组成的域名来表示主机。

我们通过一个叫 DNS 的系统,来将域名翻译成 IP 地址,DNS 系统本身说简单也简单,说复杂也复杂,在这里我们只讲述如何使用,而不研究其原理。

如果浏览器给你的提示是类似于「无法找到 jysperm.me」或者「无法连接到 jysperm.me」,那么有很大可能性是 DNS 查找失败或者出错。

我们可以使用 nslookup 这个命令来手动进行 DNS 查询

> nslookup jysperm.me
Server: 8.8.8.8
Address:    8.8.8.8#53

Non-authoritative answer:
Name:   jysperm.me
Address: 117.121.25.186

可以看到,我们向 8.8.8.8(Google 的免费公众 DNS), 查询 jysperm.me 这个域名,它返回给我们的结果是 117.121.25.186.

这里是 8.8.8.8 是系统的默认 DNS 服务器,我们还可以自己来指定 DNS 查询服务器:

> nslookup jysperm.me 114.114.114.114
Server: 114.114.114.114
Address:    114.114.114.114#53

Non-authoritative answer:
Name:   jysperm.me
Address: 117.121.25.186

这里使用了国内的 114.114.114.114 进行查询,返回的结果是一样的。

在某些网络封锁比较严重的国家,DNS 查询的结果可能会被篡改,因为 DNS 本身几乎没有任何安全措施,所以查询到的结果可能并不是可靠的。

TCP/IP 连接

知道了服务器的 IP 地址,下一步是建立 TCP 连接。这一步最常遇到的问题有:

  • 无法连接到服务器/连接超时
  • 连接被拒绝
  • 连接被重置

首先 IP 层面,要向一台主机发送数据,这个主机必须是「可达的」,换句话说就是对方必须在线。

最简单的,我们可以用 ping 命令验证一个主机是否在线:

> ping 117.121.25.186
PING 117.121.25.186 (117.121.25.186): 56 data bytes
64 bytes from 117.121.25.186: icmp_seq=0 ttl=128 time=138.651 ms
64 bytes from 117.121.25.186: icmp_seq=1 ttl=128 time=71.345 ms
64 bytes from 117.121.25.186: icmp_seq=2 ttl=128 time=286.960 ms

ping 使用的是 ICMP ECHO 指令,就好比你跟对方打招呼,问它是否在线,如果对方回应了,就说明它在线。但是,并非所有服务器都会回应 ICMP ECHO.

如果发现 ping 命令没有回应,类似于下面的情况:

> ping 59.24.3.173
PING 59.24.3.173 (59.24.3.173): 56 data bytes
Request timeout for icmp_seq 0
Request timeout for icmp_seq 1
Request timeout for icmp_seq 2

那么就说明你和服务器直接没有建立连通的连接,但是,连接是在那里断开的呢?这时我们可以用 traceroute 来探测数据包所走过的路径:

> traceroute www.qq.com
traceroute to www.qq.com (180.96.86.192), 64 hops max, 52 byte packets
 1  * * *
 2  100.64.0.1 (100.64.0.1)  10.224 ms  5.379 ms  6.794 ms
 3  218.4.12.229 (218.4.12.229)  8.342 ms  6.817 ms  8.169 ms
 4  218.94.165.2 (218.94.165.2)  8.128 ms  8.259 ms  7.173 ms
 5  61.155.133.121 (61.155.133.121)  11.152 ms  9.288 ms  9.990 ms
 6  202.102.69.254 (202.102.69.254)  11.464 ms  12.463 ms  11.027 ms
 7  180.96.51.94 (180.96.51.94)  13.674 ms  13.192 ms  12.364 ms
 8  180.96.48.202 (180.96.48.202)  13.564 ms  11.213 ms  13.945 ms
 9  * * *

traceroute 依赖于 ICMP, 并非所有服务器都开启了 ICMP, 因此会有一些路径点被显示为星号。

我们再来看另一个不可达的例子:

> traceroute twitter.com
traceroute to twitter.com (59.24.3.173), 64 hops max, 52 byte packets
 1  * * *
 2  100.64.0.1 (100.64.0.1)  4.694 ms  4.502 ms  8.044 ms
 3  218.4.12.237 (218.4.12.237)  8.874 ms  5.177 ms  8.133 ms
 4  218.4.13.49 (218.4.13.49)  15.205 ms  11.400 ms  12.011 ms
 5  202.97.55.41 (202.97.55.41)  14.011 ms  15.794 ms  16.518 ms
 6  202.97.50.254 (202.97.50.254)  12.207 ms  12.614 ms  13.165 ms
 7  202.97.35.78 (202.97.35.78)  12.246 ms
    202.97.35.22 (202.97.35.22)  13.337 ms
    202.97.34.126 (202.97.34.126)  13.701 ms
 8  202.97.60.33 (202.97.60.33)  58.292 ms  12.432 ms  18.093 ms
12  * * *

可以看到,最后一个可以追踪到的路径点是 202.97.60.33, 通过一些 IP 库可以查到,这个地址属于「广东省广州市 互联网交换中心」,可以打个电话问问他们那边出了什么情况。

加载资源

访问一个网页会发起不止一个请求,除了网页本身还会加载一些例如样式,图片,脚本之类的资源,甚至很多时候还会加载来自其他网站的资源,通过 Chrome 的开发人员工具中的「Network」选项卡,可以看到加载一个页面所发起的所有请求。

这是一个正常的网站, 所有的资源都加载成功了:

正常网站的加载情况

再看下面这张图,其中一个资源没有加载完成,导致整个页面都无法完成,这时我们就可以通过上面的方法单独排查这一个请求。

没有加载完全的情况

通过浏览器来检查一个资源究竟能不能下载在一些复杂情况下并不可靠,因为浏览器会为你的请求加上大量的参数,而且在得到资源后,还会进行渲染。

因此为了「纯粹」地下载一个资源,我们可以用 curl:

> curl http://pomotodo.com -I
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Fri, 01 Aug 2014 03:51:35 GMT
Content-Type: text/html
Content-Length: 154
Connection: keep-alive
Location: https://pomotodo.com/

可以看到 http://pomotodo.com 并非像我们看到的那样显示了一个页面,而是发起了一个到 SSL 版本地址的重定向,SSL 可以保证传输的私密性,目前国内只有很小一部分有节操的网站在使用。

渲染

等到所有资源都加载完了,但是页面还是没有内容,或者乱七八糟怎么办?

在正确地得到所有资源后,浏览器会执行网站提供的脚本,来对资源进行加工,以便像网站制作者期望的那样来展现页面。

在这个过程中,脚本需要使用浏览器提供的 API 来进行渲染,而不同的浏览器所支持的 API 是有差异的,因此一些制作不是很精良的网站会在不同的浏览器中呈现不同的效果,甚至直接出错。

在此建议大家选择一个现代而强大的浏览器,比如 Chrome, 这样可以保证浏览器支持网页所需要的功能。

1

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2024-04-08.