在网络课堂上有一个经常问的问题是“为什么我们不把所有设备都放在同一个子网之中?这样就不用再担心路由的问题。”这个原因很简单。每一次当某个系统需要通讯时,无论他是一台主机还是另一台路由器,他们都需要发送一个ARP(地址解析协议)请求。此外,还有一些非ARP的广播数据包,这些讯息网络中所有人的都可以接收到。当一个24位子网中只有 255台设备的时候,这些广播数据包算是比较有限的。在网络中一个很重要的问题就是让这个数字保持在一个较低的状态,因为任何主机在收到一条专发给它的信息或广播信息时,主机必须要处理这个数据包。这需要创建一个硬中断,而且操作系统内核必须要阅读足够的数据以便这个数据包是否要进行进一步处理。
广播风暴时有发生,这种情况的主因是2层拓扑环路。我们在前面的文章里已经接触到一些2层拓扑的问题。当数千个数据包同时涌向你的计算机时,你的机器运行速度会变得非常缓慢。操作系统内核需要把大量的时间用于处理中断,再也腾不出时间处理其它进程。所以,这就是子网为什么非常重要的原因。子网也称作广播域,它可以限制你能够收到的广播范围。
那么,创建一个子网的要点是什么?我如何记住这些看起来怪怪的子网掩码?这与IPv6地址如何配合工作?本期讲座将扩展以前关于子网和CIDR那篇教程的内容,以便让你更全面的理解子网的相关概念。
下一个问题是从一台主机的角度如何来看待广播地址和子网掩码。我们可以理解一台主机需要了解在同一个子网上有什么计算机。这些IP地址能够直接对话,而无需路由器。当子网掩码或者广播地址配置不正确时,你很快就会发现无法访问某些主机。
最常见的设置错误经常出现在当没有同时指定子网掩码和广播地址时设置一个IP地址的时候,由于一些原因,虽然可以通过这其中的一个的值算出另一个,但大多数操作系统并不负责主动更新它们,例如当你运行“ifconfig eth0 130.211.0.1 netmask 255.255.255.0”时,你可能以为一切都会按你预想的方式运行。遗憾的是,你的广播地址很可能被设置为255.255.0.0。这主要依赖于路由器的设置,但是,这个结果通常会导致所有的广播数据包丢失。相反,如果一个子网掩码设置的不正确,这个计算机就不知道这个子网的开始和结束地址。如果一台计算机认为另一个主机在同一个子网中而实际上并非如此,当需要与之通信时这台计算机就会直接向网络中发送ARP请求而不去请求路由器。当然你也可以设置路由器处理这种情况并且让路由器替代目标主机做出ARP应答(称作“ARP代理”,这时可以进行正常的通信),不过,大多数情况下这种情况的结果是无法访问主机。
理解子网掩码的设置原理就可以避免出现上面提到的问题。当你记住子网掩码的含义是“掩盖一些二进制位”时,算出这个网络地址和广播地址并不非常困难。解密一些子网掩码的含义可以加深你对子网掩码原理的理解。一个24位网络地址的子网掩码是255.255.255.0。这很简单。但是,255.255.240.0代表什么意思?破解这个地址的最佳方法就是从掩码掩盖的部分开始。把这个地址与一个标准24位网络地址进行比较。标准24位网络地址有三个字节被掩盖了,我们看到255.255.240.0有二个字节被掩盖了,另一个8位字节被掩盖了一部分。我们知道这是在一个16位网络地址和一个24位网络地址之间。我们必须要理解二进制,并且算出有多少位被掩盖了。这最前面的16个字节显然是网段的一部分。第三个8位字节240让16位的网络地址扩展子网掩码,分析这个数字你可以发现这个字节有4个二进制位没被掩盖(256-240=16,16等于2的4次方)。剩下的4个二进制位加上用于前两个字节中的16个二进制位这意味着我们在处理一个20位的网络地址。
1.0.0.0/255.255.255.248是什么意思?我们确实是在一个小于24位子网的网络中。如果我们查看最后的8位字节中的剩余的字节,我们能够看到有8个可用的IP地址。要记住,只有2的3次方能够等于8,所以,我们使用除了最后一个字节中的三个二进制位以外的全部作为网络地址。这是一个29位网络。当然,简单的地址是非常清楚的:与24位网络相比,255.255.255.128允许的主机地址数量是最后一个8位字节的一半。所以,这是一个25位网络。
关于容易混淆的子网掩码的话题,IPv6地址肯定占有一席之地。这个子网掩码实际上并不是一个问题,因为同样的原则在这里也适用。只是需要记住更多的数字。地址中的真正问题是地址本身的表达方式,IETF(互联网工程任务组)似乎为其制造混乱而感到骄傲。IPv6地址一般以16进制表示。我们的老朋友IPv4也可以用16进制表示一个IP地址,例如用B.B.B.B代表网络地址11.11.11.11。遗憾的是,IPv6地址起来更让人容易迷惑。要表达一个128位地址,IPv6通常把地址分为8个16位字段。
一个IPv6地址看起来是这样的:2013:4567:0000:CDEF:0000:0000:00AD:0000。这个地址确实更容易一些。例如,前面的零不用写,连续的四个零可以简写为::。然而,后面的零必须要显示出来。这有一点混乱。但是,这个规则适用于一个没有歧义的IP地址。每四个零中的第一个零可以删除,但是,零的连续字段的简写每个地址只能进行一次。上述地址把零缩写之后是这样的:2013:4567:0000:CDEF::AD:0000。IPv6提供的地址数量是2的128次方,足够地球每平方米使用大约1000个IP地址。
如果你记住了二进制的规则,IPv6表示地址的规则和一些简单的子网参考,你将成为子网大师。朋友们会请你提供帮助。
小结
•子网对于把广播通信量减少到最小程度是非常重要的。
•用计数被掩盖的二进制位,是推测陌生的子网掩码的最简单的方法。
•IPv6地址在分割为子网方面与IPv4相同。只要你记住表达地址的规则,就可以把混乱减少到最低的程度。
之七:理解和使用ICMP协议
随着本讲座开始接触涉及路由的层,我们必须暂时停一下。我们需要关注一下最容易误解的协议:ICMP(互联网控制消息协议 )。经理人和网络管理员如果计划制定防火墙决策就要了解ICMP协议的真正用途,而且网络管理员要能够使用ICMP协议的知识全面理解路由问题。
既然IP网络不可靠并且不能保证信息传递,因此当发生问题时通知发送人是很重要的。ICMP协议是一种提供有关阻止数据包传递的网络故障问题反馈信息的机制。 它让TCP等上层协议能够意识到数据包没有送达目的地,ICMP协议提供一种查出灾难性问题的方法。这些灾难性的问题包括“TTL exceeded”(超过生存时间)和“需要分更多的数据段”等。ICMP协议不报告IP校验失败等常见的问题。这是因为我们假定TCP或者其它可靠的协议能够处理这类数据包损坏的问题。而且,如果我们使用UDP等不可靠的协议,我们就不应理会较小数量的数据损失。
反之,网络问题需要立即报告。例如,如果IP TTL值(IP生存时间)将达到零,这就可能是网络的某个部分发生了路由环路问题,这样将没有任何数据包能发送到目的地。端点系统需要了解这些类型的故障。ICMP是一种发送各种消息报告网络状态的协议,而非仅仅是简单的ping(联通性测试程序)。回应请求(echo request)仅是ICMP协议提供的众多消息之一。Ping信息可以被过滤掉。但是,大多数ICMP消息类型是IP、TCP和其它协议正常运行所需要的。永远不要相信ICMP协议是邪恶的并且简单的封锁这个协议。
ICMP协议本身非常复杂。每一种类型的ICMP消息也称“主要类型(major type)”拥有自己的“子类型编码(minor codes)”。ICMP协议工作在第3层,因此,它能够在互联网上路由。一个ICMP数据包实际上就是一个IP数据部分包含ICMP协议数据的IP数据包。每一个ICMP消息都将包含引发这条ICMP消息的数据包的完全IP包头,这样,端点系统就会知道实际上哪一个数据包没有发送到目的地。另外引发此ICMP消息的数据包的前8个字节也将包括在内,这通常是TCP或者UDP包头。
简略的说,ICMP协议消息包含永远不会变化的三个字段,随后是ICMP数据,然后是引发此消息的源IP数据包包头。不会变化的三个字段中,前8个字节包含ICMP类型(主要类型)、第二个字段包含了类型代码、第三个字段是ICMP消息校验值。
我们需要认识到,ICMP协议在某些情况下不会发送错误信息。ICMP不会对ICMP信息做出响应。如果ICMP回应其它ICMP消息,这些消息的数量会爆炸性增长而演变为一场ICMP消息风暴。为了防止出现广播风暴,ICMP消息也不会回应一个广播或者多播地址。
最有用的ICMP数据包类型“目标不可达”(类型三)的消息。错误消息一般由路由器生成,并且发送给数据包的来源。大多数错误信息还将发送给与发送的数据包有关的应用程序。在这种情况下,TCP协议将广泛使用ICMP协议。我们在后面将很快看到这种情况。
随着本讲座开始接触涉及路由的层,我们必须暂时停一下。我们需要关注一下最容易误解的协议:ICMP(互联网控制消息协议 )。经理人和网络管理员如果计划制定防火墙决策就要了解ICMP协议的真正用途,而且网络管理员要能够使用ICMP协议的知识全面理解路由问题。
既然IP网络不可靠并且不能保证信息传递,因此当发生问题时通知发送人是很重要的。ICMP协议是一种提供有关阻止数据包传递的网络故障问题反馈信息的机制。 它让TCP等上层协议能够意识到数据包没有送达目的地,ICMP协议提供一种查出灾难性问题的方法。这些灾难性的问题包括“TTL exceeded”(超过生存时间)和“需要分更多的数据段”等。ICMP协议不报告IP校验失败等常见的问题。这是因为我们假定TCP或者其它可靠的协议能够处理这类数据包损坏的问题。而且,如果我们使用UDP等不可靠的协议,我们就不应理会较小数量的数据损失。
反之,网络问题需要立即报告。例如,如果IP TTL值(IP生存时间)将达到零,这就可能是网络的某个部分发生了路由环路问题,这样将没有任何数据包能发送到目的地。端点系统需要了解这些类型的故障。ICMP是一种发送各种消息报告网络状态的协议,而非仅仅是简单的ping(联通性测试程序)。回应请求(echo request)仅是ICMP协议提供的众多消息之一。Ping信息可以被过滤掉。但是,大多数ICMP消息类型是IP、TCP和其它协议正常运行所需要的。永远不要相信ICMP协议是邪恶的并且简单的封锁这个协议。
ICMP协议本身非常复杂。每一种类型的ICMP消息也称“主要类型(major type)”拥有自己的“子类型编码(minor codes)”。ICMP协议工作在第3层,因此,它能够在互联网上路由。一个ICMP数据包实际上就是一个IP数据部分包含ICMP协议数据的IP数据包。每一个ICMP消息都将包含引发这条ICMP消息的数据包的完全IP包头,这样,端点系统就会知道实际上哪一个数据包没有发送到目的地。另外引发此ICMP消息的数据包的前8个字节也将包括在内,这通常是TCP或者UDP包头。
简略的说,ICMP协议消息包含永远不会变化的三个字段,随后是ICMP数据,然后是引发此消息的源IP数据包包头。不会变化的三个字段中,前8个字节包含ICMP类型(主要类型)、第二个字段包含了类型代码、第三个字段是ICMP消息校验值。
我们需要认识到,ICMP协议在某些情况下不会发送错误信息。ICMP不会对ICMP信息做出响应。如果ICMP回应其它ICMP消息,这些消息的数量会爆炸性增长而演变为一场ICMP消息风暴。为了防止出现广播风暴,ICMP消息也不会回应一个广播或者多播地址。
最有用的ICMP数据包类型“目标不可达”(类型三)的消息。错误消息一般由路由器生成,并且发送给数据包的来源。大多数错误信息还将发送给与发送的数据包有关的应用程序。在这种情况下,TCP协议将广泛使用ICMP协议。
在IPv4协议中最常用的ICMP消息类型有以下几种:
•回显应答(类型0)和回显请求(类型8):这是Ping程序发送的信息。
•目标不可达(类型3)
•源抑制(类型4):这是一种用于通知发送者路由器或者主机出现阻塞现象的ICMP消息,发送者需要降低发送速度。
•重定向(类型5):这个消息用来向可以访问两台路由器的主机说“请使用另一台路由器”。我们在此系列讲座中未来的路由问题中再详细讨论这个问题。
•路由器信息应答(类型9)和路由器信息请求(类型10)
•超时(类型11):这个消息有两种用途。第一,当超过IP生存期时向发送系统发出错误信息。第二,如果分段的IP数据报没有在某种时限内重新组合,这个消息将通知发送系统。
当然,上述各种类型的消息中都包含子类型代码。类型三消息“目标不可达”本身有15个子类型代码。我们就不提供每一项的细节了。但是,ICMP协议中有一项非常重要的应用要依靠类型三的消息。
路径最大传输单元(PMTU)是各种协议用来寻找整条路径中支持的最大的MTU(最大传输单元)的机制,小于此限制的数据可以不用分段。发送者在其本地接口设置最大的数据包规格,然后,在IP包头中使用DF(不要分段)的标记发出数据包。如果有问题发送者就会收到第三种类型的ICMP错误信息,其子类型代码是“要求分段,但是已经设置了DF标记”。当发生这种情况是,发送者知道它必须要减小发送数据的规格。如果没有返回错误信息,这就表明MTU的设置没有问题。
在查找PMTU时的主要问题是人们常封锁ICMP协议,阻止这个报错信息传递到发送数据的主机。这种情况很多时候发生在你设法连接的远程站点。假如你向一台Web服务器发送一个请求,但是,一个空白页却不断出现。在虚拟专用网连接上的人们经常会看到这种情况,这是因为由于有的虚拟专用网封装的额外的文件头,它们的MTU比通常的容量要小一些。当远程Web服务器向虚拟专用网用户发送其要求的内容时,如果数据包太大,用户前面最后的路由跳数需要为这个数据分段。如果发送方设置DF标记之后,它能做的一切就是通知发送者必须发送较小的数据包。但是,发送者封锁了ICMP协议,因此这个网站将永远不会看到这种ICMP信息。不过一个好消息是大多数TCP协议的执行都是智能化的。如果它们一直得不到发送数据的许可,它们会自己以较小的分段尺寸发送数据。但是,如果你使用某些流行的、操作方便的操作系统,这种机制并没实现。
简言之,封锁ICMP协议对于成功地运行网络是有害的。这不仅会破坏ping,事实上,如果ICMP协议不工作,许多协议都将不能完全发挥作用。
小结
ICMP包括许多种类型的用于各种用途的数据包,每一种类型都有子类型代码,用于指明这些消息类型的具体内容。
查找路径最大传输单元能够让规格正确的数据包在各种数据包容量的链路上传送。
ICMP对于恰当的路由和数据包传递是非常重要的,你只能封锁你不需要的那一些消息。
之八:初步理解IP协议
本文将介绍理解路由问题所需要的IP协议知识。互联网上的大多数东西都使用IP协议。与以太网不同,了解这个协议对于理解网络在更大范围的应用非常重要。在以后发表的文章中,这个讲座将介绍TCP和UDP协议、路由理论、然后再深入研究具体的路由协议。
IP协议直接位于2层数据链路层之上,负责生成发往目的地的数据报。IP协议原来在RFC 791中定义,后来进行了修改并且进行了多次重新修订。但是,IP协议的基本设计思想仍没有变。IP层不提供任何类型的流量控制或者排序功能。这些功能留给上层。我们将使用“数据报” (datagram)这个词汇指一个完整的IP信息,使用“数据包”(packet)这个词汇指一个单个的IP数据包。
IP协议负责接收和发送指定IP地址数据包。但是,IP协议并不保证数据传递的可靠性。在IP协议层中没有“重试一下”的概念。由于各种原因,数据包有可能出现丢失、损坏、重复、不按照顺序传递或者延迟等问题。IP协议还负责处理IP选项并且以ICMP错误和控制消息等方式提供反馈信息。
IP数据报头有20个字节长,紧接在2层报头后面(因为IP协议是第3层协议)。IP数据部分包含一个完整的TCP或者UDP数据包等一切其它的信息,如下面的图表所示。还要指出的是,如果使用IP选项,IP数据报头可以超过20个字节。
IP协议的目标很简单:生成发往目的地的数据报,而且除了把这个数据包发送到下一跳路由器之外,不需要担心任何事情。实际上,IP协议很复杂,否则,IP数据报头就不需要那么多的字段。认真研究IP数据报头是非常重要的。这些字段从第一个字节开始的含义是:
•版本:使用的IP协议的版本。IPv4数据包将把这个字段设置为“4”。
•报头长度:以4个字节的倍数的方式说明报头的长度。因为很少使用IP选项功能。因此,你很可能你看到它的值将是“5”,意味着报头的长度是5个4字节,也就是20个字节。
•服务类型:这个字段很少使用。但是,在理论上,这个字段旨在向路由器提供转发队列中特定IP数据报优先级顺序信息。主要用于提高服务质量。主机可以选择设置各种选项,如低延迟、高数据吞吐量或者高可靠性等。大多数路由器都忽略这些选项。
•总长度:以字节为单位具体说明包括报头在内的整个IP数据包的总长度。因为这个字段有16位,所以IP数据包长度限制在65K之内。这个数字定义的是字段所在的IP数据包,而不是整个IP数据报的长度。
•IP数据报ID:有时候称作“段标识符”。这个标识符用来确定一个具体的IP数据包属于哪一个IP数据报。如果IP协议需要把多个单个的IP数据包组合成一个IP数据报,这个字段是必要的。
•标志:DF(不分片)位在这个字段中用来指示路由器不要把IP数据包分段。这里也可以使用MF(更多地分片)标识。
•段内偏移量:原来数据报中的分段的偏移量,用64位的块表示。
•生存时间(TTL):IP数据包在被销毁之前包含的跳数。生存时间是为了避免无法发送的数据包永远在互联网上流动。
•协议类型:具体指明下一个协议。也就是在IP数据包的数据部分中将遇到的报头。
•头校验和:一个报头的校验和,而不是数据的校验和。
•IP源:原来发送数据包的主机的IP地址。
•IP目的地:IP数据包目的地主机的IP地址。
当路由器收到一个IP数据包的时候,路由器首先要检查这个数据包的目的地。如果这台路由器有一个通向目的地的路由,这台路由器将减少这个数据包的TTL,重新计算校验和,然后再把这个数据包发出去。如果出现错误,将会发出相应的ICMP错误通知,这个数据包将被丢弃。IP协议就是以这种最简单的方式工作的:它遇到每一个数据包都要重复上述的步骤。
IP分段对于IP功能是非常重要的,它提供了这些报头字段的真正含义。并非每一个发送数据包的物理网络都能够接受同样大小的数据包。各种各样的2层帧格式允许同时发送不同大小的数据。允许的最大的MTU是65KB,最小的是68字节。RFC 1122规定,所有的主机必须能够重新组合最多为576字节的数据报,但实际上是应该能够重新组合与系统接口的MTU规格相同的数据报。
当在互联网上发送一个IP数据报的时候,你不知道沿着每一个2层链路前进的MTU将发生什么情况。你可能通过以太网连接自己的ISP。但是,你正在设法访问的远程站点也许是在一个ISDN链路上。因此,你的IP数据包在到达最后一个跳点之前必须要分段。分段可能需要进行多次。如果我们要向一个通过ISDN连接的远程站点发送一个2000个字节的数据包,我们原来可能把这个数据包分段以便符合我们的1500个字节的链路要求。但它大于576字节(ISDN的MTU)。因此,在到达ISDN链路之前的最后一个路由器必须还要对这个数据包分段。
应该知道,IP不是一个可靠的协议。因此,如果任何IP分段在传输的路径中丢失,整个数据报必须要重新发送。IP没有办法要求得到数据报中丢失的特定部分。因此,当出现错误时,其结果是重新发送该数据报所有的分段。有时候,阻塞的路由器不得不丢弃一些数据包。如果被丢弃的数据包恰巧是一个65KB数据报的一部分,那么,整个数据报必须要重新发送。TCP或者其它上层协议一般都知道一个完整的数据报是否全丢失了,并且能够要求重新发送。然而,TCP协议不能告诉你一个数据报的片段是否丢失了,因为IP收到数据报将是不完整的,并且永远不会向上层TCP协议发送这个数据报。如果TCP协议从来没有收到这个数据报,这个数据报最终将被重新发送。显然,65K数据包的一小部分的丢失对于缓解一个阻塞的链路并没有什么帮助,而是会引起更严重的阻塞。UDP应用程序发送时的大小一般不超过576字节,这有两个原因。第一,MTU小于576字节的链路并不多,因此,这个IP数据报将不会分段。第二,要记住,576是所有采用IP协议的端点系统的特殊数字:它们必须能够把数据报重新组合为这个大小。配置有限内存的设备对于处理大于这个规格的数据可能会遇到困难,因此,这个做法值得推荐。
假设我们是一台主机,我们想发送一个1550个字节的数据报(1530个字节的数据+20个字节的报头)。但是,我们的MTU是1500个字节。我们必须要分为两个数据包发送,相关的IP报头看起来是这样的:
• fragment 0, offset = 0, size = 1480, MF位设置.
• fragment 1, offset = 1480, size = 50
分段中的IP ID和IP地址总是与原来IP数据报中内容是一样的。但是,报头的校验值、偏离量和字段长度肯定要发生变化。当另一方收到第一个数据包并且看到这个数据包是一个分段的时候,另一方将等待获得其它的分段,并且把这些分段重新组合在一起,然后再发送给上层协议。
在这个数据报发出之后,假如在IP标志中没有设置DF字节,我们就不会听到任何有关这个数据报的消息。但是,如果这个链路的某一个地方的MTU是400字节会发生什么情况呢?在可以发送1480字节的数据包之前,这个链路中的路由器会先对这个数据包分段。上一篇教程的MTU路径可用来解决中间路由器为数据包分段的问题。分段要耗费时间和宝贵的路由器资源。我们避免过度分段的主要原因就是因为过度分段将不可避免地引起通信的延迟。
对数据包的重新组合总是在最后的目的地完成。因此,中间路由器不需要存储IP数据报。这也意味着IP数据包能够在不同的路径上单独地路由,而不会引起混乱。这是一个需要理解的重要的概念。这将使IP协议有多种用途。无论接收方以什么顺序收到这个数据包,接收方都能够根据IP报头中的分段偏移量字段重新把数据报组合起来。
现在,我们理解了分段。我们发现分段提出了这样一个问题:IP真的与数据链路层无关吗?
小结
IP协议是不可靠的。当IP数据包丢失的时候,要更高一层的协议认识到数据包的丢失并且要求重新发送。
路由器在每一次发送IP数据包的时候都必须要重新计算IP报头的校验值。
IP分段能够让路由器延迟发送一个数据包或者在多个链路上发送数据包。端点系统将能过重新组合整个IP数据报。
之九:初步理解TCP协议
TCP协议到处都在使用。理解TCP协议的工作原理能够帮助管理员正确诊断网络通信的故障。
TCP协议很复杂。不过不用担心。我们不是让你去阅读RFC 793。本文只是一篇启蒙讲座。在本次讲座中,我们将仅仅介绍为了让你理解下一篇关于TCP协议的讲座所需的知识。经过本文的学习你会了解一些TCP相关的术语,理解TCP包头的各组成部分,然后,我们将在后面一篇文章中重点讲解TCP协议常见的一些问题,包括TCP窗口可伸缩性问题、阻塞和TCP连接机制等问题。
我们有时候听到人们提到“TCP/IP协议栈”。这意味着他们在谈论1至4层和7层的问题,TCP协议位于第四层。其代表的含义是传输控制协议(Transmission Control Protocol)。还记得IP协议那篇文章中的协议头的构成吗?当一个数据包被封装之后,第三层当然有个IP协议头,紧接着就是这个TCP协议头。TCP协议头成为了IP协议头中的“数据”。就像其它协议都有自己的术语一样,TCP协议也有自己的专门术语,如以太网帧、IP数据报和现在的TCP段等。你可以把它们都当作数据包。但是,当它们之间在进行通讯的时候,一定要使用正确的术语。
TCP协议是一种端对端的协议。使用TCP没有任何广播或类似的概念。要用TCP协议与另一台计算机通信,两台机之间必须像打电话一样连接在一起,每一端都都为通话做好准备。“流传输”(Stream delivery)是谈到TCP时的另一个常用词语。这个短语的含义是TCP协议主要用来处理数据流,可以正确处理乱序的数据包。TCP协议甚至还允许存在丢失的或者损坏的数据包,最终它可以再次得到这些数据包。你很可能听一位程序员在谈论“流”的概念。他指的是这样一个事实:数据到底是在什么时候发送的是很难说清楚的,你也可以在TCP流中发送非结构化数据。TCP协议以它自己的方式缓存数据。不过,其缓存过程对程序员和用户是透明的。
TCP协议每发送一个数据包将会收到一个确认信息。这种发送/应答模式是提供可靠的协议的唯一方法:你必须让对方知道你否收到了数据。当然,这也会造成一些性能损失,而人们需要改善系统效率不高的状况。所以引入了“捎带确认(piggybacking ACKs)”的方法。TCP协议之所以是全双工的就是因为这个“捎带确认”信息,因为它允许双方同时发送数据。这是通过在当前的数据包中携带以前收到的数据的确认信息方式实现的。从提高网络利用率的角度看,这比单纯发送一个通知对方“信息已收到”的数据包要好得多。最后,还有一个批量确认的概念:也即一次确认一个以上的数据包,表示“我收到了包括这个数据包在内的全部数据包”。
在IP协议中,我们处理的单个数据包是一个更大的数据报的一部分。请记住,一个TCP段就是一个单个的TCP数据包。TCP是一个数据流,因此,除了“连接”之外,没有任何需要真正担心的其它概念。最大报文段长度(MSS)是在连接的时候协商的,但是,它总是在不断地改变。默认的最大报文段长度是536字节,这是576字节(IP协议保证的最小数据包长度)减去用于IP头的20个字节和用于TCP头的20个字节以后的长度。TCP协议要设法避免在IP级别上的分段。因此,TCP协议总是从536字节开始的。
TCP协议最有魅力的功能仍然保留着。这就是滑动窗口协议。这个窗口实际上是已经发出的“没有签收确认的”数据总数。这个窗口可以根据意愿放大和缩小。这是很有趣的。下一讲将介绍这方面的内容。
一个TCP数据包的头是20个字节,就像一个IP数据包一样。如果使用一些选项,IP和TCP数据包头都可以放大。TCP头不包含IP地址,它仅需要知道要连接哪一个端口。不过,你不要被这弄晕了。TCP工作时要一直跟踪状态表中的端对端的连接。这个状态表包含IP地址和端口。这就是说,只是TCP头不需要IP信息,因为它来自于IP头。
把一个数据包设想为一个字节跟着一个字节的数据流是很容易的。很多人都想要一个显示TCP头的表格。但是,这常会把事情搞乱。TCP头从第一位开始依次是下面这些内容:
•源端口,16位:用于这次连接的本地TCP端口。
•目的地端口,16位:通讯目标机器的TCP端口。
•序列号,32位:用来跟踪数据包顺序的号码。
•确认编号,32位:我们确认的以前收到的序列号。
•头长度,4位:报头中的32位字(words)的数量。如果不使用选项,这个值设定为5。
•保留,6位:为将来的使用保留的字节。
•标记,一共6位:每一个标记一个字节(开或者关)
-URG:紧急字段指针。
-ACK:本数据包是(或者包含)一个确认信息。
-PSH:推送功能(没有使用)。
-RST:重置,或者中断本次连接。
-SYN:同步数据包,也就是开始连接。
-FIN:最后一个数据包,开始挂断序列。
•窗口尺寸,16位:从接收方将收到的确认字段开始。
•校验和,16位:TCP头和数据的校验和。
•应急指针,16位:指向跟在URG数据后面的数据的序列号的偏移值。
•选项:MSS、窗口比例等等。我们在关于TCP协议的下一讲中将重点介绍这个部分。
TCP连接的两端使用两对IP地址和端口识别这个连接,并且向监听这个端口的应用程序发送数据。
先介绍这么多内容。有关TCP协议的内容还有很多。
小结
TCP是一种最常用的协议,在协议栈中位于第四层。
TCP协议提供阻塞控制、可靠性和发送数据的流。
为了提高效率,TCP协议在得到确认之前努力发送尽可能多的数据。
之十:TCP协议理解进阶
现在我们来介绍一下TCP协议的运行问题,因为我们对TCP协议实际上是什么样子知道的并不多。
我们说过,TCP协议在能够发送数据之前就建立起了“连接”。要实现这个连接,启动TCP连接的那一方首先将发送一个SYN(回忆一下在上一篇文章中讲到的TCP包头格式)数据包。这只是一个不包含数据的数据包,然后,打开SYN标记。如果另一方同时在它收到SYN标记的端口通话,它将发回一个SYN+ACK:SYN和ACK标志位都被打开,并将ACK(确认)编号字段设定为刚收到的那个数据包的顺序号字段的值。接下来,连接发起方为了表示收到了这个SYN+ACK信息,会向发送方发送一个最终的确认信息(ACK包)。这种SYN、SYN+ACK、ACK的步骤被称为TCP连接建立时的“三次握手”。在这之后,连接就建立起来了。这个连接将一直保持活动状态,直到超时或者任何一方发出一个FIN(结束)信号。
任何一方都可以关闭一个TCP连接,要求双方发送一个FIN信号关闭自己的通讯频道。一方可以在另一方之前关闭,或者双方同时关闭TCP连接。因此,当一方发送一个FIN信号时,另一方可发送“FIN+ACK”,开始关闭自己一方的通信并且确认收到了第一个FIN信号。发送第一个FIN信号的人接下来再发送一个“FIN+ACK”信息,确认收到第二个FIN信号。另一方就知道这个连接已经关闭了,并且关闭了自己的连接。发送第一个FIN的人没有办法收到最后一个ACK信号的确认信息。这时它会进入“TIME_WAIT”(等待时间)状态并启动一个定时器,防止另一方没有收到ACK信息并且认为连接仍是打开的。一般来说,这个状态会持续1至2分钟。
现在,我们来讨论第一个问题。如果有人(假如一个黑客)在你的Web服务器上留下一个半开或者半关的连接,那就是一个坏消息。每一个连接都要消耗内存,打开数千个虚假的TCP连接可能导致服务器瘫痪。当然,你实际上不可能在不影响TCP正常工作的情况下调整TCP定时器。如果你听说过TCP SYN 攻击的话,那就是这个意思。为了防止出现这种情况,大多数操作系统都要限制半开连接的数量。例如,Linux默认的限制一般是256个。
我们前面提过将讨论关于持续流控制问题,现在我们就来讨论这个问题。TCP中实现它的机制是TCP滑动窗口机制。TCP协议使用“重新发送与正向ACK”来保证数据传输的可靠性。发送方将等待一段时间,如果没有收到其发送的数据包的ACK确认信息,发送方就要重新发送。顺便说一下,TCP协议中有许多定时器。这只是其中一个定时器。ACK的概念对于流控制是非常重要的,因为TCP滑动窗口协议使TCP的往复确认变得更有效率。如果TCP要发送一个数据包并且等待每一个ACK确认信息,它实际上就把数据吞吐量削减了一半。
理想的情况是,我们能够一次发送许多数据包,然后等待收到一个确认收到全部数据包的ACK信息,而不用对方发来更多的数据。但是,我们如何知道发送了多少个数据包呢?TCP窗口尺寸可以控制在“已发送但是没有确认”的状态下能够容纳多少个数据包。如果这个窗口尺寸很大,我们不必等待ACK信息就可以发送大量的数据包。这实际上就是流控制。
接收方就是控制窗口大小的那一方。如果接收方将窗口大小设为“0”,那么,发送方根本就不能发送任何数据。如果这个窗口的尺寸是“1”,那么,我们就回到了简单的“发送和等待ACK”的协议。如果最后的窗口尺寸是“0”,发送者将发出一个探测信号以搞清这个窗口什么时间再次打开。如果发送方从来没有收到ACK信息,它就一直不断地重试,直到定时器过期。请记住,这个窗口尺寸在TCP头中是一个16位字段。如果你要一个窗口尺寸(按字节计算)大于16位可以表示的容量(2的16次方个字节),还可以使用一个名为“窗口缩放”的TCP协议选项。这个选项允许窗口尺寸乘以比例因子。如果没有极大的窗口尺寸,TCP协议就就无法充分利用GB级别的高速连接。这也是我们需要针对这些新的高速连接调整TCP参数的原因,
关于TCP流控制的问题,我们不能不提一下Nagle算法。如果我们在一个telnet连接上使用一个大的TCP窗口会发生什么事情呢?你会输入一个指令(例如敲了一个字母),然后一直等待回应它却迟迟不出现在终端回显上。这对于实时通信来说是一个大问题。而且,telnet也会增加网络的阻塞度,因为一个字节的数据(例如我们的一次击键)需要40个字节的包头。于是RFC 896定义这个Nagle算法,用以消除小的数据包。这个思路是我们应该在数据发送之前给先把小数据集中起来然后一次性发送,以便提高效率。为了更有效率,它还限定只允许存在一个未经确认的数据段,你在得到确认信息之前不能发送更多的数据。Telnet和互动SSH连接使用TCP_NODELAY套接口选项启用这个功能,这样当你按下一个按键的时候,你能够立即得到一个回应。
当然,我们仍是忽略了有关TCP协议的许多事情。然而,通过这两篇文章的了解,你应该能够理解其它一些更专业的TCP著作。阻塞控制与流控制不同,本文没有讨论。如果你真的对了解TCP协议的全部工作原理感兴趣,你可以详细阅读TCP RFC。
小结
TCP协议非常善于解决流控制问题,因此非常适应于许多应用程序。TCP协议中的流控制的含义是:“在收到对发送的数据的确认信息这前,我可以发送多少数据?”这就是TCP窗口。学习阻塞控制的问题可以留作读者的练习。需要指出的是,在TCP协议之下连接速度开始很慢,然后速度逐渐加快。这个做法并不总是最理想的。