Another RayJune

协议森林小记

协议森林》作者:Vamei 出处:http://www.cnblogs.com/vamei

协议森林是一本关于讲解计算机网络非常通俗易懂的书,因为觉得计算机网络的知识非常重要需要深刻理解,特地敲了几乎整本书并且用自己的理解小小重构了一下。以下为看这本书所做的记录。

邮局与邮差:网络协议概观

物理层(physical layer)

识别物理信息(如电压的高低等),并将物理信息转化为计算机能够识别的0/1信号。

把物理层传输过来的0/1信号组成的信号序列分割成帧(frame),帧就像是一个信封,把0/1信号列中不同的数据包裹起来。但帧的收信地址只是本地局域网内的,更远距离的通信需要更高层的网络层来实现。

以太网(Ethernet)和WiFi是现在最常见的连接层协议,分别用于有限和无线。

网络层(network layer)

网络层相当于一个邮政/快递公司,负责不同局域网的信息传送,而路由器(router)则是为此产生的”中间人设备”。这样一来,”邮局公司”网络层就将分离的局域网络连接成了覆盖全球的局域网。

传输层(transport layer)

虽然上面的三层协议让不同的计算机之间可以通信,但是计算机中实际上有多个运行着的程序,也就是所谓的进程。就好像一所房子里住了好几个人。如何让信精确的送到某个人手里呢?
由此便在网络层协议的数据部分增加了传输层(transportlayer),用来更精确的识别收信人。
传输层协议,比如TCP和UDP,使用端口号(port number)来识别收信人。写信的时候我们写上目的地的端口,当信件到达目的地的管理员手中,他会根据传输层协议、识别端口号进而将信送给不同的人。

应用层(application layer)

通过上面的 物理层、连接层、网络层、传输层四层协议,我们已经可以在任意两个进程之间进行通信。
然而,在应用层(application layer)上,我们还可以有更高层的协议。不同类型的进程就像从事不同行业的人。不同的行业会有特定的职业用于规范。比如律师之间的通信会用严格的律师术语;程序员之间的通信会用一些”道儿上”的行;如果是干间谍的,则要通过暗号来加密信息。
同样,某个类型的应用可以使用某个特定的应用层协议,进一步规范用语。

应用层的协议包括用于Web的HTTP协议,用于传输文件的FTP协议,用于Email的IMAP协议等等。这些协议可以帮助计算机应用更稳定也更完善的通信,从而让计算机更好的服务。

小结

计算机通信是基于物理信号的。物理信号可以用于传递原始的0/1序列。单位了实现广域的、多应用支持的通信,我们还需要更高层的协议。
这些高层协议像邮差、邮局、大楼管理员一样,传递符合特地用于规范的信息。通过不同层次的封装,我们不必再关心底层协议,可以专注于高层信息的编辑和发送。
而这些看起来繁杂得像森林的网络协议,是互联网最重要、也最常被忽视的基础设施。


以太网与WiFi

连接层的以太网和WiFi分别是最常见的有线和无线通信方式。

帧(frame)的格式

在连接层,信息以帧(frame)为单位传输,帧像信封一样将数据(payload)包裹起来,并注明收信地址和送信地址。
接下来分析一下以太网的帧:

帧是一段有限的0/1序列。他可以分为头部、数据(Payload)和尾部三部分。

Preamble | SFD | SRC | Type | Payload | Pad | FCS | Extension

帧按照上面的顺序从头到尾依次被发送/接收。其中,头部和尾部就是我们所谓的信封,把真正的数据包裹在中间。尽管头部和尾部并不是我们真正想要传输的数据,但他们是完成通信所必须的。

头部

序言(preamble)

最初的7个byte为序言(preamble),是固定的101010的值用来使网卡对齐频率,就像手表对准时间一样,不准的话就调准频率直到听到的是10。

起始信号SFD

对其频率后等待帧的起始信号(SFD,start fram delimiter)。SFD是也是固定的值,是在提醒网卡要开始传输信息了。

目的地(DST,destination)与发出地(SRC,source)

这里就相当于卸载信封上的地址,也就是每个物理设备的MAC地址。而按照以太网的工作方式,MAC地址只能在同一个以太网中被识别。所以一个帧也只能在局域网内传输。

类型(type)

类型是头部的最后一个区域,用以说明数据部分的类型。

数据

数据一般包含有符合更高层协议的数据,比如IP包。连接层协议本身并不在乎数据是什么,它只负责传输。主意,数据尾部可能填充有一串(PAD区域)。原因是数据需要超过一定的最小长度。

尾部

跟随在数据之后的是校验序列(FCS,Frame Check Sequence)。娇艳序列是为了检验数据的传输是否发生错误。

以太网的集线器和交换器

以太网是现在最普遍的构成局域网的方式。我们经常用一根网线把设备接入局域网。以太网使用集线器或者交换器将帧从发出滴传送到目的地。一台集线器或交换器上有多个端口,每个端口都可以用网线连接一台计算机或者其他设备。集线器和交换器是相互竞争的设备。而设计更加合理的交换器正在取代老一代的集线器,成为以太网的主流枢纽工具。

集线器的缺陷

  • 任意两台电脑的通信在同一个以太网上是公开的。
  • 不允许多路同时通信。

交换机则克服了集线器的缺陷

交换器也连接有多个设备。但集线器懒到只是随手转发,交换器却勤快的记录下各个设备的MAC地址。当帧发送到交换器时,交换器会检查帧的目的地,然后将帧只发送到对应端口。
交换器允许多路通信。总之,交换器的工作方式更加主动,也更加聪明。由于交换器的优越性,交换器基本上取代了集线器。但比较老的以太网还有可能在使用集线器。

WiFi

WiFi的工作方式与集线器连接下的以太网类似。

一个WiFi设备会向所有的WiFi设备发送帧,其他的WiFi设备检查自己是否是目的地。我们已经分析是这种通信方式的缺陷,特别是信息安全性的问题。由于每一台设备都能接受到帧,所以WiFi传输的信号很容易被窃听。但WiFi不能像以太网一样更新换代成交换器的工作方式。毕竟,WiFi采取无线电信号,难以像交换器一样定向发送。WiFi的安全性最重要通过加密的方法来弥补。

WiFi信息加密问题

早期的有限等效加密(WEP)加密方法非常脆弱。后来的WPA(WiFi Protected Access)和WPA2(第二代的WPA)加密一步步的提高了加密的安全性。但黑客依然有办法攻破这些加密保护,例如通过Wi-Fi保护设置(WPS)。WiFi通信的安全性依然是现在计算机安全的一个热点问题。

小结

我们深入了连接层协议的一些细节。连接层是物理与逻辑的借口,它的设计兼顾了物理需求(比如始终复原,CRC验证)和逻辑需求(比如地址、数据)。由于连接层处于网络逻辑的底层,有许多基于连接层的攻击手段,这需要我们队连接层的工作方式有一定的了解,以设计出更好的网络安全策略。

网络层与IP协议

网络层(network layer)是实现全球互联网的关键。网络层有多个协议,其中最重要的是IP协议。IP协议的全称是Internet Protocol,即”互联网协议”。借着IP协议,局域网可以相互连接,最终构成覆盖全球的互联网。IP协议之外,网络层还有其他协议,如ARP、RIP和BGP。这些协议起到了辅助IP协议的作用。

IP包的格式

前言

和大多数网络协议一样,IP协议同样要把传输的数据放入有固定格式的数据包,即符合IP协议规定的0/1序列。我们后面简称IP数据包为IP包。
IP包分为头部(header)和数据(Data)两部分。数据部分是要传送的信息,头部是为了能够实现传输而附加的信息

IP协议

与帧类似,IP包的头部也有多个区域。我们将注意力放在发出地(source address)和目的地(destination address)。它们都是IP地址。

IP协议可以分为IPv4和IPv6两种。IPv6是是IPv4的改进版本,但IPv4依然使用广泛,在这里我们暂时忽略两者的区别。
IPv4的地址为4bytes的长度,即32位置(bit)。我们通常将IPv4的地址分成四个十进制的数,每个数的范围为0-255,比如192.0.0.1就是一个IP地址。填写在IP包头部的是该地址的二进制形式。

IP地址分类

IP地址是全球地址,它可以识别片房子的社区(局域网)和单个房子(一台主机)。这是通过将IP分类来实现的。
每个IP地址的32位可以分为两部分。

  • 第一部分用来区分区域网
  • 第二个部分用来区分该局域网的主机

子网掩码告诉我们这两部分的分界线。

比如255.0.0.0,换算成0/1序列是8个1和24个0。它表示对应IP地址的前8位用于辨识局域网,后24位用于识别该局域网下的主机。
由于A、B、C分类是已经规定好的,所以当一个IP地址属于B类范围时,子网掩码就是255.255.0.0,我们就知道它的前16位和后16位分别表示局域网和主机。C类子网掩码就是255.255.255.0。

网卡和路由器

网卡(NIC)

IP地址就像是每个计算机的”邮编”。但这个说法并不精确。

IP地址真正识别的是网卡(NIC,Network Interface Card)。

网卡是计算机的一个硬件,它在接受到网络信息之后,将信息交给计算机。当计算机需要发送信息的时候,也要通过网卡发送。
而且一台计算机可以有不止一个网卡,比如笔记本就有一个以太网卡和一个WiFi网卡。计算机在接受或者发送信息的时候,要先决定想要通过哪个网卡。

路由器(router)

路由器(router)是构成互联网的基础设备。它实际上就是一台配备有多个网卡的转用电脑。路由器的不同网卡接入到不同的网络,成为跨越网络的桥梁。

IP包接力

名词解释

IP包的传输要通过路由器的接力。每一个主机和路由中都存有一个路由表(routing table)。

路由表根据目的地的IP地址,规定了等待发送的IP包所英爱走的路线。就好像一个路标,如果目的地是北京,那么请左转;如果是上海,请右转。

举个例子

如果我们从主机145.17生成发送到146.21的IP包,那么要采用如下的步骤。

首先铺开信纸,写好信的开头。注明目的地IP地址(199.165.146.21)和发出地IP地址(199.165.145.17)。数据部分用来包含信息,可以是高层的TCP包或UDP,也可以是任意乱写的字。
现在,信写好了要邮寄出去。主机145.17参照自己手中的路由表(routing table),看到里面有两行记录:

145.17 routing table (Genmask为子网掩码,Iface用于说明使用哪个网卡接口)

DestinationGatewayGenmaskIface
199.165.145.00.0.0.0255.255.255.0eth0
0.0.0.0199.165.145.170.0.0.0eth0

除去注释说明部分,路由表中有两行记录。
第一行表示,如果IP目的地是199.165.145.0这个网络的任何主机,那么只需要自己在eth0上的网卡直接传送,在本地社区内直接送达,而不需要前往任何路由器。Gateway 0.0.0.0的意思就是本地送信。
第二行表示所有不符合第一行的IP目的地,都应该送往Gateway 199.165.145.17。这个IP地址是中间路由器接入在eth0的网卡IP地址,即邮局在eth0网络的分支。

我们的包目的地为199.165.146.21,不符合第一行,所以按照第二行,发送到中间的路由器。主机145.17会将IP包放入帧的数据部分,并在帧的头部协商199.165.145.17对应的MAC地址,这样,就可以按照以太网与WiFi中的方法在局域网中传送了。

中间的路由器收到以太协议的帧,然后从帧中的数据部分读取IP包。IP包中有目的地IP地址。中间路由起现在可以对照自己的路由表,找到下一步的发展方向:

DestinationGatewayGenmaskIface
199.165.145.00.0.0.0255.255.255.0eth0
199.165.146.00.0.0.0255.255.255.0eth1
0.0.0.0199.165.146.80.0.0.0eth1

记录的前两行分别对应eth0和eth1两个网络。由于路由器横跨eth0和eth1两个网络,它可以直接通过eth0和eth1上的网卡直接传送IP包。
第三行表示,如果是前面两行之外的IP地址,则需要通过eth1,送往199.165.146.8,也就是右边的路由器。
我们的目的地199.165.146.21符合第二行,所以将IP放入一个新的帧中。路由器在帧的头部协商199.165.146.21的MAC地址,直接发往主机146.21。

总结

如果IP地址指向更遥远的目的地,那么IP包可以进一步接力,到达更远的主机。IP包从主机出发,根据沿途路由器的路由表知道,在路由器间接力。
IP包最终到达某个路由器,这个router与目标主机位于一个局域网中,可以直接建立连接层的通信。最后,IP包被送到目标主机。这样一个过程叫做路由。我们把路由成为”IP包接力”,原因是路由这个词混合了太多的意思。
整个过程中,IP包不断被主机和路由封装入帧再拆开,然后借助连接层,在局域网的各个网卡(NIC)之间传送帧。整个过程中,我们的IP包的内容保持完整,没有发生变化。最终的效果是一个IP包从一个主机传送到另一个主机。利用IP包,我们不需要去操心底层发生了什么。

ARP协议

在上面的过程中,我们实际上假设了,每一台主机和路由都能了解局域网内的IP地址和MAC地址的对应关系,这是实现IP包封装(encapsulation)到帧的基本条件。IP地址与MAC地址的对应是通过ARP协议传播到局域网的每个主机和路由。每一台主机或者路由中都有一个ARP缓存(ARP cache),用以存储局域网内IP地址和MAC地址如何对应。
ARP介于连接层和网络层之间,AR包需要包裹在一个帧中。

ARP协议只用于IPv4。IPv6使用Neighbor Discovery Protocol来替代ARP的功能。

路由表的生成

我们还有一个假设,就是每个主机和路由上都已经有了合理的路由表。这个路由表描述了网络的链接结构,也就是所谓的网络拓扑结构。如果你了解自己的网络连接,可以手写自己主机的路由表。
但是,一个路由器可能有多个出口,所以路由表可能会很长。更重要的是,周围连接的其他路由器可能发生变动,比如新增路由器或者路由器坏掉,我们就需要路由表能即使将交通导向其他的出口。我们需要一种更加智能的探测周围的网络拓扑结构,并自动生成路由表

一种用来生成路由表的协议是RIP(Routing information Protocol)。它通过距离来决定路由表。对于RIP来说,所谓的距离是从出发地到目的地途径的路由器数目(hop number)。

RIP出于技术上的原因(looping hops),认为距离超过15的IP不可达。所以RIP更多用于互联网的一部分(比如整个中国电信的网络)。这样一个互联网的部分往往属于同一个ISP或者有同一个管理机构,所以叫做自治系统(AS,autonomous system)。
自治系统内部的主机和路由器根据通向外部的边界路由器来和其他的自治系统通信。各个边界路由器之间通过BGP(Border Gateway Protocol)来生成自己前往其他AS的路由表,而自治系统内部则参照边界路由器,使用RIP来决定路由表。
BGP的基本工作过程与RIP类似,但在考虑距离的同时,也权衡比如政策、连接性能等其他因素,再决定交通的走向。

小结

我们一开始讲述了IP包根据路由表进行接力的过程。为了顺利实现接力,我们又进一步很入到ARP和RIP/BGP。这三个协议都协助了IP传输。

  • ARP让每台电脑和路由器知道自己局域网内IP地址和MAC地址的对应关系,从而顺利实现IP包到帧的封装。
  • RIP协议可以生成自治系统内部合理的路由表。
  • BGP协议可以生成自治系统外部的路由表。

尽管讲解以IPv4为例,但上面的接力过程适用于IPv6。

IPv4与IPv6

前言

IP地址是IP协议的重要组成部分,它可以识别接入互联网中的任意一台设备。
在IP接力中,我们已经看到,IP包的头部写有出发地和目的地的IP地址。IP包上携带的IP地址和路由器相配合,最终允许IP包从互联网的一台电脑传送到另一台。

IPv4与IPv6

IPv4和IPv6是先后出现的两个IP协议版本。

  • IPv4的地址就是一个32位的0/1序列,比如11000000 00000000 00000000 00000011。为了方便人类记录和阅读,我们通常将32位0/1分成4段8位序列,并用10进制来表示每一段。这样,一段的范围就是0到255。段与段之间以.分割。比如上面的地址可以表示为192.0.0.3。
  • IPv6地址是128位0/1序列,它也按照8位分割,以16进制来记录每一段。使用16进制而不是10进制,这能让写出来的IPv6地址短一些。对于IPv6来说,段与段之间以:分割

在TCP/IP套装中,IP协议作为底层协议,实现了全球互联。
在IP协议的基础上,TCP协议负责信息的传输

但在历史上TCP协议的诞生要早于IP协议。毕竟,在早期局域化的网络中,人们对信息传输的需求超过了对全球互联的需求。

IP地址的分配

IP地址的分配是一个政策性问题。

由ICANN的IANA部分将IP地址分配格5个区域性的互联网注册机构(RIR,Reginal Internet Registry),在有RIR分配给当地的ISP(Internet Service Provider),比如中国电信和中国网通。再有ISP分发给用户和机构等。

但并不是所有的地址都会被分配。一些地址被预留,用于广播、测试、死有网络使用等。这些地址被称为专用地址(special-use address)。

IPV4地址耗尽

由于IPv4协议的地址为32位,所以它可以提供大约40亿个地址。但是一个入网设备就会占用一个地址,所以地址将要耗尽了。

  • 而且IPv6的出现,最重要的就是解决地址耗尽危机
  • IPv6协议的地址最重要的改进就是加长

小结

IPv4地址正在耗尽,而IPv6通过更长的序列提供了更多的IP地址。IPv4向IPv6的迁移正在发生。

阻碍迁移的过程的主要在于IPv4和IPv6格式的不兼容性

老的路由器支持IPv4格式的IP包,但它们无法理解IPv6格式的IP包。所以这一迁移过程必然要伴随着设备的更新。

然而,我们的许多互联网资产都是建立在IPv4网络上的,不可能一夜之间停止IPv4网络的服务而整体迁移到IPv6网络中。这一过程注定充满坎坷。

IP协议详解

前言

上两节”IP接力”和”IP地址”,让我们了解了IP协议的计本工作方式。
我们在这一节中回归IP协议本身,看一看IP协议的具体细节和设计哲学

IPv4和IPv6头部的对比

头部+数据

我们已经在IP接力中介绍过,IP协议把数据包裹进一个个数据包。每个IP包分为头部(header)和数据(payload/data)两部分。

重复下,IP包由头部(header)和数据(payload/data)两部分组成。

和其他网络传输协议一样

  • 头部鞋带的是实现通信必须的附加信息。* 数据部分是通信真正要传送的信息。

相同点

  • 都有存活时间,即课最大传输路由器次数。避免在IP包在互联网中无限接力。
  • 都对控制不同优先级IP包的功能不到位,交通状况的控制往往由更高层的比如TCP协议实现。
  • 都支持对IP包的数据部分使用更高层的协议,并有协议/内层格式来说明IP包封装的是一个怎样的高层协议包,比如TCP协议或者UDP协议。

不同点

  • 版本号不同,用来区分是IPv4还是IPv6。
  • IPv4的总长是整个数据包的长度。而IPv6的数据长度只记录了数据部分的大小,没有记录IPv6的固定长度为40字节的头部。所以IPv6的IP包总长为40字节加上数据长度。
  • IPv4有头部校验码(即对上频率),IPv6没有,它通过依赖高层的协议来完成校验,因此免去了执行校验的时间,减少了网络延迟。
  • IPv6不支持碎片化,即一个路由器将接受到的IP包分拆成多个IP包发送,而接受这些”碎片”的路由器或者主机需要将”碎片”重新组合(reassembly)成一个IP包。不同的局域网所支持的最大传输单元(MTU,Maximum Transportation Unit)不同。如果一个IP包的大小超过了局域网支持的MTU,就需要在进入该局域网时碎片化传输。这就好像方面面饼太大了,必须掰碎了才能放进碗里,碎片化会给路由器和网络带来很大的负担。最好在IP包发出之前探测整个路径上的最小MTU,IP包的大小不超过该最小MTU,就可以避免碎片化。
  • IPv6新增流标签(Flow Label),它被用来提醒路由器来重复使用之前的接力路径。这样IP包可以自动保持出发时的顺序。这对于流媒体之类的应用有帮助。流标签的进一步应用尚在开发中。

Best effort

前言

IP协议在产生时是一个送伞的网络,这个网络由各个大学的局域网相互连接而成的,又一群”蓬头垢面”的geek维护。所以,IP协议认为自己所处的环境是不可靠的:注入路由器坏掉,实验室失火,某个PhD踢掉电缆之类的事情随时会发生。

不靠谱的传送服务

在这样凶险的环境下,IP协议提供的传送只能是”不靠谱”式的,即如果事情出错不要怪我,我只是打赢了尽力(Best effort),可没保证什么。所以IP包传输过程中可能出现各种各样的错误,比如校验码对不上、交通太繁忙、超过最大存活时间I、IP包丢掉等等。

Best effort原则让IP协议保持很简单的形态。更多的质量控制交给高层协议处理,IP协议只负责有效率的传输。

“效率优先也体现在IP包的顺序上。即使出发地和目的地保持不变,IP协议也不保证IP包到达的先后顺序。”我们已经知道,IP接力是根据路由表决定接力路线的。
如果在连续的IP包发送过程中,路由表有时会更想你,比如有一条新建的捷径出现。这种时候,后发出的IP包选择走不一样的接力路线。如果新的路径传输速度更快,那么后发出的IP包有可能先到。这就好像多扯到的公路上,每辆车都在不停的变换扯到,最终所有的扯到都塞满汽车,这样可以让公路利用率达到最大。

IPv6中的流标签可以建议路由器将一些IP包保持一样的接力路径。但这只是”建议”,路由器可能忽略该建议。

小结

每个网络协议的形成都有其历史原因。比如IP协议是为了将各个分散的实验室网络连接起来。由于当时的网络很小,所以IPv4的地址总量为40亿。尽管当时被认为是很大的数字,但数字浪潮很快带来了地址耗尽危机。

IPv6的主要目的是增加IPv4的地址容量,但同事根据IPv4的经验和新时代的技术进步进行改进,比如避免碎片化以及取消验证码。网络协议技术上并不复杂,更多的考量是政策性的。

IP协议是”我尽力(best effort)”式的,IP传输是不可靠的。
但这样的设计成就了IP协议的效率。

ICMP协议

前言

网络层中最重要的协议是IP协议,我们已经通过前面的章节了解了IP协议。IP协议的一个重要补充是ICMP协议。ICMP(Internet Control Message Protocol)介于网络层和传输层的协议

它用于传输网络诊断信息

ICMP协议

ICMP协议传输的信息可以分为两类。

  1. 一类是错误(error)信息,这一类信息可用来诊断网络故障。

我们已经知道,IP协议的工作方式是”我尽力(best effort)”,如果IP包没有被传送到目的地,或者IP包发生错误,IP协议本身不会做进一步的努力。但上游发送IP包的主机和接力的路由器并不知道下游发生了错误和故障,它们可能继续发送IP包。通过ICMP包,下游的路由器和主机可以讲错误信息汇报给上游,从而让上游的路由器和主机进行调整。
由于ICMP只提供特定类型的错误汇报,而不会真正纠错,所以它不能帮助IP协议成为”可靠(reliable)”的协议

  1. 另一类信息是咨询(informational)性质的,比如某台计算机询问路径上的每个路由器都是谁,然后各个路由器同样用ICMP回答。

ICMP基于IP协议

也就是说,一个ICMP包需要封装在IP包中,然后在互联网传送。

再重复一遍,ICMP基于IP协议。一个ICMP包需要封装在IP包中,然后在互联网中传送。

ICMP是IP套装的必须成分,也就是说,任何一个支持IP协议的计算机,都要同时支持ICMP。

ICMP包通常是由某个IP包出发的。这个触发IP包的头部和一部分数据会被包含在ICMP包的数据部分。

ICMP协议是命令行ping和tracerouted的基础,这两个工具常用于网络排错。

常见的ICMP包类型

回音(Echo)

回音(Echo)属于咨询信息。ping命令就是利用了该类型的ICMP包。当使用ping命令的时候,将向目标主机发送Echo-询问类型的ICMP包,而目标主机在接受到ICMP包之后,会回复Echo-回答类型的ICMP包,并将询问ICMP包包含在数据部分。

ping命令是我们进行网络排插的一个重要工具。如果一个IP地址可以通过ping命令收到回复,那么其他的网络协议通信方式也很有可能成功。

源头冷却

源头冷却(source quench)属于错误信息。如果某个主机快速的向目的地传送数据,而目的地主机没有匹配的处理能力,目的地主机可以向出发主机发出该类型的ICMP包,提醒出发主机放慢发送速度

目的地无法到达

目的地无法到达(Destination Unreachable)属于错误信息。如果一个路由器接受到一个没办法进一步接力的IP包,它会向出发主机发送该类型的ICMP包

比如当IP包到达最后一个路由器,路由器发现目的主机无法正常工作,就会向出发主机发送目的地无法到达(Destination Unreachable)类型的ICMP包。目的地无法到达还有可能有其他的原因,比如不存在接力路径,比如不被接受的端口号等等。

超时

超时(Time Exceeded)属于错误信息。IPv4中的存活时间(Time to Live,TTL)和IPv6中的最大中继数(Hop Limit)会随着经过的路由器而递减,当这个区域值减为0时,就认为该IP包超时(Time Exceeded)。如果超时,路由器将发出给出发主机的超时类型的ICMP包,通知主机发生了超时错误。

traceroute就利用了这种类型的ICMP包。traceroute命令用来发现IP接力路径(route)上的各个路由器。它向目的地发送IP包,第一次的时候,将TTL设置为1,引发第一个路由器的超时错误。这样,第一个路由器回复ICMP包,从而让出发主机知道途径的第一个路由器信息。随后TTL被设置为2、3、4、…..,知道到达目的主机。这样,沿途的每个路由器都会向出发主机发送ICMP包来汇报错误。traceroute将ICMP包的信息打印在屏幕上,就是接力路径的信息了。

重新定向

重新定向(redirect)属于错误信息。当一个路由器收到一个IP包,对照其路由表,发现自己不应该收到该IP包,它会向出发主机发送重新定向类型的ICMP,提醒出发主机修改自己的路由表。

IPv6的邻居搜索(Neighbor Discovery)

ARP协议用于发现周边的IP地址和MAC地址的对应。

然而,ARP地址只用于IPv4,IPv6并不使用ARP协议。IPv6并不使用ARP协议。IPv6通过邻居搜索(ND,Neighbor Discovery)来实现ARP的功能。ND的工作方式与ARP类似,但它基于ICMP协议。ICMP包有邻居询问(Neighbor Solicitation)和邻居答复(Neighbor Advertisement)类型。这两个类型分别对应ARP协议的询问和回复信息。

小结

ICMP协议是IP协议的排错帮手,它可以帮助人们及时发现IP通信中出现的故障。基于ICMP的工具ping和traceroute也构成了重要的网络诊断工具。
然而,需要注意的是,尽管ICMP的设计是出于好的意图,但ICMP却经常被黑客借用进行攻击

比如利用伪造的IP包引发大量的ICMP回复,并将这些ICMP包导向受害主机,从而形成拒绝服务攻击(Denial-of-Service attack)。而重新定向类型的ICMP包可以引起某个主机更改自己的路由表,所以也被用作攻击工具。许多站点选择忽视某些类型的ICMP包来提高自身的安全性

UDP协议

前言

物理层、连接层、网络层

我们已经讲解了物理层、连接层和网络层。

连接层的时候,协议种类繁多,比如以太网、WiFi、ARP(ARP协议用于发现周边的IP地址和MAC地址的对应)等等。

到了网络层,选择缩减到只有一个IP协议。

进入到传输层,协议又开始繁多起来,比如TCP、UDP、SCTP等。

树根、树枝、树叶

这就好像一棵大树。连接层是根。根部的分叉很多,然后统一到一个树干,即网络层。
再到作为树冠传输层,又开始分出树枝。最终每个树枝上长出更多的树叶,也就是未来的应用层协议

在网络层已经看到,通过树干的统一,我们实现了一个覆盖全球的互联网。然而,我们可能出于不同的目的利用这张”网”,随之实用的方式也有所区分。不同的传输层协议,以及更多的应用层协议,正是我们使用”网”的不同方式的体现。

TCP&UDP

传输层最重要的协议为TCP协议和UDP协议。这两者使用”网”的方式走了两个极端。两个协议的对比非常有趣。

  • TCP协议复杂,但传输可靠。
  • UDP协议简单,但传输不可靠。

其他的各个传输层协议某种程度上都是这两个协议的折中。

UDP协议简介

不可靠的UDP

UDP(User Datagram Protocol)传输与IP传输非常类似。你可以将UDP协议看作IP协议呈现在传输层的一个接口。UDP协议同样以数据包(datagram)的方式传输、它的传输方式也是”我尽力(best effort)”式的,所以UDP协议也像IP协议一样,是不可靠的。

UDP的来由

那么,为什么我们不直接使用IP协议而要额外增加一个UDP协议呢?

一个重要的原因是IP协议中并没有端口(port)的概念。

IP协议进行的是IP地址到IP地址的传输,这意味着两台计算机之间的对话。但每台计算机中需要有多个通信通道。多个通信通道分配给不同的进程使用。

为了满足这一需求,传输层的网络协议实现了端口。一个端口就代表了一个通信通道。正如我们在邮局和邮差中提到的收信人的概念一样。UDP协议实现了端口,从而让数据包可以在送到IP地址的基础上,进一步送到某个端口。

UDP协议的产生晚于更加复杂的TCP协议

UDP晚于TCP

早期的网络开发者开发出IP协议和TCP协议分别位于网络层和传输层。所有的通信都要先经过TCP封装,再经过IP封装,也就是应用层->TCP->IP的过程。开发者将TCP/IP视为相互合作的套装。

但很快,网络开发者发现,IP协议的功能和TCP协议的功能是相互独立的。对于一些简单的通信,我们只需要”我尽力(best effort)”的IP传输就可以了,而不需要TCP协议复杂的建立连接的方式。特别是在早期网络环境中,如果过多建立TCP连接,会造成很大的网络负担。

而UDP协议可以相对快速的处理这些简单通信。UDP协议随之被开发出来,作为IP协议在传输层的”傀儡”。这样,网络通信可以通过应用层->UDP->IP的封装方式,用简单的UDP协议绕过TCP协议。

UDP的结构

UDP的数据包同样分为头部(header)和数据(payload)两部分。

UDP是传输层协议,高于网络层

因此,UDP的数据包最终会经过IP协议的封装,然后通过IP协议传输到目的电脑。随后IP包中包含的UDP包在目的电脑拆封,并将数据部分送到相应端口。

UDP拥有起始端口(source port)和目的地端口(destination port)分别为UDP包的出发端口和目的地端口。长度(length)为整个UDP包的长度。

校验码(checksum)的算法与IP协议的头部校验算法相类似。然而,UDP的校验码所校验的序列包括了整个UDP数据包,以及封装的IP头部的一些信息,主要是出发地IP和目的地IP。这样,校验码就能确保IP地址和端口两者的正确性了。在IPv4中,UDP的校验码可以为0,意味着不使用校验。IPv6要求必须进行校验码的校验。

端口与socket

端口的诞生

端口是伴随着传输层诞生的概念。它可以将网络层的IP通信分送到各个通信通道。
UDP协议和TCP协议尽管在工作方式上有很大的不同,但它们都建立了从一个端口到另一个端口的通信

套接字(socket)

随着我们进入传输层,我们也可以调用操作系统中的API,来构建套接字(socket)。

套接字(socket)是操作系统提供的一个编程接口,它用来代表某个网络通信。

套接字是操作系统提供的一个编程接口,它用来代表某个网络通信。

应用程序通过调用系统内核中处理网络协议的模块,而这些内核模块会负责具体的网络协议的实施。这样,我们可以让内核来接收网络协议的细节,而我们只需要提供索要传输的内容就可以了,内核会帮我们控制格式,并进一步向底层封装。

因此,在实际应用中,我们并不需要知道具体怎么构成一个UDP包,而只需要提供相关信息,比如IP地址,比如端口号,比如所要传输的信息。操作系统内核会在传输之前根据我们提供的相关信息构成一个合格的UDP包。

套接字是一个比较大的课题,在这本书中我们不会过多深入。下面提供一个简单的Python变成代码,来说明UDP套接字编程的计本方式。下面一段代码保存为udp_send.py发出一条消息。

1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*-
import socket
UDP_IP = "127.0.0.1" #目标IP
UDP_PORT = 9999 #目标端口
MESSAGE = "RayJune的协议森林" #要发出的信息
sock = socket.socket(socket.AF_INET, #说明是互联网socket.SOCK_DGRAM) #说明是UDP套接字
sock.sendto(MESSAGE,(UDP_IP,UDP_PORT))#开始发出信息

Python标准库中的socket模块负责套接字编程

根据程序,消息将发往127.0.0.1的IP地址。这个地址是指本机。如果你有两台电脑,也可以尝试在两台电脑间发送消息。只需要把地址改为目标电脑的IP就可以了。在接受电脑上运行下面一段程序。程序udp_receive.py不断的查看端口的缓存空间,把那里存储的消息打印出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding: utf-8 -*-
import socket
UDP_IP = "127.0.0.1" #监视的网卡IP
UDP_PORT = 9999 #监视的端口号
sock = socket.socket(socket.AF_INET, #说明是互联网
socket.SOCK_DGRAM) #说明是UDP协议
sock.bing((UDP_IP,UDP_PORT)) #绑定套接字
while True:
data,addr = sock.recvfrom(1024) #缓存大小为1024字节
print("接受到的信息:",data) #打印出接受到的信息。

在保持Udp_receive.py运行的同事,运行udp_send。接收端将打印出接受到的信息:

1
接受到的信息: RayJune的协议森林

小结

端口(port)是传输层带来的最重要的概念

我们进一步了解UDP协议。如果易经掌握了IP协议,那么UDP协议就没有任何困难可言,UDP协议只是IP协议暴露在传输层上的接口

TCP协议与流通信

前言

TCP协议(Transportation Control Protocol)协议与IP协议是一同产生的。

事实上,两者最初是一个协议,后来才被分拆成网络层的IP传输层的TCP

我们已经在UDP协议中介绍过,UDP协议是IP协议在传输层的”傀儡”,用来实现数据包形式的通信。而TCP协议则实现了”流”形式的通信

TCP的内容非常丰富。这一片主要介绍TCP协议的下面几个方面:

  1. “流”通信的意义与实现方式
  2. 如何实现可靠传输
  3. 使用滑窗提高效率

“流”通信

TCP的特别之处

TCP协议和UDP一样,属于传输层协议,所以实现了端口到端口的通信。

TCP协议特别的是,它虚拟了文本流(byte stream)的通信

什么是文本流

计算机数据的本质是有序的0/1序列。如果以byte为单位,数据序列就叫做文本流。计算机的功能就是储存和处理文本流。CPU+memory+存储设备实现了文本流在同一台计算机内部的加工处理。通过一些IO,比如屏幕和键盘,文本流实现了人机交互

数据处理方式对接

而进一步,如果网络通信能实现计算机之间的文本流交互,那么我们就和整个计算机系统的数据处理方式实现了对接

IP协议和UDP协议采用的是数据包的方式传送,后发出的数据包可能早到,我们并不能保证数据到达的次序,并非由顺序的”流”

TCP实现了顺序的”流”

TCP协议确保了数据到达的顺序与文本流数据相符

当计算机从TCP协议的接口读取数据时,这些数据已经是排列好顺序的”流
“了。

比如我们有一个大文件要从本地主机发送到远程主机,如果是按照”流”接收到的话,我们可以一边接收,一边将文本流存入文件系统。这样,等到”流”接收完了,硬盘写入操作也已经完成。
如果采用UDP的传输方式,我们需要等到所有的数据到达后,进行排序,才能组装成大的文件。这种情况下,我们不得不使用大量的计算机资源来存储已经到大的数据,知道所有数据都达到了,才能开始处理。

“流”的要点是次序(order)

“流”的要点是次序(order)。想要有效率的实现这一点可不简单。

TCP协议是基于IP协议的,所以最终数据传送还是以IP数据包为单位进行的。如果一个文本流很长的话,我们不可能将整个文本流放入到一个IP数据包中,那样有可能会超过局域网所支持的最大传输单元(MTU)。

所以,TCP协议封装到IP包的不是整个文本流,而是TCP协议所规定的片段(segment)

TCP”片段”

与之前的一个IP或者UDP数据包类似,一个TCP片段同样分为头部(header)和数据(payload)两部分。所谓的”片段”,是与完整的数据坐对比:嘿 这个片段算不上完整的文本流。整个文本库按照次序被分成小段,而每一段被放入TCP片段的数据部分。一个TCP片段封装成的IP包不超过整个IP接力路径上的最小MTU,从而避免令人通过的碎片化(fragmentation)。

给文本流分段是在发送主机完成的,而碎片化是在网络中的路由器完成的。路由器要处理许多路的通信,所以相当的繁忙。文本流提前在发送主机分好段,可以避免在路由器上执行碎片化,课大大减小网络负担

TCP片段的头部都会有该片段的序号(sequence number)。这样,接受的计算机就可以知道接受到的片段在原文本流中的顺序了,也可以知道自己下一步需要接受哪个片段以形成流。比如已经接受到了片段1,片段2,片段3,那么接受主机就开始期待片段4。如果接受到不符合顺序的数据包,比如片段8,接收方的TCP模块可以拒绝接受,从而保证呈现给接受主机的信息是符合次序的”流”。

可靠性

不可靠的IP协议

片段编号这个初步的想法并不能解决我们所有的问题。IP协议是不可靠的,所以IP数据包可能在传输过程中发生错误或者丢失。而IP传输是”我尽力(best effort)”式的,如果发生异常情况,我们的IP是举报就会被轻易的丢弃掉。另一方面,如果乱序(out of order)片段到达,根据我们上面说的,接收主机不会接收。这样,错误片段、丢失片段和被拒片段的联手破坏之下,接受主机可能只收到一个充满”漏洞”的文本流。

TCP的补救方法

TCP的补救方法是:在每收到一个正确的、符合次序的片段之后,就向发送方,即连接的另一端,发送一个特殊的TCP片段,用来知会(ACK,acknowledge)发送方:我已经收到那个片段了。

这个特殊的TCP片段叫做ACK回复

如果一个片段序号为L,对应ACK回复有回复号L+1,也就是接受方期待接受的下一个发送片段的序号。如果发送方在一定是见等待之后,还是没有收到ACK回复,那么它推断之前发送的片段一定发生了异常。发送放会重复发送(restransmit)那个异常的片段,等待ACK回复,如果还没有收到,那么再重复发送原片段…知道收到该片段对应的ACK回复。回复号为L+1的ACK。
当发送方收到ACK回复时,它看到里面的回复号为L+1,也就是发送方下一个应该发送的TCP片段序号。发送方推断出之前的片段已经被正确的接收,随后发出L+1号片段。ACK回复也有可能丢失。对于发送方来说,这和接收方拒绝发送ACK回复是一样的。发送方会重复发送,而接受方接受到已知会过的片段,推断出ACK回复丢失,会重新发送ACK回复。

励志的TCP与不负责的UDP

通过ACK回复和重新发送机制,TCP协议将片段传输变得可靠。尽管地盘是不可靠的IP协议,但TCP协议以一种”不放弃”的精神,不断尝试,最终成功。
说起来,TCP协议也蛮励志的。

作为对比,UDP协议走了两个极端。
UDP协议轻便但不可靠。在处理异常的时候,TCP极端负责,而UDP一副无所谓的样子。同样面对”挫折”,UDP的态度是,管他呢…

滑窗

停止等待的利与弊

上面的工作方式中,发送放保持发送->等待ACK->发送->等待ACK…的单线工作方式,这样的工作方式叫做停止等待(stop-and-wait)

“停止等待”虽然实现了TCP通信的可靠性,但同时牺牲了网络通信的效率。

我们希望有一种方式,可以同时发出多个片段。可是这样的话,那么由于IP包传送是无次序的,有可能生成乱序片段。在停止等待这种工作方式下,乱序片段会完全被拒绝,这样效率也不高。
我们可以在缓存中先存放提前到达的乱序片段,等它之前的片段补充完毕,再将它缀在后面。
然而,如果一个乱序片段太过提前,那这个流传输就太乱了。该片段将长期占用缓存。

由此有了一种更为巧妙的办法:

  • 利用缓存保留一些”不那么乱”的片段,期望能在短时间内补充上之前的片段。所以计算机暂不处理,但发送相应的ACK。
  • 对于乱得比较厉害的片段,无情的拒绝它们:不处理,也不发送对应的ACK。

滑窗(sliding window)

滑窗(sliding window)被同时应用与接收方和发送方。以解决以上问题。

发送方和接收方各有一个滑窗。当片段位于滑窗中时,表示TCP正在处理该片段。滑窗中可以有多个片段,也就是可以同时处理多个片段
滑窗越大,能同时处理的片段书目越多。当然,滑窗越大,计算机也必须分配出更多的缓存供滑窗使用。天下毕竟没有免费的午餐。

可以看到,随着滑窗的滑动,越来越多的片段被正确的传送。利用滑窗,我们一定成都上实现了对乱序数据的缓存。但是,过于乱序的数据依然会被拒绝。我们之前说的”停止等待”的工作方式,相当于发送放和接受方的滑窗都只能容纳一个片段。

在后面的文章中我们会看到,TCP协议能实时调整滑窗大小,以实现最优效率

小结

TCP协议和UDP协议走了两个极端。TCP协议复杂但可靠,UDP协议轻便但不可靠。

在处理异常的时候,TCP极端负责,而UDP一副无所谓的样子。

  • 在TCP中,分段和编号实现了次序。ACK和重新发送实现和可靠性。
  • 滑窗则让上面的机智更加有效率的运行。

不放弃,这就是TCP协议的态度。

建立TCP连接

前言

上一章中,我们见到了TCP协议稳定而靠谱的通信方式。
在TCP通信之前,它必须要先经过一个建立连接的阶段。这一节正是要介绍TCP连接。

TCP连接

网络层在逻辑上提供端口概念

正如我们一再强调的,网络层在逻辑上提供了端口的概念。一个IP地址可以有多个端口。一个具体的端口需要IP地址和端口号共同确定,常常记为IP:port格式。
UDP同样有端口,这没有什么稀奇。

TCP通信前要建立连接

但TCP通信之前,会有两个端口建立连接。这就好像两个人打电话,总要先拨通对方的号码。只有在电话拨通之后,两人才能开始通话。相比之下,UDP就简单粗暴得多。它直接把信息甩给对方:爱看不看。

参与连接的经常是互联网上的两台电脑。两台电脑中安装的TCP模块负责建立连接。每个连接有4个参数,也就是两个IP地址和两个端口。这四个桉树可以表明”谁在和谁通话”。两台电脑都会记录有这四个参数,以确定是哪一个连接。如果这四个参数完全相同,则为同一连接。如果这四个参数有一个不同,即为不同的连接。

这意味着,一台电脑的同一个端口上,可以有多个连接。连接生成之后,将交给运行的进程使用。

TCP连接是双向(duplex)的

TCP连接是双向(duplex)的。在上一节中,我们展示了单向的TCP传输过程。双向连接实际上就是建立两个方向的TCP传输,所以很容易理解。这时,连接的每一方都需要两个滑窗,以分别处理发送的文本流和接受的文本流。由于连接的双向性,我们也要为两个方向的文本流编号。这两个文本流的编号相互独立。为文本流分段和编号由发送方来处理,回复ACK则由接受的一方进行。

TCP片段的头部

在深入TCP连接之前,我们需要对TCP片段的头部格式有一些了解。我们知道,TCP片段分为头部和数据。数据部分为TCP真正传输的文本流数据。

先关注下面几点:

  1. 一个TCP头部需要包含出发端口(source port)和目的地端口(destination port)。这些与IP头部的两个IP地址共同确定了连接。
  2. 每个TCP片段都有序号(sequence number)。这些序号最终将数据部分的文本片段整理成文本流。
  3. ACK是一位(bit)。只有ACK位设定的时候,回复号(Acknowledgement number)才有效。ACK回复号说明了接收方期待接受的下一个片段,所以ACK回复号为最后接受到的片段序号+1.

很多时候,ACK回复”附着”在发送的数据片段中。TCP协议是双向的

比如A和B两个电脑。ACK回复是接收方回复给发送方,比如A发送给B,以及B回复A。但同时,B也可以是发送方,B有可能有数据发送给A,所以B就把ACK回复附着在它要发送给A的数据片段的头部。这样可以减少ACK所占用的交通流量。一个片段可以只包含ACK回复。一个纯粹的ACK回复片段不传送文本流,所以不消耗序列号
如果有下一个正常的数据片段,它的序号将与纯粹ACK回复片段的序号相同。ACK回复还可以”附着”在SYN片段和FIN片段

ACK后面还有SYN和FIN,它们也各占据一位(bit)。接下来将在后面说明这两位。

建立连接

交换初始序号(ISN)

TCP连接从无到有一系列复杂的手续。建立连接的最重要目的是让连接的双方交换初始序号(ISN,Initial Sequence Number)。根据TCP协议的规定,文本流的第一个片段的序号不能是确定的数字,比如说1。连接的双方各自随机生成自己的ISN,然后再利用一定方式让对方了解。这样的规定是出于TCP连接安全考虑。如果以一个确定的数字作为初始的TCP序号,那么其他人很容易猜出接下来的序列号,并按照正确的序号发送”伪装”的TCP片段,以插入到文本流中。

SYN片段实现ISN交换

ISN交换是通过SYN片段实现的。SYN片段由头部的SYN位表明,它的序号为发送方的ISN。

该片段由连接的一方首先发给另一方,我们将发送SYN的一方称为客户(client),而接受SYN的一方称为服务器(server)。我们使用ISN(c)表示客户一方的ISN,使用ISN(s)表示服务器一方的ISN。

随后,接受到SYN的服务器需要回复ACK,并发送出包含有server的ISN的SYN片段。

TCP三次握手(three-way handshaking)

整个过程的本质是双方互相发送含有自己ISN的SYN片段。根据TCP传输的规则,接受到ISN的一方需要回复ACK,所以共计四篇信息在建立连接过程中传输。

之所以是三次握手而不是四次,是因为服务器将发送SYN和回复ACK合并到一个TCP片段中。

我们以客户为例:客户知道自己的ISN,即ISN(c)。建立连接之后,它也知道了对方的ISN(s)。伺候,如果需要发送文本流片段,则编号为ISN(c) + 1,ISN(c) + 2…。如果接受文本流片段,则期待接收ISN(s) + 1, ISN(s) + 2…。

连接建立之后,连接的双方就可以按照TCP传输的方式相互发送文本流了。

挂断连接

四次握手挂断连接

一个连接建立之后,连接两端的进程可以利用该连接进行通信。当连接的一方觉得”我讲完了”,它可以像挂电话一样,挂断连接。连接通过四次握手(four-way handshaking)的方式中止,连接中止使用的是特殊片段FIN。FIN为1的片段。

挂断连接的过程中,双方也交换了四篇信息,即两个FIn和两个ACK。在挂断过程中,TCP并没有合并FIN和ACK片段
原因是TCP连接允许单向关闭(half-close)

别忘了TCP的单向连接

也就是说,TCP连接关闭了一个方向的传输,成为一个单项连接(half-duplex)。第二个箭头和第三个箭头合并在一起,那么,随着一方关闭,另一方也要被迫关闭。

第二和第三次握手之间,服务器可以继续单向的发送片段给客户,但客户不能发送数据片段给服务器。上面的解释中,连接中止从客户先发起,TCP连接也可以先从服务器先发起。原理相同。

不会立即关闭的客户

在客户发送出最后的ACK回复,但该ACK可能丢失。服务器如果没有收到ACK,将不断重复发送FIN片段。所以客户不能立刻关闭,它必须确认服务器接受到了该ACK。客户会在发送出ACK之后进入到TIME_WAIT状态。它会设置一个计时器,等待2MSL的时间。如果在该时间内咋爱收到FIN,那么客户会重发ACK并再次等待2MSL。所谓2MMSL就是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户都没有收到FIN,那么可会推断ACK已经被成功接受,则结束TCP连接。

总结

TCP是连接导向的协议,与之对应的是UDP这样非连接导向的协议。

连接能带来更好的传输控制,但也需要更多的额外工作,比如连接的建立和挂断。

我们还初步了解了TCP的头部格式。应该注意到,许多时候我们将ACK片段”附着”在其他片段上。相对于纯粹的ACK片段,我们这样可以节约ACK所需的流量。

TCP滑窗管理–深入细节

前言

我们已经知道,TCP通信中使用了滑窗。通过滑窗与ACK的配合,我们可以确保TCP传输的可靠性,并在一定程度上提高效率。

但之前的讲解省略了很多细节。细节是魔鬼

等到真的深入到小结,我们才能看清楚TCP协议背后极为精心的设计。

累计ACK

ACK附着在数据片段

在TCP连接中,我们通过将ACK回复”附着”在其他数据片段的方式,减少了ACK回复所消耗的流量。但这并不是全部。

ACK累计回复

TCP协议并不是对每个片段都发送ACK回复。TCP协议实际采用的是累计ACK回复(accumulative acknowledgement)

接收方往往利用一个ACK回复来知会联系多个片段的成功接收。通过累计ACK,所需要的ACK回复通常可以降到50%。

比如滑块可以容纳三个片段,2和3都已经到了但是片段1还没有收到。这样就在滑窗中制造了一个”空穴”(hole)。当滑窗最终接受到片段1时,滑窗送出一个回复号为4的ACK回复。发送方收到该回复,会意识到,片段10之前的片段已经按照次序被成功接收。整个过程中节约了片段7和片段8所需的两个ACK回复。

接收方ACK延迟

此外,接收方在接受到片段,并应该回复ACK的时候,会故意延迟一些时间。如果在延迟的时间里,有后续的片段到达,就可以利用累计ACK来一起回复了。

滑窗结构

在之前的介绍中,我们一片段为单位,来衡量滑窗的大小的。真实的滑窗是以字节为单位表示大小,但这并不会对我们之前的讨论造成太大的影响。

接收方的滑窗相对于发送方的滑窗多了一个”Received;ACKed;Not Sent to Proc”的部分。接收方接受到的文本流必须等待进程来读取

如果进程正忙于做别的事情,那么这些文本流即使已经正确接受,还是需要暂时占用接受缓存。当出现上述占用时,滑窗的可用部分就会缩水。这意味着接收方的处理能力下降。如果这个时候发送方依然按照之前的速率发送数据给接收方,接受方将无力接受这些数据

流量控制

TCP协议会根据情况自动改变滑窗大小,以实现流量控制

流量控制(flow control)是指接收方将广播窗口的大小通知给发送方,从而指导发送放修改提议窗口的大小。接收方将该信息放在TCP头部的窗口大小(window size)区域。

发送放在收到窗口大小的信息时,也会调整自己滑窗的大小,让提议窗口和广播窗口相符。这样,发送窗口变小,文本路发送速率降低,从而减少了接收方的负担。

零窗口

广播窗口大小有可能变为0,这意味着接收方的接受能力降为0。发送放收到0大小的广播窗口的通知时,将停止发送。

当接收方再次产生可用的广播窗口时,接收方会通过纯粹的ACK回复来通知发送方,让发送方恢复发送。

然而,ACK回复的传送并不是可靠的。如果该ACK回复丢失,那么TCP传输将陷入死锁(deadlock)状态。

为此,发送方会在零售窗口后,不断探测接收方的窗口。窗口探测(window probe)时,发送方会向接受方发送包含1字节文本流的TCP片段,并等待ACK回复。该ACK回复包含有窗口大小信息。由于有1字节的真实数据存在,所以该传输是可靠的,而不用担心ACK回复丢失的问题。如果探测结果显示窗口依然为0,发送方会等待更长的时间,然后再次进行窗口探测,直到TCP传输恢复。

白痴窗口综合症

滑窗机智有可能犯病,比如白痴窗口综合症(Silly Window Syndrome)。假设这样一种情形:接收方发布(advertise)一个小的窗口。发送方根据发布的窗口大小,发送一个小的片段。接受方的小窗口被填满,经过处理,接收方再宣布一个小的窗口…

这就是”白痴窗口综合症”:TCP通信的片段中包含的数据量很小。在这样的情况下,TCP通信的片段所含的信息都很小,网络流量主要是TCP片段的头部,这样头重脚轻的TCP片段,会造成网络流量的浪费。

如果发送方不断发送晓得片段,也会造成白痴窗口。为了解决这个问题,需要从良方面入手。TCP中有相关的规定,要求:

  1. 接收方宣告的窗口必须达到一定的尺寸,否则等待。
  2. 除了一些特殊情况,发送方发送的片段必须达到一定的尺寸,否则等待。特殊情况主要是指需要最小化延迟的TCP应用比如命令行互动

小结

TCP协议相当复杂,并充斥者各种细节。这些细节正是TCP协议成功的原因,值得我们深入了解。

  • 累计ACK减少了TCP传输过程中所需要的ACK流量。
  • 通过流量管理,TCP连接两端的工作能力可以匹配,从而减少不必要的传输浪费。

累计ACK流量控制都是TCP协议的重要特征。

TCP重新发送

前言

TCP协议是个靠谱的协议。它的可靠性靠的是TCP片段的重新发送(retransmission)。TCP会不断重复发送TCP片段,直到片段被正确接收。这一节,我们来看一下重新发送过程中的细节

TCP片段丢失

接收方(receiver)可以通过校验TCP片段头部中校验(checksum)区域来检验TCP片段是否出错。

我们已经接触过了IP协议详解的校验算法。TCP片段的校验算法与之相似。但IP协议只校验头部,TCP片段头部的校验会校验包括IP头部、TCP头部和TCP数据在内的整个序列,确保IP地址、端口号和其他相关信息正确。如果TCP片段出错,接收方可以简单的丢弃该TCP片段,也就相当于TCP片段丢失。

TCP片段包裹在一个IP包中传输。IP包可能在网络中丢失。导致IP包丢失的原因可能有很多:

  • 如果IP包经过太多的路由器接力,达到最大中继数的限制。
  • 如果路由器太过拥挤,会导致一些IP包被丢弃。
  • 再比如路由表(routing table)没有及时更新,会造成IP包无法送达目的地。

如果TCP片段丢失,那么TCP通信就要重新发送。下面我们要介绍两种重新发送TCP片段的机制:

  • 超时重新发送
  • 快速重新发送

超时重新发送

我们之前已经简单介绍过重新发送的机制:当发送方送出一个TCP片段后,将开始计时,等待该TCP片段的ACK回复。如果接收方正确接受到符合次序的片段,接收方会利用ACK片段回复发送方。发送方得到ACK回复后,继续移动窗口,发送接下来的TCP片段。如果直到计时完成,发送放还是没有收到ACK回复,那么发送方推断之前发送的TCP片段丢失,因此重新发送之前的TCP片段。

这个计时等待的时间叫做重新发送超时时间(RTO,retransmission timeout)。超时时间好像一个计时的沙漏。沙漏中的沙子流完了,就要重新发送。

发送方应该在等待多长时间之后重新发送呢?这是重新发送的核心问题。上述过程实际上有往返两个方向:

  1. 发送片段从发送方到接收方的传输。
  2. ACK片段从接收方到发送方的传输。

整个过程实际耗费的时间称作往返时间(RTT,round trip time)。如果往返时间是固定的,比如1s,那么我们可以让超时时间等于往返时间。

但实际上,往返时间的上下浮动很大。比如某个时刻,网络中有许多交通,那么往返时间就增加。在往返时间浮动的情况下,如果我们设置了过小的超时时间,那么TCP会等待很短的时间之后重新发送,而实际上之前发送的片段并没有丢失,只是传输速度比较慢而已,这样,网络中就被重复注入TCP片段,从而浪费网络资源。

另一方面,如果超时时间过长,那么当TCP片段已经实际丢失的情况下,发送方不能及时重新发送,会造成网络资源的闲置。所以,超时时间必须符合当前网络的使用状况。网络状况越好,超时时间应该越短。网络状况越差,超时时间应该越长

TCP协议通过统计往返时间,来决定合理的超时时间。发送方可以测量每一次TCP传输的往返时间。这样实际测量得到的往返时间,叫做采样往返时间(SRTT,sampling round trip time)。建立连接之后,每次的采样往返时间都是采样样本。收集样本后,计算它们的平均值(mean)和标准差(standard deviation)。往返时间等于采样往返时间的平均值加上四倍的采样往返时间的标准差。

平均值反映了平均意义上的往返时间。平均往返时间越大,超时时间越大。另一方面,标准差越大也会影响往返时间。标准差代表了往返时间样本的离散程度。如果往返时间上下距离浮动,标准差比较大。往返时间浮动大。说明当前网络状况相对不稳定。因此要设置更长的超时时间,以应对不稳定的网络状况。计算超时时间的算法有多个变种,根据平台不同有所变化,但基本原理与上面相同。

快速重新发送

打断计时器的等待

我们刚才介绍了超时重新发送的机制:发送方送出一个TCP片段,然后开始等待并计时,如果超时时间之后还没有收到ACK回复,发送方则重新发送。

TCP协议有可能在计时完成之前启动重新发送,也就是利用快速重新发送(fast-retransmission)。快速发送机制如果被启动,将打断计时器的等待,直接重新发送TCP片段

乱序片段出现

由于IP包的传输是无序的,所以接收方有可能先收到后发出的片段,也就是乱序(out-of-order)片段。

乱序片段的序号并不等于最近发出的ACK恢复好。已接受的文本流和乱序片段之间将出现空洞(hole),也就是等待接收的空位。

比如已经接收了正常片段5、6、7,此时又接受乱序片段9,此时8依然空缺,片段8的位置就是一个空洞。

机制原理

TCP协议固定,当接收方收到乱序频段额时候,需要重复发送ACK。比如接收到乱序片段9的时候,接收方需要回复ACK。回复号为8,也就是7+1。伺候接收方如果继续收到乱序片段,即需要不是8的片段,将再次发送序号为8的ACK。当发送方收到3个ACK为8的回复时,发送方推断片段8丢失。即使此时片段8的计时器还没有超时,发送方会打断计时,直接重新发送片段8,这就是快速重新发送机制(fast-retransmssion)。

提示空洞

快速重新发送机制利用重复的ACK来提示空洞的存在。当重复次数达到阀值时,认为空洞对应的片段在网络中丢失。快速重新发送机智提高了检测丢失片段的频率,往往可以在超时之前探测到丢失片段,并重复发送丢失的片段。

小结

TCP协议利用重新发送保证了通信可靠性。重新发送的计本形式是超时重新发送。

  • 这种机制根据统计的往返时间来设置超时标准。如果超时,则重新发送TCP片段。
  • 另一方面,快速重新发送机智通过乱序片段的ACK来更早的推断出片段的丢失。

TCP堵塞机制

前言

在TCP协议中,

  • 我们使用连接记录TCP两端的状态
  • 使用编号和分段实现了TCP传输的有序
  • 使用广播窗口来实现了发送放和接收方处理能力的匹配
  • 还使用重复发送机制来实现TCP传输的可靠性

最初的TCP协议就是由上述的几个方面构成。1980年代,TCP协议又引入了流量控制机制

令人头痛的堵塞

越来越繁忙的路由器

从1980年代开始,网络变得繁忙。许多网络中出现了大量的堵塞(congestion)。堵塞类似于现实中的堵车。网络被称为”信息高速公路”,许多IP包在网络中行驶,并经过一个个像十字路口一样的路由器,知道到达目的地。一个路由器如果过度繁忙,会丢弃一些IP包
UDP协议不保证传输的可靠性,所以丢失就丢失了。而TCP协议需要保持传输的可靠性。当包含有TCP片段的IP包丢失时,TCP协议会重复发送TCP字段。于是,更多的”汽车”进入到公路中,原本繁忙的路由器变得更加繁忙,更多的IP包丢失。这样就构成了一个恶性循环。

堵塞崩溃

这样的情况被称为堵塞崩溃(congestion collapse)。每个发送方为了保证自己的的发送质量而乱发车,是造成堵塞崩溃的主要原因。当时的网络中高达%90的传输资源可能被堵塞崩溃所浪费。

弥补缺陷

为了弥补这一缺陷,从1980年代开始,TCP协议中开始加入堵塞控制(congestion control)的功能,以避免堵塞崩溃的出现。多个算法被提出并实施,大大改善了网络的交通状况。

流量控制类似于生活中的交通。当我们遇到堵车,可能就会希望建立立交桥和高架,或者希望有一位交警来疏导交通。

约束自己

而TCP协议的堵塞控制是通过约束自己来实现的。

当TCP的发送方探测到网络交通拥堵时,会控制自己发送片段的速率,以缓解网络的交通状况,避免堵塞崩溃。简言之,TCP协议规定了发送放需要约束的”公德”。

我们先来说明堵塞是如何探测的。在TCP重新发送中,我们已经总结了两种推测TCP片段丢失的方法:

  • ACK超时
  • 重复ACK

一旦发送放认为TCP片段丢失,则认为网络中出现堵塞。另一方面,TCP发送放是如何控制发送速率的呢?

滑动窗大小的控制

TCP协议通过控制滑动窗大小来控制发送速率

在TCP滑窗管理中,我们已经见到了一个窗口限制,就是广播窗口大小,以实现TCP流量控制。

TCP还会维护一个阻塞窗口大小,以根据网络状况来调整滑窗大小。真实滑窗大小取这两个滑窗限制的最小值,从而同时满足流量控制和堵塞控制的限制

阻塞窗口

阻塞窗口总是处于两种状态的一个。这两种状态是

  • 慢启动(slow start)
  • 堵塞避免(congestion avoidance)。

阻塞窗口从慢启动状态开始。慢启动的特点是初始速率低,但速率不断倍增。每次进入到慢启动状态时,阻塞窗口大小都需要重置为初始值1。发送方每接到一个正确的ACK,就会将阻塞窗口大小增加1,从而实现速率的倍增。

需要注意的是,由于累计ACK,速率增长可能会小于倍增。

当阻塞窗口大小达到阀值时,阻塞窗口进入到阻塞避免状态。发送速率会继续增长。发送方在每个窗户所有片段成功传输后,将窗口尺寸增加1,等效于每个往返时间增加1。所以在阻塞避免模式下,阻塞窗口大小线性增长,增长速率慢。如果在阻塞避免有片段丢失,重新回到慢启动状态,并将阀值更新为阻塞窗口大小的一半。

我们看到,触及阀值是从慢启动到C阻塞避免的切换点。而片段丢失是阻塞避免到慢启动的切换点。一开始阀值一般比较大,所以慢启动可能在切换成阻塞避免之前就丢失片段。这种情况下,慢启动就会重新开始,而阀值更新为阻塞窗口大小的一半。

总的来说,发送速率总是在增长。如果片段丢失,则重置速率为1,并快速增长。增长到一定程度,则进入到慢性增长。快行增长和慢性增长的切换点会随着网络状况更新。通过上面的机制,让发送速率处于动态平衡,不断的尝试更大值。初始时增长快,而接近饱和时增长慢。但一旦尝试过度,则迅速重置,以免造成网络负担。阻塞控制有效的提高了对互联网的利用率,但依然不完善

一个常见的小问题是阻塞窗口小在接近饱和时线性增长,因此对新增的宽带带宽不敏感。

TCP实践

TCP协议利用流量控制机制来实现整个网络的总体效率。到现在为止,已经讲解了TCP的几大模块:分段与流,滑窗,连接,流量控制,重新发送,堵塞控制。现在,我们可以在编程中实际利用一下TCP协议。

在Python中,我们可以使用标准库中的socket包来建立TCP连接。这个包已经用于UDP协议的套接字(socket)变成,它同样可以用于TCP协议的套接字编程。我们需要写两个程序,分别用于TCP连接的服务器端和客户端。一旦连接建立,双方可以相互通信。下面是服务器端的程序。我们用bind()方法来赋予套接字以固定的地址和端口,用Listen()方法来被动的监听该端口。当有客户尝试用connect()方法连接的时候,服务器使用accept()接受连接,从而建立一个TCP连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Written by Vamei(RayJune just copy)
# Server side
import socket
# Address
HOST = ''
PORT = 8000
reply = 'Yes'
# Configure socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((HOST,PORT))
# passivel wait:maximum number of connetions in the queue
s.listen(3)
#accept and establish connection
conn,addr = s.accept()
# receive message
request = conn.recv(1024)
print 'request is: ',request
print 'Connected by',addr
# send message
conn.sendall(reply)
# close connection
conn.close()

上面的程序中,socket.socket()创建了一个socket对象,并说明socket使用的是IPv4(AF_INET,IP version 4)和TCP协议(SOCK_STREAM)。服务器写好了,下面是客户。在客户的程序中,我们主动使用connect()方法来搜索服务器端的IP地址和端口,以便客户可以找到服务器,并建立连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Thanks for Vamei
# Client side
import side
# Address
HOST = '172.20.202.155'
PORT = 8000
request = 'can you hear me?'
# configure socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((HOST,PORT))
# send message
s.sendall(request)
# receive message
reply = s.recv(1024)
print 'reply is:',reply
# close connection
s.close()

TCP协议是双向的。因此,我们在连接两s端都可以调用recv()方法来接受信息,调用sendall()方法来发送信息。这样,我们就可以在分处于两台计算机的两个进程间进行通信了。当通信结束的时候,我们使用close()方法来关闭TCP连接。为了展示网络通信,上面程序最好运行与两台电脑。

但如果没有两台计算机做实验,也可以将客户端的IP改为127.0.0.1。这是个特殊的IP地址,指向主机自身。这样,我们在同一台电脑上建立了TCP连接。

小结

流量控制要求参与TCP通信的各方守公德,从而提高了TCP通信的整体效率。这一节的最后,还有一个实际建立TCP连接的例子,用Python实现。到了这里,TCP协议的介绍就可以告一段落了。下一节我们将进入应用层。

DNS协议

前言

在周星驰的电影《唐伯虎点秋香》中,周星驰扮演的主角一进入华府,就被强制增加了一个代号9527。从古,华府的人开始称呼主角为9527,而不是他的姓名。

在互联网上,域名(domain name)是IP地址的代号。

域名与DNS

域名由字符组成

域名通常是由字符够横的。对于人类来说,字符构成的域名,比如www.rayjune.xyz,要比纯粹数字构成的IP地址(192.30.252.154)容易记忆。当然,对于记忆力惊人的计算机来说,记几个IP地址根本不是问题。因此,如果互联网只是给机器使用,那么根本就不需要什么域名。但对于可怜的人类来说,域名要容易记得多。很多公司的域名,如Google、Apple、Taobao、RayJune等,都成为了著名的互联网品牌。

域名的好处

域名解析系统(DNS,domain name system)负责将域名翻译为对应的IP地址。在DNS的帮助下,我们可以在浏览器的地址栏输入域名,而不是IP地址。另一方面,出于维护和运行的原因,一些网站可能会变更IP地址。这些网站可以更改DNS中的对应关系,从而保持域名不辨,只改变IP地址。由于大部分用户记录的都是域名,这样就可以降低IP变更带来的影响。

DNS劫持

但这也意味着,如果有人操纵DNS系统,我们将可能被导向错误的网站。因此,DNS劫持成了常见的网络攻击手段。

DNS服务器

域名和IP弟子的对应关系存储在DNS服务器(DNS server)中。所谓的DNS服务器,是指在网络中进行域名解析的一些专用计算机。这些服务器有自己的IP地址,我们可以通过IP地址找到他们。

与域名服务器的通信需要按照DNS协议进行。DNS协议是基于UDP协议的应用层协议。

DNS头部格式

我们看一下头部重要的一些信息。第一部分id(identification)用于将请求包和回复包的配对。

DNS协议按照”请求-回复“的方式进行,一组请求和回复有相同的id。QR说明通信类型是请求还是回复。opcode说明了操作类型,如用户和DNS服务器之间的标准查询,或DNS服务器之间的信息通讯。RCode说明了回复类型,如正常回复、请求格式错误或服务器故障等。对于请求类型的DNS包来说,RCode是0。具体的问题和回复内容存在DNS包的数据部分

域名查询

我们在浏览器中输入域名。随后,用户计算机会以DNS协议去访问DNS服务器,获得相应的IP地址。解析过程中,需要前往多个DNS服务器。这是因为,DNS服务器是一个分级(hierachical)的树状体系。每个节点(node)就是一个服务器。计算器在查询域名的时候,会先访问出口DNS服务器,也就是树顶端的服务器。

用户计算机中记录有出口DNS服务器的IP地址。可以使用ipconfig/all来查询出口DNS服务器。设置顶端服务器的方法有很多。更具需要,你也可以手动变换出口DNS服务器。树的末端是真正的域名和IP的对应关系记录。

一次DNS查询就是从树的顶端节点出发,最终找到相应末端记录的过程。

中间结点根据域名的构成,将DNS查询引导向下一级的服务器。比如说一个域名cs.berkely.edu,DNS会将域名分割为cs,berkeley,edu,然后按照相反的顺序查询(edu、berkeley、cs)。出口DNS服务器首先根据edu,将查询指向下一层的edu节点。然后edu节点根据berkeley,将查询指向下一层的berkely节点。这台berkeley服务器上存储有cs.berkeley.edu的IP地址。所以、中间结点不断重新定向,并将我们引导到正确的记录。

在整个DNS查询过程中,无论是重新定向还是最终取得对应关系,都是用户计算机和DNS服务器使用DNS协议通信。用户计算机根据DNS服务器的反馈,依次与下一层的DNS服务器建立通信。用户计算机经过递归查询,最终和末端节点通信,并获得IP地址。

域名解析模块

用户计算机的操作系统中的域名解析模块(DNS Resolver)负责域名解析的相关工作。任何一个应用程序,如邮件和游览器,都可以通过该模块来进行域名解析。

许多编程语言都可以操作这个模块,从而进行域名解析。例如在Python中:

1
2
3
import socket
address = socket.gethostbyname("www.rayjune.xyz")
print(address)

并不是每次域名解析都要完整的经历解析过程。域名解析模块通常有DNS缓存(local cache),用来记录最近使用的域名和IP对应关系。在进行DNS查询之前,计算机会先查询缓存中是否有相关记录。这样,重复使用的域名就不用总要经过整个递归查询的过程。

反向DNS

上面的DNS查询均为正向的DNS查询:已经知道域名,想要查询对应IP。

而反向DNS(reverse DNS)是已经知道IP的前提下,想要查询域名。反向DNS采用分层查询的方式,过程与DNS解析类似。IP地址如果是106.10.170.118,反向DNS将查询in-addr-arpa节点服务器(如果是IPv6,则为ip6.arpa节点)、106节点、10节点、170节点。最终,在170节点获得106.10.170.118对应的域名。

需要注意的是,反向DNS服务器是独立于DNS服务器的。因此,有些网站可以进行正向的域名解析,却不能通过地址进行反向查询。DNS解析模块同样提供了反向DNS查询的功能:

1
2
3
import socket
reversedDns = socket.gethostbyaddr('173.230.147.149')
print(reverseDns)

HTTP协议概览

前言

数据流的传输

TCP协议实现了数据流的传输

但人们平时用电脑时,很少会接触数据流。大多数时候,人们都是用文件存储数据。文件是磁盘上的存储单位。

HTTP被用来传输文件

因此,HTTP协议被用来传输文件特别是超文本文件(hypertext document)。超文本文件中除了文本,还内建有格式,能以更加丰富的形式呈现。超文本文件还包含了指向其他资源的超链接,从而构成了万维网(World Wide Web,即www)。

HTTP协议是应用层协议

HTTP协议是应用层协议大多数情况下是基于TCP协议使用偶尔也会用UDP为底层。HTTP随着万维网的发展而流行。早期的HTTP传输的文件主要是静态的,即真实存储在服务器上的文件。

动态文件

随着万维网的发展,HTTP越来越多的用于传输”动态文件”。服务器上根据HTTP请求即时生成的动态文件。我们将HTTP的传输对象统称为资源(resource)。

点单

资源的订购和传送

HTTP实现了资源的订购和传送,其工作方式类似于快递点单。其工作流程分为两步:

  1. 请求(request):顾客向服务员提出请求”来个鸡腿汉堡”。
  2. 回复(response):服务员根据情况,回应顾客的请求。

根据情况的不同,服务员的回应可能会有很多,比如:

  • 服务员准备鸡腿汉堡,把鸡腿汉堡交给顾客。(一切OK)
  • 服务员说这里是个甜品站。他让顾客前往正式柜台点单。(重新定向)
  • 服务员告诉顾客鸡腿汉堡没有了。(无法找到)

交易结束后,服务员就将刚才的交易抛到脑后,准备服务下一位顾客。

请求和回复的格式

HTTP协议的通信是一次”请求”与”回复”的交换。客户端(guest)向服务器(server)发出请求(request)。服务器回复(response)客户端。HTTP协议规定了请求和回复的格式:

  • 起始行(start line)
  • 头信息(headers)
  • 主体(entity body)

重要的起始行

起始行只有一行。它包含了请求和回复最重要的信息。请求的起始行表达顾客”想要什么”。回复的起始行表示服务员”回复了什么”。

头信息可以有多岗。每一行是一对键值对(key-value pair),比如:
Content-type: text/plain

它表示,包含有一个名为Content-type的参数,该参数的值为text/plain。

头信息作为补充

头信息是对起始行的补充。请求的头信息对服务器有指导意义。这就好在菜单上注明”鸡腿不要辣”。回复的头信息则是提示客户端,就好像服务员在盒子上提示”小心烫”。

主体部分

主体部分包含了具体的资源。主题有没有不一定,有时候我们只是在下单,而不管后厨送什么东西;有时候,请求有主题内容,以便传递数据给服务端。回复中包含的主题是一段文本文字”Hello World”。这段文本文字正式顾客所期待的鸡腿汉堡。

请求

请求的具体格式

我们深入一些细节。先来看一下请求的具体格式:

1
2
GET /index.html HTTP/1.1
Host: www.rayjune.xyz

在请求的起始行中,有三段信息,由空格隔开:

  • 首先是GET方法。这段信息用于说明请求的类型。
  • 随后,是index.htm表明的资源路径。这里指向服务器上的/index.html文件。其中的/表示根目录,index.html表示了文件名。
  • 最后的HTTP/1.1说明了协议版本。HTTP第一个广泛使用的版本是1.0,当前版本为1.1.

GET方法

早期的HTTP协议只有GET方法。遵从HTTP协议,服务器接受到GET请求后,会将特定资源传送给客户。这类似于客户点单,并获得汉堡的过程。使用GET方法时,是客户向服务器索取资源,所以请求往往没有主体部分。

GET方法也可以用于阐述一些不重要的数据。它是通过改写URL的方式实现的。URL就是我们平时在浏览器中的地址栏输入的文本,代表了索要请求的资源。GET数据可以用URL?变量名=变量值的方式附带在URL后面。比如向http://127.0.0.1发送一个变量"q",它的值为"a"。那么,实际的URL为http://127.0.0.1?q=a。服务器收到请秋后,就可以知道"q"的值为"a"。

POSt方法

后来,HTTP协议中加入了其他类型的方法。GET方法之外,最常用的就是POST方法。

POST方法用于从客户端向服务器提交数据。使用POST方法时,URL不再被改写。数据位于http请求的主体。POST方法最常用于提交HTML的表单数据。服务器往往会对POST方法提交的数据进行一定的处理,比如存入服务器数据库

样例请求中有一行头信息。该头信息的默认端口是HOST。
HTTP的请求必须有Host头信息,用于说明服务器的地址和端口

HTTP协议的默认端口是80,如果在HOST中没有说明端口,那么将默认采取该端口。在该例子中,服务器的域名为www.rayjune.xyz,端口为80。域名将通过DNS服务器转换为IP地址,从而确定服务器在互联网上的地址。

回复

起始行

服务器在接受到请求之后,会根据程序,生成对应于该请求的回复,比如:

1
2
3
4
5
HTTP/1.1 200 OK
Content-type: text/plain
Content-length: 12
Hello World~

回复的起始行同样包含三部分信息。HTTP/1.1表示了协议版本。200是状态码(status code)。OK用来描述状态。

状态码

OK很容易读懂,它对应了200这个状态码,OK这样的状态描述只是为了便于人类的阅读。电脑关心的是三位的状态码(status code)。状态码代表了服务器回复的类型。200表示一切正常,资源正常返回。

其他的状态码还有很多,例如:

  • 302:重新定向(redirect)。我这里没有你想要的资源,但我知道另一个地方xxx有,你可以去那里找。重新定向时,客户端可以根据302的建议前往xxx寻找资源,也可以忽略该建议。
  • 404:无法找到(not found)。我找不到你想要的资源,无能为力。

头信息

起始行之后是头信息。Content-type说明了主体所包含的资源的类型。根据类型的不同,客户端可以启动不同的处理程序,比如显示图像文件、播放声音文件等等。下面是一些常见的资源类型:

  • text/plain 普通文本
  • text/html HTMl文本
  • image/jpeg jpeg图片
  • image/gif gif图片

Content-length说明了主体部分的长度,以字节(byte)为单位。
头信息之后空一行,就是回复的主题。回复的主题部分为一段普通文本,即”Hello World~”。

无状态

服务器的失忆症

根据早期的HTTP协议,每次”请求-回复”时,都要重新建立TCP连接。TCP连接每次都重新建立,所系服务器无法知道上次请求和本次请求是否来自同一个客户端。因此,HTTP通信是无状态(stateless)的。服务器认为每次请求都是一个全新的亲够,无论该请求是否来自同一个地址。

对于无状态连接,服务员好像犯了失忆症,不能存储客户的信息。假如我们去一家高级餐厅,那里的服务员能一眼认出客人,然后不需要客人点单,久违客人端上来它最喜欢的饭菜。快餐店自然不能提供这样的服务。

为了弥补这个缺陷,HTTP通信的同时可以使用cookie。

cookie是存在客户浏览器的一小段数据,客户发出HTTP请求时,会把cookie交给服务器,以便验证自己的身份,唤醒服务器可怜的回忆。使用cookie的方式有很多,比如和会话(session)结合。这种形式下,客户的资料存在服务器。客户登录之后,服务器会发给客户一张”会员卡”。当客户在cookie中放入会员卡。当它再次来访时,服务器就会认出它,并调出客户的相关资料。

其他不足

除了无状态连接,HTTP还有其他不足。在HTTP发展初期,为了简单起见,HTTP连接完成后就会断开下层的TCP。每次重新建立连接都要耗费额外的时间。

所以,现在的HTTP协议允许TCP连接复用。也就是说,两次HTTP连接之间,如果指向相同的IP和端口,那么将使用同一个TCP连接,但HTTP层面的通信依然是无状态的。这样,HTT通信以**节省连接所好肥的时间。

还有另一个问题。HTTP连接总是按照”请求-回复”的方式一次性完成,由客户端采取主动,服务器只能被动等待。这样的话,服务器不能主动发信息给客户端。这给许多实时通信应用造成不便。

比如说,我的网站可以实时更新足球比分。但客户必须不断刷新,也就是不断发出请求,才能跟得上我的更新。如果客户某段时间不刷新,又有了新的进球,服务器就没办法告诉客户。依靠不断请求来实时更新的方式成为轮询(polling)。我们以多快的频率来更新就成了问题。
频率低,信息不能实时更新。频率高,又会浪费网络资源。总的来说,HTTP的设计并不适合实时通信场景,所以后来又有websocket这样的新协议来满足这一需求

小结

HTTP协议实现了万维网上的资源传输,采用”请求-回复”的工作方式。请求最常用的方法包括GET和POST,分别用于获得和提交数据。HTTP通信是”无状态”的。

实践HTTP通信

前言

了解HTTP协议之后,就可以实践HTTP通信。其实我们在平时生活中浏览网页,已经是在使用HTTP通信。此外,程序员建立一个网站,也是在构建HTTP通信。这里,我们用编程的方法来体验一下HTTP编程。使用的编程语言是Python,版本为2.7。

简单的HTTP服务器

在Python中,可以用标准库中的SIMpleHTTPServer来快速的实现一个HTTP服务器。

DHCP协议

前言

DHCP协议用于动态的配置电脑的网络相关参数,如主机的IP地址,路由器出口地址,DNS域名服务器地址等。一台电脑只要接上网,就可以通过DHCP协议获得相关配置,从而顺利的畅游网络。

网络配置

电脑刚刚接上网络时,像一个走入一家新餐厅的食客,不免会有很多疑惑:
“我该坐在哪里?”、”我怎么点菜?”、”我该如何结账?”

如果顾客的问题长时间得不到解答,那么餐厅很可能失去这位客人。于是,餐厅往往会雇一些服务员。他们可以把客人引导到空闲的作为,并告诉顾客如何点餐、如何结账等。

同样,在网络通信中,一台电脑需要设置自己的IP地址等网络参数。可对于电脑用户来说,这些设置太过复杂。幸亏,DHCP协议可以解决这一让人头痛的问题。

DHCP协议全称为”动态主机设置协议”(Dynamic Host Configuration Protocol)。通常来说,普通电脑中都内置有DHCP客户端模块。电脑接上网络后,DHCP客户端发现新连通的网络,会在该网络上找DHCP服务器。DHCP服务器将给电脑提供合理的网络配置,并把设置信息传回本机。

所谓的DHCP服务器,其实就是一些运行有DHCP服务器端软件的特殊电脑。他们像等候在网络上的服务员,为新来的顾客排忧解难。本机和DHCP服务器之间的通信,都是通过DHCP协议进行的。

其实在网络诞生初期,就有了一台服务器为网络上的电脑配置参数的方法。最早的一个协议叫BOOTP(Bootstrap Protocol),主要用于开机配置。计算机开机时需要很多配置参数。对于没有磁盘的计算机来说,它无法获得这些参数,但可以通过BOOTP从网络上的其他设备获得。DHCP大体上继承了BOOTP的工作方式,但在细节上进行了改进。我们将在以后看到。

地址分配

帮助分配IP”租期”

服务员最重要的任务是帮客人找座位。类似的,DHCP服务器的首要任务是分配IP地址。分配的IP地址要符合一下原则:

  1. 地址合法,即对应该局域网的IP地址和子网掩码。
  2. 地址空闲,同一网络下没有其他设备使用该地址。

DHCP服务器上存有一个地址池,里面是可用的IP地址,相当于服务员手中的空闲桌号列表。当新客人出现时,DHCP服务器就会从地址池中取出一个IP地址分配给客人。此外,服务器还会说明IP地址的占用时间,也就是租期(lease time):
“先生,请注意您的用餐时间为1个小时”

当然,主机使用网络可能超过租期。如果主机在租期到时都没有联系DDHCP服务器,那么DHCP服务器会收回IP地址,再分配给其他主机。可如果主机想继续使用IP地址,就要在中途申请延长租期。收到申请的DHCP服务器通常会答应主机的请求,允许它继续使用现有IP地址。但少数情况下,服务器会要求主机更换IP地址。如果主机联系不上DHCP服务器,那么它必须立即停止使用IP地址,重启寻找DHCP服务器的过程。

有效利用IP地址资源

有了动态分配,DHCP服务器不但简化了网络配置过程,还可以有效利用IP地址资源。例如一个咖啡馆的Wifi路由允许200台设备接入。咖啡馆每天进出的客人很多。如果每个客人都要求一个不一样的IP地址的话,所需地址会远远超过200太。而通过动态的方式,200个IP地址不断回收使用,完全可以满足客人的需要。

有时,DHCP服务器会预留一些地址给特定MAC地址的设备使用。这就好像餐厅给贵宾预留座位,不允许其他人客人坐。这样的地址通常会分配给打印机、传真机等特殊设备。由于IP地址固定,用户可以方便的通过IP地址找到它们。

通信过程

DHCP协议的底层是UDP协议。我们知道,网络上的点对点沟通需要有IP地址。但新接入网络的客户机正是想通过DHCP通信来获得IP地址。这简直成了”鸡生蛋、蛋生鸡”的死胡同。

幸好,除了点对点通信,UDP协议还允许广播通信。把UDP数据包发送到网络的广播地址,网络上每个设备都能收到。因此,DHCP通信主要靠这种广播的形式进行
DHCP通信分为四步:

  1. Discovery:客户机发广播,搜寻DHCP服务器。
  2. Offer:DHCP服务器发出邀请,提供一个可用的IP地址。
  3. Request:客户机正式请求使用该IP地址。
  4. Acknowledge:DHCP服务器确认,并提供其他配置参数。

每一步的通信内容都放在一个符合DHCP格式的数据包中。数据包中可以包括客户机IP地址、服务器IP地址、客户的硬件MAC编号等字段,还能附加多条网络设置参数。当某些信息未知时,如客户机的IP地址,相应的字段可以填成0。
我们可以再次用餐厅来类比通信过程:

1
2
3
4
客人老张大喊:"服务员,我是老张,给我张桌子呗?"
服务员小美回喊:"我是服务员小美。老张你坐3号桌行吗?"
老张觉得3号桌不错,又喊了一嗓子,"这桌子行,我就坐在这里了"
小美回复:"成啊。记得用餐时间是一个小时呦"

经过这一系列的DHCP通信,客户机获得了自己的IP地址,也记下了租期时间。为了防止DHCP服务器不靠谱,客户机通常还会探测一下网络,以免IP已经被其他设备占用。除了租期,DHCP服务器最终确认中,还可能加上其他网络配置信息,如DNS服务器地址、网络出口地址等。客户机可以选择接受,也可以拒绝DHCP服务器的”好意”,自行设置这些参数。

DHCP攻击

一定程度上说,DHCP服务器是个活雷锋,为网络上的其他设备提供公共服务,免得设备之间私自打架。人们也越来越信赖这个活雷锋。只管接上网线、连上Wifi,让DHCP服务器处理配置之类的杂事。无形中,DHCP服务器掌握了很大的公共权力。一些黑客攻击手法开始瞄准DHCP服务器。

针对DHCP服务器的一种攻击办法是从服务器那里骗IP地址。攻击者的电脑可以不断发出DHCP请求,冒充成新入网的客户机。于是,DHCP服务器的地址池被耗干,无法分配地址给后来的用户。后来的用户再也没办法使用网络服务。攻击者很有可能会继续下连环套。攻击者占有了大量IP地址,可以装扮成新的DHCP服务器,把自己骗来的IP地址分配给网络上的新用户。
等等,好像有什么不对,难道攻击者搞瘫一台DHCP服务器,就为了过一下cosplay的瘾?

当然不是。DHCP服务器还能提供其他网络设置参数。攻击者可以让自己成为DNS服务器或者网络出口。于是,客户机的域名解析和外网通信,必须经过攻击者的电脑。这个时候,攻击者的权限就很大了。他可以偷听通信、伪装成客户机、假扮成某个域名的网站。比如说,攻击者就可以篡改域名解析,让你在访问www.rayjune.xyz时,实际上访问的是攻击者提供的一个网页。当你在这个网页上输入用户名和密码时,你的信息就完全泄露给了攻击者。

DHCP攻击让人防不胜防。DHCP协议在设计中并没有考虑到安全性的问题,所以很难从软件商杜绝DHCP攻击。某些品牌的交换机上,可以指定特定端口给合法的DHCP服务器,以免其他人伪装。

当然,最重要的保护方式,还是防止攻击者连入局域网。

小结

DHCP服务器可以动态的分配IP地址,还可以提供其他网络设置参数。客户机和服务器进过四步来完成DHCP通信。

CIDR与NAT

前言

IPv4由于最初的设计原因,长度只有32位,所以只提供了大约40亿个地址。这造成了IPv4地址的耗尽危机。随后,IPv6被设计出来,并可以提供足够多的IP地址。但是IPv4与IPv6并不兼容,IPv4向IPv6的迁移并不容易。一些技术,比如说这里要说的CIDR与NAT,相继推广。这些技术可以环节IPv4地址的稀缺状况,成就了IPv4的逆袭。

CIDR

CIDR(Classless Inter Domain Routing)改进了传统的IPv4地址分类。

  • 传统的IP分类将IP地址直接对应为默认的分类,从而将Internet分割为网络。
  • CIDR在路由表中增加了子网掩码,从而可以更细分网络

利用CIDR,我们可以灵活的将某个范围内的IP地址分配给某个网络。

IP地址分类

在IP接力赛中,我们提到,IP地址可以分为如下几类:

IP classFromToSubnet mask
A1.0.0.0126.255.255.255255.0.0.0
B0.0.0.0191.255.255.255255.255.0.0
C128.0.0223.255.255.255255.255.255.0

这是最初的IPv4地址分类设计。一个IPv4地址总共有32位,可以分为

  • 网络(network)
  • 主机(host)两部分。

子网掩码(subnet mask)是用于表示哪些部分代表了网络部分。比如如下subnet mask 255.0.0.0的二进制表示为:

1
11111111 00000000 00000000 00000000

它的前八位为1,所以表示IP地址的前八位为网络部分。而后面的24位代指该网络的各个主机。一个A类网络可以有224台主机,也就是16777216。由于IPv4地址已经分好了类,所以当我们拿到一个IP地址,我们就可以通过上面查到它的子网掩码。与A类网络相比,B类只有216太主机,而C类网络只能容纳28太主机。

传统路由表

IP分类方便了IP包的接力。IP包到达某个路由器后,会根据该路由器的路由表,来决定接力的下一站。一个传统的路由表看起来是这样的:

DestinationGatewayIface
199.165.145.00.0.0.0eth0
199.165.146.00.0.0.0eth1
0.0.0.0199.165.146.8eth1

由于IP分类,我们不需要记录子网掩码。当我们要前往199.165.146.17时,我们已经知道这台主机位于一个C类地址,所以它的子网掩码是255.255.255.0,也就是说199.165.146代表了网络,17代表了主机。

CIDR路由表

然而,由于默认分类造成了网络只能按照A、B、C的方式存在。假设一个网络分配了一个A类地址,那么该网络将容许16677216个主机。如果该网络无法用完这些IP地址,这些IP地址将无法被其他网络使用

再比如上面的网络,199.165.145必须作为一个整个的网络存在。如果我们只有10台主机,那么将会有200多个IP地址被浪费。CIDR的本质是在路由表中加入子网掩码,并根据该列信息对网络进行分割,而不是根据默认的A、B、C进行分割。比如:

DestinationGatewayGenmaskIface
199.165.145.2540.0.0.0255.255.255.254eth2
199.165.145.00.0.0.0255.255.255.0eth0
199.165.145.00.0.0.0255.255.255.0eth0
199.165.146.00.0.0.0255.255.255.0eth1
0.0.0.0199.165.146.80.0.0.0eth1

根据路由表的第一条记录,
199.165.145.254 (IP address):11000111 10100101 10010001 11111110
255.255.255.254 (subnet mask):11111111 11111111 11111111 11111110

通过子网掩码可以知道,前31位表示网络,最后一位表示主机。子网掩码总是有连续多个1组成,比如上面的31个1。所以也可以记为199.165.145.254/31,来同时表示IP地址和子网掩码

路由器将原来199.165.145网络中的一部分分割出来。这一网络可以容纳两台电脑,也就是199.165.145.254和199.165.145.255.这个网络对应网卡是eth2。当有IP包通向这两个IP地址的时候,会前往eth2而不是eth0。

利用CIDR,我们可以将IP地址根据需要进行分割,从而不浪费IP地址。

NAT

CIDR虽然可以更加节约IP地址,但它并不能创造新的IP地址。IP地址的耗尽危机并不能因此得到解决。我们来看IPv4的第二袭,NAT(Network Address Translation)。
理论上,每个IP地址代表了Internet上的一个设备。但有一些IP地址被保留,用于一些特殊用途。下面三段IP地址被保留用作私有IP地址:

FromTo
10.0.0.010.255.255.255
172.16.0.0172.31.255.255
192.168.0.0192.168.255.255

私有IP地址只用于局域网内部。理论上,我们不应该在互联网上看到来自或者发往私有IP地址的IP包。与私有IP地址对应的是全球IP地址(global IP address)。

NAT是为私有网络服务。该网络中的主机使用私有IP地址。当私有网络内部主机和外部Internet通信时,网关(gateway)路由器负责将私有IP地址转换为全球IP地址,这个地址转换过程就是网络地址翻译(Network Address Translation,NAT)。

网关路由器的NAT功能。最极端情况下,我们可以只分配一个全球IP地址给网关路由器,而且私有网络中的设备都使用私有IP地址。由于私有IP地址可以在不同私有网络中重复使用,所以就大大减小了设备对IP地址的需求。

基础NAT

NAT的一种为基础NAT,也成为一对一NAT。在基础NAT下,网关路由器————转换一个外部IP地址和一个私有IP地址。网关路由器保存有IP的NAT对应关系。

基础NAT尽管是一对一转换IP地址,它还是可以减小内部网络对IP地址的需求。通常来说,一个局域网中只有少数的设备出于开机状态,并不需要给每个设备对应一个全球IP地址。NAT可以动态的管理全球IP地址,并将全球IP地址对应到开机设备,从而减小内部网络对IP地址的需求。

NAPT

NAT还有一种,被称为NAPT(Network Address and Port Translation)。在基础NAT中,高层协议的端口号并不会改动。NAPT下,IP地址和端口号可能同时改动

我们在UDP和TCP中提到端口的概念。在建立UDP或者TCP通信时,我们实际上是用IP:Port来代表通信的一端,就好像打电话时主机和分机号一样。NAPT就是在网关路由器处建立两个通信通道,一个通往内部网络,一个通往外部网络,然后将网关处的通道端口连接,从而让内部和外部通信。比如:

我们假设,通往IP 199.165.145.1建立了三个端口的连接:8888,8889,和8080。它们分别在NAPT处改为通往10.0.0.1:80,10.0.0.1:8080和10.0.0.3:6000。NAPT记录有外部IP:端口和内部IP:端口的一一对应关系。在IP包经过时,网关路由器会更改IP地址,端口号以及相关的checksum。
利用NAPT我们可以使用一个,或者多个但少量的外部IP,配合大量的端口号,来对应多个内部IP以及相应的端口号,从而大大减小了对全球IP地址的需求。

无论是基础NAT还是NAPT,它们的设置都比较复杂,并且从本质上违背了互联网最初的设计理念。但由于IPv4的使用惯性,NAT还是被广泛推广。

于NAT所处的网关服务器是理想的设置防火墙的位置,NAT还往往和防火墙共同建设,以提高私有网络的安全性。

小结

即使是CIDR和NAT广泛使用,IPv4还是在不可避免的耗尽。IPv6正在加紧步数。但上述的两种技术,CIDR和NAT在IPv6中同样被采用,所以了解它们依然是有意义的。

SSL/TLS协议

前言

TLS名为传输层安全协议(Transport Layer Protocol),这个协议是一套加密的通信协议。它的前身是SSL协议(安全套接层协议,Secure Sockets Layer)。这两个协议的工作方式类似,但TLS协议针对SSL协议进行了一些改善。SSL/TLS协议利用加密的方式,在开放的互联网环境中实现了加密通信,让通信的双方可以安心的说悄悄话。

加密

原始加密方法

SSL协议的基础是加密技术。加密和解密是自古就有的技术了。比如说古代的男女偷偷发生私情,不能被相互之间有血海深仇的两个家族知道。男孩问女孩要不要一起私奔。女孩第二天传来答复,上面写着:KFQ

男孩拿着这串字符串想了半天,没明白女孩的意思,就以为女孩不愿意放弃舒适的生活和他私奔。知道十年后,男孩突然灵光一闪,发现如果把每个字母都替换成字母表上提前两个的字母的化,这三个字符就变成了:IDO

对称加密的不安全

这种加密方法是将原来的某种信息按照某个规律打乱。打乱的方式称为加密算法,而打乱过程中的桉树就叫做密钥(cipher code)。上面女孩的加密方式是把元字母替换为字母表上后固定位的字母。而密钥就是固定的位数2了。发出信息的人根据密钥来给信息加密,而接收信息的人利用相同的密钥,来给信息解密。就好像一个带锁的盒子。发送信息的人将信息放到盒子里,用钥匙锁上。而接收信息的人则用相同的钥匙打开。加密和解密用的是同一个密钥,这种加密成为对称加密(symmetric encryption)。

如果一对一的话,那么两人需要交换一个密钥。理论上,如果密钥绝对安全,而且加密算法绝对复杂的话,对称加密是很难破解的。但通信双方很难保证绝对密钥的安全。

公钥私钥

对称加密的薄弱之处在于给了太多人了钥匙。如果换一种思路,只给特工所,而总部保有钥匙,那么就容易了。特工将信息用锁锁在盒子里,谁也打不开,除非到总部用唯一的一把钥匙打开。只是这样的话,特工每次出门都要带上许多锁,太容易被识破身份了。总部老大想了想,干脆就把造锁的技术公开了。特工、或者其他人,可以就地屈才,按照图纸制造锁,但无法根据图纸制造钥匙。钥匙只有总部的一把。

上面的关键是锁和钥匙工艺不同。知道了锁,并不能知道钥匙。这样,总部可以将”造锁”的方法公布给所有用户。每个用户可以用锁来加密自己的信用卡信息。即使被别人切听到,也不用担心:只有总部哎呦钥匙呢~!

非对称加密中,给所有人用的锁被称为公钥(public kye),总部自己保留的钥匙被称为私钥(private key)。这样一种钥匙和锁分离的加密算法就叫做非对称加密(asymmetric encryption)。

非对称加密

非对称加密的原理相对比较只管,而非对称加密听起来就有些神奇。经过非对称加密产生的密文,就算知道加密的方法,也无法获知原文。实现了非对称加密的经典算法是RSA算法。它来自数论与计算机计数的巧妙结合。

SSL协议

可以看到,非对称加密从安全上要强国对称加密。但天下没有免费的午餐。非对称加密的运算成本同样也比较高。为了兼顾效率和安全,SSL协议同时使用了非对称和对称加密。它用对称加密算法来加密信息本身。但对于安全性比较脆弱的对称加密密钥,则采用非对称加密的方法来传输

SSL协议分为客户端和服务器端。通信的核心步骤很简单:

  1. 双方利用明文通信的方式确立实用的加密算法。
  2. 利用非对称算法通信,交换一个密钥。
  3. 该密钥用于对称加密算法,加密接下来的通信正文。

可以看到,SSL协议的关键是用一个非常安全的方式来交换一个对称密钥。交换的过程会比上面的描述更加复杂一些。

  1. 客户发起请求时,除了说明自己支持的非对称加密算法,还会附加一个客户端随机数(Client Random)。
  2. 服务器回复请求时,会确定非对称加密算法和哈希函数,并附上公钥。此外,服务器端还会在此次通信中附加一个服务器端随机数(Server Random)。
  3. 客户端会产生第三个随机数(Premaster Secret),然后利用服务器确定的非对称加密算法和公钥来加密这个随机数,再发送给服务器端。
  4. 客户端用自己的私钥解密第三个随机数。
  5. 这样,客户端和服务器端都知道了三个随机数。双方各自用商量好的哈希函数从三个随机书获得对称加密的密钥。

即使明文通信的时候,某些信息被窃听,但第三部的非对称加密通信部分可以保证窃听者无法完整的获得三个随机书。这样,窃听者还是不知道对称加密的密钥是什么。这样,对称加密的密钥就在一个安全的环境中获得了。为了进一步安全,服务器的公钥会包含在一个数字证书中发送给客户。这样,客户还可以通过数字证书来验证服务器的身份,以免服务器本身出现问题。

HTTPS协议

近年来使用越来越广泛的HTTPS协议就是在SSL/TLS协议的基础上进行通信。HTTP协议在通信过程中要经过多重路由,很容易被窃听。经过SSL协议加密的信息就算被窃听,也只能被通信目的地的人解读,从而保证了信息的安全。所以,如果所访问的网站没有使用HTTPS协议,那么在输入银行帐号和密码之类的敏感信息时,就要三思而后行了。

文章标题:协议森林小记

文章作者:RayJune

时间地点:又玄图书馆

原始链接:http://rayjune.xyz/2017/02/27/protocol-forest-note/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。