2012年5月31日星期四

阮一峰的网络日志

阮一峰的网络日志


互联网协议入门(一)

Posted: 30 May 2012 09:44 PM PDT

我们每天使用互联网,你是否想过,它是如何实现的?

全世界几十亿台电脑,连接在一起,两两通信。上海的某一块网卡送出信号,洛杉矶的另一块网卡居然就收到了,两者实际上根本不知道对方的物理位置,你不觉得这是很神奇的事情吗?

互联网的核心是一系列协议,总称为"互联网协议"(Internet Protocol Suite)。它们对电脑如何连接和组网,做出了详尽的规定。理解了这些协议,就理解了互联网的原理。

下面就是我的学习笔记。因为这些协议实在太复杂、太庞大,我想整理一个简洁的框架,帮助自己从总体上把握它们。为了保证简单易懂,我做了大量的简化,有些地方并不全面和精确,但是应该能够说清楚互联网的原理。

=================================================

互联网协议入门

作者:阮一峰

一、概述

1.1 五层模型

互联网的实现,分成好几层。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。

用户接触到的,只是最上面的一层,根本没有感觉到下面的层。要理解互联网,必须从最下层开始,自下而上理解每一层的功能。

如何分层有不同的模型,有的模型分七层,有的分四层。我觉得,把互联网分成五层,比较容易解释。

如上图所示,最底下的一层叫做"实体层"(Physical Layer),最上面的一层叫做"应用层"(Application Layer),中间的三层(自下而上)分别是"链接层"(Link Layer)、"网络层"(Network Layer)和"传输层"(Transport Layer)。越下面的层,越靠近硬件;越上面的层,越靠近用户。

它们叫什么名字,其实并不重要。只需要知道,互联网分成若干层就可以了。

1.2 层与协议

每一层都是为了完成一种功能。为了实现这些功能,就需要大家都遵守共同的规则。

大家都遵守的规则,就叫做"协议"(protocol)。

互联网的每一层,都定义了很多协议。这些协议的总称,就叫做"互联网协议"(Internet Protocol Suite)。它们是互联网的核心,下面介绍每一层的功能,主要就是介绍每一层的主要协议。

二、实体层

我们从最底下的一层开始。

电脑要组网,第一件事要干什么?当然是先把电脑连起来,可以用光缆、电缆、双绞线、无线电波等方式。

这就叫做"实体层",它就是把电脑连接起来的物理手段。它主要规定了网络的一些电气特性,作用是负责传送0和1的电信号。

三、链接层

3.1 定义

单纯的0和1没有任何意义,必须规定解读方式:多少个电信号算一组?每个信号位有何意义?

这就是"链接层"的功能,它在"实体层"的上方,确定了0和1的分组方式。

3.2 以太网协议

早期的时候,每家公司都有自己的电信号分组方式。逐渐地,一种叫做"以太网"(Ethernet)的协议,占据了主导地位。

以太网规定,一组电信号构成一个数据包,叫做"帧"(Frame)。每一帧分成两个部分:标头(Head)和数据(Data)。

"标头"包含数据包的一些说明项,比如发送者、接受者、数据类型等等;"数据"则是数据包的具体内容。

"标头"的长度,固定为18字节。"数据"的长度,最短为46字节,最长为1500字节。因此,整个"帧"最短为64字节,最长为1518字节。如果数据很长,就必须分割成多个帧进行发送。

3.3 MAC地址

上面提到,以太网数据包的"标头",包含了发送者和接受者的信息。那么,发送者和接受者是如何标识呢?

以太网规定,连入网络的所有设备,都必须具有"网卡"接口。数据包必须是从一块网卡,传送到另一块网卡。网卡的地址,就是数据包的发送地址和接收地址,这叫做MAC地址。

每块网卡出厂的时候,都有一个全世界独一无二的MAC地址,长度是48个二进制位,通常用12个十六进制数表示。

前6个十六进制数是厂商编号,后6个是该厂商的网卡流水号。有了MAC地址,就可以定位网卡和数据包的路径了。

3.4 广播

定义地址只是第一步,后面还有更多的步骤。

首先,一块网卡怎么会知道另一块网卡的MAC地址?

回答是有一种ARP协议,可以解决这个问题。这个留到后面介绍,这里只需要知道,以太网数据包必须知道接收方的MAC地址,然后才能发送。

其次,就算有了MAC地址,系统怎样才能把数据包准确送到接收方?

回答是以太网采用了一种很"原始"的方式,它不是把数据包准确送到接收方,而是向本网络内所有计算机发送,让每台计算机自己判断,是否为接收方。

上图中,1号计算机向2号计算机发送一个数据包,同一个子网络的3号、4号、5号计算机都会收到这个包。它们读取这个包的"标头",找到接收方的MAC地址,然后与自身的MAC地址相比较,如果两者相同,就接受这个包,做进一步处理,否则就丢弃这个包。这种发送方式就叫做"广播"(broadcasting)。

有了数据包的定义、网卡的MAC地址、广播的发送方式,"链接层"就可以在多台计算机之间传送数据了。

四、网络层

4.1 网络层的由来

以太网协议,依靠MAC地址发送数据。理论上,单单依靠MAC地址,上海的网卡就可以找到洛杉矶的网卡了,技术上是可以实现的。

但是,这样做有一个重大的缺点。以太网采用广播方式发送数据包,所有成员人手一"包",不仅效率低,而且局限在发送者所在的子网络。也就是说,如果两台计算机不在同一个子网络,广播是传不过去的。这种设计是合理的,否则互联网上每一台计算机都会收到所有包,那会引起灾难。

互联网是无数子网络共同组成的一个巨型网络,很像想象上海和洛杉矶的电脑会在同一个子网络,这几乎是不可能的。

因此,必须找到一种方法,能够区分哪些MAC地址属于同一个子网络,哪些不是。如果是同一个子网络,就采用广播方式发送,否则就采用"路由"方式发送。("路由"的意思,就是指如何向不同的子网络分发数据包,这是一个很大的主题,本文不涉及。)遗憾的是,MAC地址本身无法做到这一点。它只与厂商有关,与所处网络无关。

这就导致了"网络层"的诞生。它的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做"网络地址",简称"网址"。

于是,"网络层"出现以后,每台计算机有了两种地址,一种是MAC地址,另一种是网络地址。两种地址之间没有任何联系,MAC地址是绑定在网卡上的,网络地址则是管理员分配的,它们只是随机组合在一起。

网络地址帮助我们确定计算机所在的子网络,MAC地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理MAC地址。

4.2 IP协议

规定网络地址的协议,叫做IP协议。它所定义的地址,就被称为IP地址。

目前,广泛采用的是IP协议第四版,简称IPv4。这个版本规定,网络地址由32个二进制位组成。

习惯上,我们用分成四段的十进制数表示IP地址,从0.0.0.0一直到255.255.255.255。

互联网上的每一台计算机,都会分配到一个IP地址。这个地址分成两个部分,前一部分代表网络,后一部分代表主机。比如,IP地址172.16.254.1,这是一个32位的地址,假定它的网络部分是前24位(172.16.254),那么主机部分就是后8位(最后的那个1)。处于同一个子网络的电脑,它们IP地址的网络部分必定是相同的,也就是说172.16.254.2应该与172.16.254.1处在同一个子网络。

但是,问题在于单单从IP地址,我们无法判断网络部分。还是以172.16.254.1为例,它的网络部分,到底是前24位,还是前16位,甚至前28位,从IP地址上是看不出来的。

那么,怎样才能从IP地址,判断两台计算机是否属于同一个子网络呢?这就要用到另一个参数"子网掩码"(subnet mask)。

所谓"子网掩码",就是表示子网络特征的一个参数。它在形式上等同于IP地址,也是一个32位二进制数字,它的网络部分全部为1,主机部分全部为0。比如,IP地址172.16.254.1,如果已知网络部分是前24位,主机部分是后8位,那么子网络掩码就是11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。

知道"子网掩码",我们就能判断,任意两个IP地址是否处在同一个子网络。方法是将两个IP地址与子网掩码分别进行AND运算(两个数位都为1,运算结果为1,否则为0),然后比较结果是否相同,如果是的话,就表明它们在同一个子网络中,否则就不是。

比如,已知IP地址172.16.254.1和172.16.254.233的子网掩码都是255.255.255.0,请问它们是否在同一个子网络?两者与子网掩码分别进行AND运算,结果都是172.16.254.0,因此它们在同一个子网络。

总结一下,IP协议的作用主要有两个,一个是为每一台计算机分配IP地址,另一个是确定哪些地址在同一个子网络。

4.3 IP数据包

根据IP协议发送的数据,就叫做IP数据包。不难想象,其中必定包括IP地址信息。

但是前面说过,以太网数据包只包含MAC地址,并没有IP地址的栏位。那么是否需要修改数据定义,再添加一个栏位呢?

回答是不需要,我们可以把IP数据包直接放进以太网数据包的"数据"部分,因此完全不用修改以太网的规格。这就是互联网分层结构的好处:上层的变动完全不涉及下层的结构。

具体来说,IP数据包也分为"标头"和"数据"两个部分。

"标头"部分主要包括版本、长度、IP地址等信息,"数据"部分则是IP数据包的具体内容。它放进以太网数据包后,以太网数据包就变成了下面这样。

IP数据包的"标头"部分的长度为20到60字节,整个数据包的总长度最大为65,535字节。因此,理论上,一个IP数据包的"数据"部分,最长为65,515字节。前面说过,以太网数据包的"数据"部分,最长只有1500字节。因此,如果IP数据包超过了1500字节,它就需要分割成几个以太网数据包,分开发送了。

4.4 ARP协议

关于"网络层",还有最后一点需要说明。

因为IP数据包是放在以太网数据包里发送的,所以我们必须同时知道两个地址,一个是对方的MAC地址,另一个是对方的IP地址。通常情况下,对方的IP地址是已知的(后文会解释),但是我们不知道它的MAC地址。

所以,我们需要一种机制,能够从IP地址得到MAC地址。

这里又可以分成两种情况。第一种情况,如果两台主机不在同一个子网络,那么事实上没有办法得到对方的MAC地址,只能把数据包传送到两个子网络连接处的"网关"(gateway),让网关去处理。

第二种情况,如果两台主机在同一个子网络,那么我们可以用ARP协议,得到对方的MAC地址。ARP协议也是发出一个数据包(包含在以太网数据包中),其中包含它所要查询主机的IP地址,在对方的MAC地址这一栏,填的是FF:FF:FF:FF:FF:FF,表示这是一个"广播"地址。它所在子网络的每一台主机,都会收到这个数据包,从中取出IP地址,与自身的IP地址进行比较。如果两者相同,都做出回复,向对方报告自己的MAC地址,否则就丢弃这个包。

总之,有了ARP协议之后,我们就可以得到同一个子网络内的主机MAC地址,可以把数据包发送到任意一台主机之上了。

五、传输层

5.1 传输层的由来

有了MAC地址和IP地址,我们已经可以在互联网上任意两台主机上建立通信。

接下来的问题是,同一台主机上有许多程序都需要用到网络,比如,你一边浏览网页,一边与朋友在线聊天。当一个数据包从互联网上发来的时候,你怎么知道,它是表示网页的内容,还是表示在线聊天的内容?

也就是说,我们还需要一个参数,表示这个数据包到底供哪个程序(进程)使用。这个参数就叫做"端口"(port),它其实是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据。

"端口"是0到65535之间的一个整数,正好16个二进制位。0到1023的端口被系统占用,用户只能选用大于1023的端口。不管是浏览网页还是在线聊天,应用程序会随机选用一个端口,然后与服务器的相应端口联系。

"传输层"的功能,就是建立"端口到端口"的通信。相比之下,"网络层"的功能是建立"主机到主机"的通信。只要确定主机和端口,我们就能实现程序之间的交流。因此,Unix系统就把主机+端口,叫做"套接字"(socket)。有了它,就可以进行网络应用程序开发了。

5.2 UDP协议

现在,我们必须在数据包中加入端口信息,这就需要新的协议。最简单的实现叫做UDP协议,它的格式几乎就是在数据前面,加上端口号。

UDP数据包,也是由"标头"和"数据"两部分组成。

"标头"部分主要定义了发出端口和接收端口,"数据"部分就是具体的内容。然后,把整个UDP数据包放入IP数据包的"数据"部分,而前面说过,IP数据包又是放在以太网数据包之中的,所以整个以太网数据包现在变成了下面这样:

UDP数据包非常简单,"标头"部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

5.3 TCP协议

UDP协议的优点是比较简单,容易实现,但是缺点是可靠性较差,一旦数据包发出,无法知道对方是否收到。

为了解决这个问题,提高网络可靠性,TCP协议就诞生了。这个协议非常复杂,但可以近似认为,它就是有确认机制的UDP协议,每发出一个数据包都要求确认。如果有一个数据包遗失,就收不到确认,发出方就知道有必要重发这个数据包了。

因此,TCP协议能够确保数据不会遗失。它的缺点是过程复杂、实现困难、消耗较多的资源。

TCP数据包和UDP数据包一样,都是内嵌在IP数据包的"数据"部分。TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。

六、应用层

应用程序收到"传输层"的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。

"应用层"的作用,就是规定应用程序的数据格式。

举例来说,TCP协议可以为各种各样的程序传递数据,比如Email、WWW、FTP等等。那么,必须有不同协议规定电子邮件、网页、FTP数据的格式,这些应用程序协议就构成了"应用层"。

这是最高的一层,直接面对用户。它的数据就放在TCP数据包的"数据"部分。因此,现在的以太网的数据包就变成下面这样。

至此,整个互联网的五层结构,自下而上全部讲完了。这是从系统的角度,解释互联网是如何构成的。下一次,我反过来,从用户的角度,自上而下看看这个结构是如何发挥作用,完成一次网络数据交换的。

(完)

文档信息

2012年5月13日星期日

阮一峰的网络日志

阮一峰的网络日志


失败的总和----读《黄河青山:黄仁宇回忆录》

Posted: 13 May 2012 12:10 AM PDT

历史学家黄仁宇的最出名著作,大概非《万历十五年》莫属。

可是,我更喜欢他的回忆录《黄河青山》

多年前,在学校图书馆读到的时候,就觉得这本书很特别,讲述了一个国民党军官如何变成一个历史学家,夹叙夹议,将个人命运放在历史背景上,写得非常生动。

========================================================================

最近,我重读这本书,才发觉以前读得太粗糙,完全没有领会作者的用意。通常来说,自传总是记录一些令人骄傲的个人经历,可是打开《黄河青山》,你只看到一件接着一件的失败。

全书一开始,就是黄仁宇的恋爱失败:

"1945年末,我遇见一个比自己小一岁的女孩,名字叫安,我对她一见倾心。许多天的下午及黄昏时刻,我从第三方面军总部借出吉普车,停在她家门口。佣人带我进到起居室,我就一直等,只听到走道中某处有座老式座钟发出的滴答声。安很少让我只等二十分钟。

即使我的虚荣心再强,都无法骗自己说,安曾经爱过我。......这样也好,因为如果她说,"让我们结婚吧,你最好认真一点",我就会不知所措。我的新羊毛制服经过适当的熨烫后,勉强让我可以在上海的社交圈中走动。但除此以外,身为上尉的我,甚至负担不起一间套房。我的母亲、弟弟及妹妹仍然住在重庆的山间破屋中,甚至没有自来水可用,更不要说每一层都有浴室了。"

然后,他开始讲自己选择人生道路的失败。1937年,他是南开大学电机系的二年级学生,听到日军入侵,不顾父亲的反对,毅然决定投笔从戎。

"抗战爆发后不久,沿海各处相继失陷,我即立意去从军。"

30年代,大学生当兵是一件极其稀有的事情。这种爱国热情当然值得肯定,可是从历史角度来看,他放弃工程师生涯,选择加入军队,等于选择了失败的人生。而且,毫不意外地,他不选择加入共产党。

"1938年,我个人反对延安是因为他们教的是游击战,并不合我的胃口。我觉得如果要当职业军人,就应该领导军队进攻。我甚至想当拿破仑。躲在暗处放冷箭,然后快速逃走,听起来可不光彩,不是我要做的事。"

讽刺的是,加入国民党军以后,他也没有上战场,而是被送到成都中央军校,大部分时间都在练习踢正步,等到三年后出来,抗日战争已经接近尾声。他作为军官被派到云南前线,可是日军临时放弃进攻云南,导致他连续几个月驻守在大山里,无所事事。这时,他开始体会到理想与现实的巨大差距。

"我们还停留在明朝的条件。如果我需要一头驴来驮负重物,我必须派士兵到村落里去找村长,在枪支的威胁下,他可能听从我们的差遣。至于邮政,要送一封信到邻近的省份,必须耗上一个月的时间。我必须慎选词汇,才能让村民听懂我说的话。"

"士兵穿着冬季的棉袄蜷缩身体入睡,用蚊帐、毛毯或帆布当被子,抓到什么就盖什么,甚至几个人合盖一床被。地板上则铺着稻草,这样的环境造就了虱子的天堂。"

"我们的兵士每月薪饷十二元,身为上尉的我,月薪也不过四十元。可是,山头上的土匪开出每支枪七千元的条件,而且保障携枪逃亡者的安全。......有些连队晚上把步枪锁起来,军官睡觉时把手枪放在枕头下。"

战争的最后阶段,黄仁宇的部队开赴缅甸,终于与日军正面作战了。可是,他在书中一笔带过那些"光辉经历",比如,被日军狙击手击中大腿,差点丧命,或者给全国第一大报《大公报》当战地记者,后来出了一本《缅北之战》。详细写的,却是下面这样的事情:

"一大块生铁从炮壳剥落,飞落到身旁不远处,我才知道自己逃过一劫。我本能想捡起来当纪念品,却发现铁片滚烫难耐,手掌几乎长水泡。"

"一天晚上,自部队后方传来'卡碰'声,前方部队于是向我们还击。一片混乱中,后方部队也朝我们射击,机关枪及迫击炮此起彼落。为了避免被击中,我们尽量压低身体,浸泡在湿寒冰冷中。"

日军投降后,内战开始,他始终不受重用,最后还被怀疑可能叛变。调查表明他是清白的,但是他最终还是被强制退役。

"我不知道台北当局如何处理我的退役。我请成都中央军校的同班同学汪奉曾上校回台北时,帮我查查我在国防部的档案。他说我的退役完全合乎规定,记录上还添了备注:'该军官应永远不再委任或聘用'。"

既然成了平民,大陆和台湾都回不了,黄仁宇只好来到美国,以34岁的"高龄"重新进入大学读本科。

"时年34岁还是大学生的我,除了学费偶尔可以延后缴纳外,得不到任何单位的帮助,长期的工读生涯就成为很自然的结果。有一次,唐纳德·季林教授问我几个中国内战的问题,我那时在当电梯服务员。我对他说,我不介意回答他的问题,但我必须工作,他可能要上上下下电梯好几次。"

他有过各种各样的打工经历。

"我经历过各种工作形态:全职工作、兼职工作、一周上两天班、只在周末和学校放假日上班、完全停掉工作、重新申请等等,大部分是在餐饮业。"

"在餐厅当打杂小弟,必须穿上浆过的白制服,戴上顶端有个网子的白帽。店内有儿童时,收银员会按铃,我就冲上前去帮他们处理杯盘。我第一次做这件事时,一位年轻的妈妈对儿子说:'把盘子留着,只要给那个中国人就行了。'小孩好像听不懂,她又说:'艾瑞克,我告诉你,只要给那个中国小弟就行了!'我当时已年近四十,待在学校的时间多过其他人。不过我也找不到抱怨的原因,谁叫我做的工作就是打杂'小弟'呢。"

博士毕业后,依靠老师余英时的帮忙,他才在纽约州一所师范类大学找到了一个教职。可是,一所美国地方大学,会有多少学生对中国古代史的课程感兴趣呢?

"只有6到10名学生选我的课,一半以上消失得无影无踪,或是不定期来上课,我根本无法准备教材,不知该针对谁的水准来上课。负责任的学生向我抱怨,宿舍太过吵闹喧嚣,再也无法念书(,所以来上课)。懒惰的学生持续扰乱我上课,有一名学生已经缺席两星期,竟然在课堂上要我简述前两堂课的内容。如果不回答这种扰乱秩序的问题,只会弘扬我心胸偏狭的名声。"

"我已经养成习惯,只要学生连续缺席几次,我就设法联络他们。我的学生一开始就很少,可不能再丢掉任何一个。"

更糟糕的是,1979年,校方通知黄仁宇,他被解聘了。那时,他已经61岁了。

"当天晚上,妻子将消息告知我们的儿子。当时他只有11岁,还在念中学。在这个很小的大学城,人人都知道别人的举动及遭遇。直到今天,只要想到1979年3月27日那一天,我的儿子如何接受这个令人不快的消息,我就觉得很难过。儿子知道他的父亲已被解聘,而许多同学的父母却在大学里有杰出表现。有人的妈妈最近被选为系主任,有人的父亲筹组野外探险队,带学生去特殊景点,但他的父亲却被解聘了。他仍然坚持要我去参观他的赛跑大会和学校音乐会,但在心里一定也和父母一样难过。有些同学好奇地问他,你爸爸下一步要怎么办?我接到通知的数天后,邻家十岁男童丹尼走近在后院的我:'你要卖房子吗?'"

解聘以后,找不到工作。

"我没有办法再找到另一个职位,即使朋友们试着帮我忙,但没有人会雇用一个刚被解聘的六十多岁的人。"

生活水准急剧下降。

"我被解聘后,就没有找到工作,也没有申请到研究经费。目前,我的家庭支出大半依靠社会福利津贴,每个月500美元,我的妻子和儿子也可以各领450美元。此外,我每个月的教师年金300美元。这些钱让我们勉强维生,略微超过最低生活水平。我的版税收入可以用来缴税,有时还要动用我妻子的储蓄。我只要一听到热水器要更新,或是屋顶有破洞,心都会一阵抽痛。我们可以设法偶尔附近玩玩,但如果要去一次纽约,家庭预算就必须重新大幅更动。我每次定大笔出版品或买几本书时,就必须考虑财源。"

直到《万历十五年》出版,在中国引起轰动,黄仁宇的经济状况才开始逐步改善。自传也就写到这个地方。

==================================================================

看了上面摘录,我们不禁要问,为什么黄仁宇只强调自己的人生失败,他想证明什么?

我联想到了《万历十五年》,里面一共写了六个人物----万历皇帝、申时行、张居正、海瑞、戚继光、李贽----他们也全部失败了。事实上,《万历十五年》的主题就是,中国作为一个整体的失败。它的结尾是这样的:

"当一个人口众多的国家,个人行动全凭儒家简单粗浅而又无法固定的原则所限制,而法律又缺乏创造性,则其社会发展的程度,必然受到限制。即便是宗旨善良,也不能补助技术之不及。1587年,是为万历十五年,丁亥次岁,表面上似乎是四海升平,无事可记,实际上我们的大明帝国却已经走到了它发展的尽头。在这个时候,皇帝的励精图治或者宴安耽乐,首辅的独裁或者调和,高级将领的富于创造或者习于苟安,文官的廉洁奉公或者贪污舞弊,思想家的极端进步或者绝对保守,最后的结果,都是无分善恶,统统不能在事业上取得有意义的发展,有的身败,有的名裂,还有的人则身败而兼名裂。

因此我们的故事只好在这里作悲剧性的结束。万历丁亥年的年鉴,是为历史上一部失败的总记录。"

仔细阅读这段话,"最后的结果,都是无分善恶,统统不能在事业上取得有意义的发展",这就是说,失败是不可避免的。《万历十五年》的主题是,中国的失败不可避免;那么《黄河青山》的意思是不是说,黄仁宇个人的失败不可避免?两者之间有什么联系吗?

"我写回忆录不是为了自己,而是为了说明我的背景,为了特定的历史史观。"

显然,黄仁宇在用自传,解释他的历史观。

"在美国读书和打工时,我常被在中国的痛苦回忆所折磨,不时陷入沉思。后来当教师,拿着麦克风站在五百名大学生面前,无法立即解释:为何康有为失败了,孙中山失败了,袁世凯失败了,张作霖失败了,陈独秀失败了,蒋介石失败了,而毛泽东也失败了。为使我的讲课内容前后一致又有说服力,唯一的方法就是说,中国的问题大于上述人士努力的总和。中国文明将和西方文明融合的说法,是人类历史上空前的事件。上述不同阶段的失败必须被视为阶段的调试,以达成一致的终点。对我们这些有后见之识的人来说,这点很明显,但舞台上的演员看不到。"

这是黄仁宇在解释为什么他要写《万历十五年》,"中国的问题大于上述人士努力的总和。上述不同阶段的失败必须被视为阶段的调试,以达成一致的终点。"那么,推广到黄仁宇自己身上,是不是他在暗示,自己的各种失败大于努力的总和,而这些失败必须被视为对历史的阶段性调试,最终将到达一个更深远的终点?

"我开始领悟,为何我必须在生命中见识如此多的奇人异事,面临如此多的暴力。我恰巧出生在中国政治的最低点,以及人心惶惶的最高点。

我阅读的东西,听过的对话,在中国见证的事件,都只有在我迁居美国多年后才产生意义。由于离主体很远,又有够长的时间来发展后见之明,终于可以轮到我说,我懂了。"

黄仁宇是在说,他的个人失败,是20世纪中国遭受挫折的一种个体反映。

"以长期观点阅读中国现代历史时,就不会连连沮丧,反而会看到全本的戏剧在眼前开展。中国历史很可能即将融入世界历史,不但是空前的进展,而且是实质上的融和,不再缺乏希望与期许,纵使还会有挫败及暂时的逆转。"

如果你看到了历史的长期合理性,那么当你经历了种种失败,年老时回望自己人生,才能平静地接受命运,体会其中的必然,然后静静地等待隧道的尽头开始展现一丝曙光。这大概就是《黄河青山》的写作目的吧。

(完)

文档信息

2012年5月1日星期二

阮一峰的网络日志

阮一峰的网络日志


自适应网页设计(Responsive Web Design)

Posted: 30 Apr 2012 11:57 PM PDT

随着3G的普及,越来越多的人使用手机上网。

移动设备正超过桌面设备,成为访问互联网的最常见终端。于是,网页设计师不得不面对一个难题:如何才能在不同大小的设备上呈现同样的网页?

手机的屏幕比较小,宽度通常在600像素以下;PC的屏幕宽度,一般都在1000像素以上(目前主流宽度是1366×768),有的还达到了2000像素。同样的内容,要在大小迥异的屏幕上,都呈现出满意的效果,并不是一件容易的事。

很多网站的解决方法,是为不同的设备提供不同的网页,比如专门提供一个mobile版本,或者iPhone / iPad版本。这样做固然保证了效果,但是比较麻烦,同时要维护好几个版本,而且如果一个网站有多个portal(入口),会大大增加架构设计的复杂度。

于是,很早就有人设想,能不能"一次设计,普遍适用",让同一张网页自动适应不同大小的屏幕,根据屏幕宽度,自动调整布局(layout)?

一、"自适应网页设计"的概念

2010年,Ethan Marcotte提出了"自适应网页设计"(Responsive Web Design)这个名词,指可以自动识别屏幕宽度、并做出相应调整的网页设计。

他制作了一个范例,里面是《福尔摩斯历险记》六个主人公的头像。如果屏幕宽度大于1300像素,则6张图片并排在一行。

如果屏幕宽度在600像素到1300像素之间,则6张图片分成两行。

如果屏幕宽度在400像素到600像素之间,则导航栏移到网页头部。

如果屏幕宽度在400像素以下,则6张图片分成三行。

mediaqueri.es上面有更多这样的例子。

这里还有一个测试小工具,可以在一张网页上,同时显示不同分辨率屏幕的测试效果,我推荐安装。

二、允许网页宽度自动调整

"自适应网页设计"到底是怎么做到的?其实并不难。

首先,在网页代码的头部,加入一行viewport元标签

  <meta name="viewport" content="width=device-width, initial-scale=1" />

viewport是网页默认的宽度和高度,上面这行代码的意思是,网页宽度默认等于屏幕宽度(width=device-width),原始缩放比例(initial-scale=1)为1.0,即网页初始大小占屏幕面积的100%。

所有主流浏览器都支持这个设置,包括IE9。对于那些老式浏览器(主要是IE6、7、8),需要使用css3-mediaqueries.js

  <!--[if lt IE 9]>
    <script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
  <![endif]-->

三、不使用绝对宽度

由于网页会根据屏幕宽度调整布局,所以不能使用绝对宽度的布局,也不能使用具有绝对宽度的元素。这一条非常重要。

具体说,CSS代码不能指定像素宽度:

  width:xxx px;

只能指定百分比宽度:

  width: xx%;

或者

  width:auto;

四、相对大小的字体

字体也不能使用绝对大小(px),而只能使用相对大小(em)。

  body {
    font: normal 100% Helvetica, Arial, sans-serif;
  }

上面的代码指定,字体大小是页面默认大小的100%,即16像素。

  h1 {
    font-size: 1.5em;
  }

然后,h1的大小是默认大小的1.5倍,即24像素(24/16=1.5)。

  small {
    font-size: 0.875em;
  }

small元素的大小是默认大小的0.875倍,即14像素(14/16=0.875)。

五、流动布局(fluid grid)

"流动布局"的含义是,各个区块的位置都是浮动的,不是固定不变的。

  .main {
    float: right;
    width: 70%;
  }

  .leftBar {
    float: left;
    width: 25%;
  }

float的好处是,如果宽度太小,放不下两个元素,后面的元素会自动滚动到前面元素的下方,不会在水平方向overflow(溢出),避免了水平滚动条的出现。

另外,绝对定位(position: absolute)的使用,也要非常小心。

六、选择加载CSS

"自适应网页设计"的核心,就是CSS3引入的Media Query模块。

它的意思就是,自动探测屏幕宽度,然后加载相应的CSS文件。

  <link rel="stylesheet" type="text/css"
    media="screen and (max-device-width: 400px)"
    href="tinyScreen.css" />

上面的代码意思是,如果屏幕宽度小于400像素(max-device-width: 400px),就加载tinyScreen.css文件。

  <link rel="stylesheet" type="text/css"
    media="screen and (min-width: 400px) and (max-device-width: 600px)"
    href="smallScreen.css" />

如果屏幕宽度在400像素到600像素之间,则加载smallScreen.css文件。

除了用html标签加载CSS文件,还可以在现有CSS文件中加载。

  @import url("tinyScreen.css") screen and (max-device-width: 400px);

七、CSS的@media规则

同一个CSS文件中,也可以根据不同的屏幕分辨率,选择应用不同的CSS规则。

  @media screen and (max-device-width: 400px) {

    .column {
      float: none;
      width:auto;
    }

    #sidebar {
      display:none;
    }

  }

上面的代码意思是,如果屏幕宽度小于400像素,则column块取消浮动(float:none)、宽度自动调节(width:auto),sidebar块不显示(display:none)。

八、图片的自适应(fluid image)

除了布局和文本,"自适应网页设计"还必须实现图片的自动缩放

这只要一行CSS代码:

  img { max-width: 100%;}

这行代码对于大多数嵌入网页的视频也有效,所以可以写成:

  img, object { max-width: 100%;}

老版本的IE不支持max-width,所以只好写成:

  img { width: 100%; }

此外,windows平台缩放图片时,可能出现图像失真现象。这时,可以尝试使用IE的专有命令

  img { -ms-interpolation-mode: bicubic; }

或者,Ethan Marcotte的imgSizer.js

  addLoadEvent(function() {

    var imgs = document.getElementById("content").getElementsByTagName("img");

    imgSizer.collate(imgs);

  });

不过,有条件的话,最好还是根据不同大小的屏幕,加载不同分辨率的图片。有很多方法可以做到这一条,服务器端和客户端都可以实现。

(完)

文档信息