From 44a1354ea749a8b4038fc738e940fe53f0e5e8d7 Mon Sep 17 00:00:00 2001 From: Shine wOng <1551885@tongji.edu.cn> Date: Wed, 23 Oct 2019 19:32:37 +0800 Subject: [PATCH] add new director network/ --- network/IP layer.md | 208 ++++++++++++++++++++++++++++++ network/Transport Layer.md | 242 +++++++++++++++++++++++++++++++++++ network/application layer.md | 203 +++++++++++++++++++++++++++++ thu_dsa/chp5/chp5.md | 8 +- 4 files changed, 657 insertions(+), 4 deletions(-) create mode 100644 network/IP layer.md create mode 100644 network/Transport Layer.md create mode 100644 network/application layer.md diff --git a/network/IP layer.md b/network/IP layer.md new file mode 100644 index 0000000..6c61ca6 --- /dev/null +++ b/network/IP layer.md @@ -0,0 +1,208 @@ +网际协议IP知识总结 +=============== + +## 网际层提供的功能 + +从结论来说,网际层向运输层提供的是无连接的服务。网际层负责将数据从一台主机,传输到连接到网络的另一台主机。 + +为什么不像传统_电信网_一样,提供面向连接的服务? + +电信网是需要两个用户首先建立连接,然后才能进行通信,所以是一种可靠的传输。可是计算机之间的通信与电话机的通信有本质的区别--计算机具有强大的差错处理能力,而这时电话机所不具有的。所以网际层的传输可以尽量简单灵活,尽最大努力交付,而把可靠传输的工作留给上面的运输层。 + +## 产生的一些思考 + +上面说,网际层的工作是实现数据在主机之间的传输,那么这究竟意味着什么?完成这点又需要哪些工作呢? + +首先,为了把数据传输到另一台主机,当前的主机需要知道另一台主机的位置。这意味着: + ++ 网络中的每台主机都要有一个独一无二的id,一个不会重复的地址 ++ 当前主机需要有办法从茫茫人海(机海?)中标志出目标主机 ++ 传输过程当中,需要有一些信息中转站(数据链路层),否则信道的利用率会相当低 + +第一条是容易理解的,因为如果主机地址不能被唯一标志的话,很有可能数据会被传输到别的主机上去,会有信息泄露之类的问题。实际上,每一台主机的网卡都有唯一确定的硬件地址(MAC地址),是由网卡生产商决定的。 + +第二条是基于下面的理由。如果我只想和唯一的一台主机通信,问题也不是那么简单,因为主机的位置是会发生变化的,所以每次我可能都会需要一条不同的通信链路,如何找到这条最佳的链路是一个问题。另外,实际情况是,对于互联网中每一台主机,理论上都可以相互通信,这样每个主机都需要管理所有其他主机的地址,而这是不可能的,因为我们有数百亿个主机,以后还可能会更多。 + +第三条详见数据链路层与物理层协议。 + +这样的话,主机之间通信的步骤应该是下面这样: + ++ 主机A需要和主机B通信,A知道B的唯一标志地址 ++ 主机A查询到B的路径(需要经过哪些信息中转站),然后向第一个中转站发送信息 ++ 信息在各个中转站之间传递,直到信息发送到主机B + +从上述过程可见,网际层传输最核心的工作就是获得到另一台主机的路径问题。或是大规模网络单源最短路径问题。下面的讨论都是基于路径选择而展开。 + +## 使用IP地址 + +首先指出结论,要在百亿级的主机中找到目的主机,使用硬件地址是不可能的,而需要使用逻辑地址。 + +上述结论是基于以下考虑: + ++ 数据的传输是与地域有关的,当前主机与同一地域的不同主机通信,极有可能是使用相似或是相同的路径。因此,如果给处于相近地域的主机划分一个共同的<上一级地址>,数据传输时先传输到<上一级地址>,再在组间传输,需要管理的地址数量会大幅度减小 ++ 不能直接使用硬件地址,因为硬件地址是与地域无关的,只与网卡生产的厂家有关。 + +所以,我们需要在硬件地址之上抽象出一层逻辑地址,并且该逻辑地址是与地域相关的,共同地域的一些主机享有相同的逻辑地址头部,就好像楼层与房间的划分关系一样。实际上,这是树形结构的思想。 + +而IP地址,就是这里使用的逻辑地址。 + +### 分类的IP地址 + +如上面所述,IP地址的结构是<网络地址:主机地址>,一共含32位。根据网络地址的头部,又可以划分为几类IP地址 + ++ A类地址:网络号8位,首部为零。主机号占24位 ++ B类地址:网络号16位,首部为10。主机号占16位 ++ C类地址:网络号24位,首部为110,主机号占8位 ++ D类地址:多播地址 ++ E类地址:保留 + +此外,还有一些特殊的IP地址。 + ++ 网络号为`0111 1111`的A类地址,保留为本地环回测试,用于本主机的进程之间通信之用 ++ 网络号全零:表示本网络 ++ B类和C类的固定位以外,全零的网络号都是不指派的:`128.0.0.0` 以及 `192.0.0.0` ++ 主机号全零表示本主机连接到的__当前网络地址__ ++ 主机号全一表示当前网络的全部主机 + +### IP地址实现树形结构 + +那么问题来了,IP地址是如何实现将多个同一地域主机归属到同一个地址呢? + +使用IP地址后,连接到同一个网络的主机(通常是同一个地域,如局域网)都拥有同一个网络号。在数据传输的时候,路由器只是根据网络号来转发分组,从而一个网络的多台主机都使用同一个网络地址。需要存储的地址空间大幅减小了。 + +在数据到达本网络后,本网络的路由器只需存储了本网络主机的IP地址与映射的物理地址,就可以把数据发送给目的主机了 + +### 划分子网与构造超网 + +上面的解决方案看起来已经很好了,只是在IP地址方面,似乎还存在一些问题。 + +IP地址分为A,B,C三类网络,可是这三类网络主机的划分却是很不均匀的。如A类网络,每个网络可以有2^24-2台主机(24位主机号,除去全零和全一),可是C类网络最多只能有254台主机。 + +这会产生一些问题: + ++ 地址空间利用不足。如分配了A类地址的网络,往往不能完全用完所有的IP地址。 ++ 对于A类地址的网络,即使是通过网络号找到了目的网络,在其网络内部定位到目的主机仍然是一个不小的工作,因为A类网络最多可以拥有一千多万台主机,需要管理这么多主机的地址并不是一件轻松的工作。 ++ C类地址的网络还是太小了,世界上可以存在大量C类地址网络,在这些网络中定位到目的网络也非常困难,需要管理大量的网络地址信息。 + +对于前两个问题,可以通过构造子网解决。最后一个问题,可以通过构造超网解决。但实际上,构造子网与超网本质上是同一个思想。 + +我们先看IP地址的设计。IP地址分为了多种类型,这些类型的网络号是具有唯一前缀的。就是说通过网络号前缀,就可以知道这是哪一些IP地址,从而知道其网络号与主机号分别占多少位。 + +所以,如果我们增加一个变量表示网络号的位数,那么理论上我们是不是就可以拥有任意位数的网络号与主机号。这个变量就是子网掩码。如果该新的网络号位数大于原来的位数,这就是构造子网;如果该新的网络号囊括了多个原来的多个网络,这就是构造超网。 + +可以看到,通过上面的方案,IP地址的分类已经没有意义了。因此这种方法也叫无分类编址(CIDR, Classless Inter-Domain Routing, 或叫无分类域间路由选择) + +当然,实际上并不能拥有所有位数的网络号,因为这样会导致一些网络太大,而另一些网络太小。实际上常用的网络号前缀长度包括/13 - /27 + +### 从IP地址到硬件地址的转换 + +当数据传输到目的主机所在网络的路由器后,该路由器需要将数据直接转发给目的主机。可以看到,这里已经不是网际层的工作范围了,因为这里的数据传输是网内传输。这一步的数据传输需要知道目的主机的硬件地址了,因此需要完成IP地址到硬件地址的转换。这这就要用到地址解析协议ARP。 + +ARP的功能就是从网际层使用的IP地址,解析出在数据链路层使用的硬件地址。所以,按照我的理解的话,这应该是一个数据链路层的协议。 + +ARP的实现主要需要一个ARP高速缓存,里面存放了本局域网上各主机和路由器的IP地址到硬件地址的映射表。局域网的每一台主机都管理有一个ARP高速缓存。 + +ARP高速缓存的构造流程如下: + ++ 当前主机A需要发送信息给本局域网的另一台主机B,因此A查询自己的ARP高速缓存,如果查找到了B的硬件地址,则直接交付给B。若没有,则进入下一条 ++ 主机A在本局域网上广播一个ARP请求分组,所有局域网上的本机都将受到这个请求 ++ 主机B和请求分组中要查询的IP地址一致,因此主机B向A发送ARP响应分组,写入自己的硬件地址 ++ A收到B的ARP响应分组后,在其ARP高速缓存写入主机B的IP地址到其硬件地址的映射。 + +### 网际层数据传输的流程 + +根据上面的讨论,可以归纳出网际层数据传输的流程 + ++ 主机A要向另一台主机B发送数据。主机A首先提取出主机B的IP地址,得出目的网络地址 ++ 如果目的网络与当前网络是同一个网络,主机A查询主机B的硬件地址,并对B进行直接交付 ++ 如果目的网络不是当前网络,则将数据包交付给合适的路由(特定主机路由或是默认路由) ++ 路由器查询其路由表,找到到达目的网络的下一条路由器,并且交付给下一路由器 ++ 上述过程循环进行,知道交付给目的网络所在的路由器 ++ 若没有找到路由,则报告转发分组出错 + +## 路由选择 + +根据上面的讨论,IP地址的抽象,子网与超网的划分,都是为了减少路由表中的条目,提升路由选择的效率。有了上面的基础,大概是可以归纳路由表具有哪些条目 + +### 路由表的条目 + +路由表并没有给出从一个网络到另一个网络的完整路径,而是给出了下一跳路由器。这样,通过不断地查询到达的路由表,最终还是可以将数据传输到目的网络。 + +路由表中的基本条目包括: ++ 目的网络地址 ++ 子网掩码(网络掩码):为了区分网络号和主机号 ++ 下一跳地址 + +此外,还有一些字段根据路由选择算法的不同而异。 + +### 路由表的建立:路由选择算法 + +路由选择算法实际上是构造路由表的算法。 + +上面提到过,无分类编址的IP地址已经是为了减少路由表中的条目数,提高路由选择的效率。但在实际应用中,这样的无分类网络还是太多了,一个路由器不可能保存全部网络的路径。因此,按照和之前一样的思想,我们可以人为的再把这些网络做一些归类,这就是自治系统(Autonomous System)。 + +自治系统内部应该使用同一种路由选择算法。这也是产生自治系统的另外一个原因--许多网络会采用自己的路由选择协议,这样在路由选择时,由于前后会有多种不同的路由选择协议,很难得到一个全局比较优化的路径。因此可以把使用相同路由选择协议的网络划分成一个自治系统。一个自治系统对外表现出单一和一致的路由选择算法。 + +这样,路由选择协议就呈现出分层次的特征--在各个自治系统间会使用一种路由选择协议(外部网关协议EGP),自治系统内部会各自采用自己的路由选择协议(内部网关协议IGP)。而自治系统对外的路由器会需要同时运行外部和内部的路由选择协议。 + +### 内部网关协议 + +类似于Dijkstra或是Floyd算法。总之就是在自治系统内部找到全局最短"路径"。不过这里的路径有不同的定义方式。 + +#### 路由信息协议RIP + +RIP只使用路由器之间的距离(跳数)作为衡量路由选择的指标,尽管有时候路径更长的网络传输效率更高。 + +RIP协议的特点: ++ 每个路由器只与相邻路由器交换信息 ++ 交换的信息是当前路由器的全部路由表 ++ 按固定的时间间隔交换信息 + +各个路由器更新的原则就是找到到某个网络最短的路径。因此 ++ 一个路由器受到相邻结点X发来的路由表。将其中的距离信息加一 ++ 对于受到的路由表中的每一个项目,若本路由表中没有该目的网络,则添加到自己的路由表中 ++ 若当前路由表中存在目的网络,且下一跳路由器是X,则用收到的项目替换当前项目(因为这是最新的消息,以最新的消息为准) ++ 若当前路由表中存在目的网络,但下一跳不是X,则比较距离。取距离更小的条目 ++ 若三分钟还没有受到相邻路由器的信息,则把此路由器标记为不可达 + +存在的问题:好消息传播的快,坏消息传播慢 + +#### 开放最短路径优先OSPF + +同样是三个要点 + ++ 每个路由器是向自治系统内的所有路由器发送信息。实现方法是每个路由器先发给自己相邻的路由器,相邻的路由器受到后再向自己相邻的进行转发。(洪泛法) ++ 发送的信息只是与本路由器相邻的路由器的链路状态(如是否连通,以及该链路的度量)。这个度量可以是距离,也可以是费用、带宽、时延。可以由自己定义,所以比较灵活。 ++ 只有在链路状态发生变化时才进行更新。 + +与RIP相比的话,RIP中每个路由器只能知道到所有网络距离以及下一跳,而采用OSPF可以构造出全网的拓扑结构图(就跟Floyd算法似的)。然后说的是当网络较大时,OSPF可以更快收敛,运行性能更好(反正我是不太明白,我觉得OSPF的数据量要大的多???) + +为了运行的更快,还可以在自治系统里面划分区域,洪泛法在各个区域内进行 + +### 外部网关协议BGP + +边界网关协议BGP的目的并非是寻找到一条最优路由,而是力求寻找到一条能够到达目的网络的比较好的路由。这是由于各个自治系统运行各自的路由选择协议,对路径的度量不同。此外,还有一些自治系统会不允许某些自治系统通过,或是需要付费。因此找到全局最优路径非常困难。 + +采用路径向量路由选择协议 + ++ 各个自治系统的BGP发言人之间交换路由信息 ++ 交换的信息是要到达某个网络所要经过的一系列自治系统(路径) ++ 每个自治系统可以构造出一个自治系统连通图,然后选择一条较好的路径 + +### 路由表的检索 + +使用二叉线索树进行检索。唯一前缀(大约是哈夫曼树那些东西) + +### 路由器的结构 + +#### 路由选择部分 + +也叫做控制部分,核心部件是路由选择处理机。主要是运行路由选择算法,与其他路由器交换信息,然后构造路由表。并且需要不断地更新和维护路由表。 + +#### 分组转发部分 + +由三部分组成 + ++ 交换结构:根据路由表对分组进行处理(查表) ++ 输入端口 ++ 输出端口 diff --git a/network/Transport Layer.md b/network/Transport Layer.md new file mode 100644 index 0000000..386481d --- /dev/null +++ b/network/Transport Layer.md @@ -0,0 +1,242 @@ +传输层协议知识总结 +================ + +## 传输层提供的功能 + +### 进程之间的通信 + +通过IP层,数据可以从网络中另一台计算机交付到当前的计算机。这些数据必然需要某个特定的应用程序来读取,以便进行处理,显示给用户。传输层提供的就是这样_进程对进程_之间的通信。 + +这样的话,发送数据的一方就不只是需要指定接收方的IP地址,还需要指定对方主机是由哪个进程来接收这些数据。 + +一个想法是每一台计算机中每一个进程都被分配了一个进程号,可以通过这个进程号来指定通信的双方。但是,很明显的是,这里存在一些问题。首先是不同计算机分配的进程号格式不一致,在报文的首部并不容易用固定的字长编码。还有就是,这些进程号显然是不断变化的,每次通信的进程号不同会给数据传输带来很大的不方便。 + +为了解决上面的问题,一个自然的想法是给不同计算机的所有进程一个统一的进程编码。但是进程往往是不确定,并且常常会产生新的进程。这样的话,很少会有主机会知道某台主机上出现了新的进程并与之通信。 + +真正的解决方案是采用_协议端口号_,这是基于缓存的思想:与当前主机通信的其他主机上的进程将数据传输到当前主机后,就将数据放在某个本地仓库中,之后由本地的进程前去读取。因此发送方只需要知道目的主机的某个应用进程的仓库号是多少,就可以把数据发送到目的主机进程的制定仓库,从而实现进程之间的通信。 + +存在一些常见的应用进程,他们是互联网中最重要的一些应用程序。因此给这类进程都制定了一个_熟知端口号_(或系统端口号),数值为0 ~ 1023。这些进程有: + ++ 21: ftp控制信息端口 ++ 22: ssh ++ 23: telnet ++ 25: SMTP ++ 80: HTTP ++ 53: DNS ++ 69: TFTP ++ 443: HTTPS + +这些功能大多还会在后面的应用层中讨论。 + +此外还有登记端口号,数值为1024 ~ 49151,是给没有熟知端口号的应用进程使用的。 + +客户端端口号则是客户进程在请求服务器的服务时,系统自动给客户端分配的端口号。这些服务器进程就可以通过这个客户端端口号与客户进程进行数据与命令的交互。通信结束后,刚才使用过的客户端端口号就不复存在了,可以分配给其他客户进程使用。 + +### 复用和分用 + ++ 复用: 不同的应用进程可以复用同一个运输层协议来传输数据 ++ 分用: 接收方的运输层在剥去报文的首部后可以将这些数据交付给不同的目的应用进程 + +### 实现可靠传输 + +前面说过,IP层的传输是不可靠传输。这是因为一开始它就把锅丢给了传输层(误 IP层认为计算机的差错处理能力很强,所以它只负责简单灵活地交付数据,然后依靠传输层来保证数据的可靠传输。 + +除此以外,传输层还会实现一些更高级的功能,如流量控制,拥塞控制这样。 + +## UDP + +话虽然像上面那样说,但是UDP就是一个叛徒。它明明知道作为一个传输层,它的主要任务应该是可靠传输,可是由于人人都有惰性,它就采用了一个怎么简单怎么来的尽最大努力交付。所以,简单说来,它的特点就体现在一个字,简单! + ++ 无连接的传输。传输前不需要先建立连接 ++ 尽最大努力交付,不保证可靠交付 ++ 面向报文的。上面的应用进程交给它多大的报文要它发送,它就一股脑一起打个包就发送了。尽管报文太大会使下面IP层分片,报文太小会使得传输效率低下。简单说来就是消极怠工,让干什么就只干什么,绝对不会多做一点工作。 ++ 没有拥塞控制。(当然没有了! 就是说即使网络已经很拥塞了,他还是继续发他的数据,这样就可能导致一些数据的丢失(缓存满了就被路由器丢掉了)。但是这样居然还有一定的好处,就是尽管数据会有丢失,但不会有太大的时延,因此对于一些实时性应用非常有用。比如像QQ这样的就是主要用UDP。 ++ 支持一对一,一对多,多对一和多对多的交互通信。毕竟无连接啊 ++ 首部开销小。它都这么简单了,还需要什么首部开销?无非 + - 源端口:两字节 + - 目的端口:两字节 + - 长度:两字节,单位Byte,最小是8(仅有首部) + - 检验和:两字节,同时检验了源IP,目的IP,端口号,数据 + +## TCP + +### 概述 + +TCP就比较强了,提供的是可靠的传输。除此以外,还提供了额外的功能,像是流量控制,拥塞控制这样。堪称模范好学生好吧。所以很明显,他非常复杂...他的特点也一个字,难 + ++ 面向连接的传输。数据传输之前必须先建立连接。传输完数据后必须释放已经建立的连接。 ++ 只能一对一。因为面向连接啊,你只能传输给连接的另一方。 ++ 提供可靠交付的服务。通过TCP传输的数据,无差错,不丢失,不重复,并且按序到达(你听听,这还是人话吗 ++ 提供全双工通信。双方都有发送缓存和接受缓存,可以同时收发 ++ 面向字节流。TCP并不理解应用进程交给它的报文,而只是看做无结构的字节流。所以他可以自己决定什么时候发送多大的字节流,这样发送的时候就顺便加上流量控制和拥塞控制。只要接收方受到同样的字节流就行。 + +关于TCP的连接的话,前面也说过了,需要同时知道IP地址和端口号。所以TCP使用套接字作为连接的端点。其中,套接字socket = (IP地址: 端口号)。比如说:127.0.0.1:80就表示本机的80端口 + +### 可靠传输的原理 + +关于这个问题可以再思考思考<两支蓝军与白军作战的例子>。之前就是思考这个想出了自动重传请求(ARQ, Automatic Repeat reQuest) + +#### 自动重传请求 + ++ 前提: A是发送方,B是接收方 ++ A向B发送数据 ++ 如果B收到数据,则向A发送一个确认信息 ++ 若A向B发送数据失败,或是数据在传输过程中失真,则B不会向A发送确认信息,那么A由于没有受到B的确认信息,A会向B重新发送信息 ++ 若B向A发送的确认信息在传输过程中丢失,那么A也不会受到这个确认信息,A仍然会向B重新发送 ++ 若A第一次发送的数据时延较大,A向B第二次发送数据并成功接收。B过了一段时间后又受到了A第一次发送的数据,B收到以后就丢弃这个数据 + +可以看到,实现自动重传请求有两种方法 ++ 停止等待协议:A每发送一个数据就停止发送,直到收到B发来的确认信息。停止等待协议固然很简单,不需要维护太多信息。但是很明显信道的利用率太低。因此一般是使用滑动窗口协议。 ++ 滑动窗口协议:基于流水线的原理,A不断地发送数据,并且在发送过程中不断接收来自B的确认信息。 + +要实现滑动窗口协议,需要满足 + ++ 发送方需要发送当前所发报文的是第几个报文,以便让接收方将没有按序到达的报文组装起来。由于TCP是基于字节流的传输,所以一般是发送当前报文的第一个字节序号 ++ 接收方要对发送方发送的字节流进行确认。如果每个字节逐个确认,那效率也太低了吧,需要发送大量的信息。所以一般是使用_累积确认_的方式,即只对按序受到的数据中的最高字节进行确认,表示到这个字节以前接收方都已经收到了。发送方即可丢掉之前的数据。 + +所以基于上面的讨论,TCP报文的首部应该包含当前发送的_字节序号_,以及_确认号_ + +### TCP可靠传输的实现 + +#### 再谈滑动窗口协议 + +发送方A需要管理一个发送窗口,里面包含当前已经发送但未收到确认的数据,以及当前可以发送的数据。同理,接收方B需要管理一个接受窗口。很明显的是,发送方的发送窗口不能大于接收方的接受窗口。 + +要实现上面那个条件,就需要接收方告诉发送方自己当前的接收窗口有多大,使得发送方可以调整自己的发送窗口。这样,我们的TCP报文的首部就又增加了一个字段:窗口大小 + +#### 超时重传时间 + +发送法在没有收到来自接收方的信息时,会对之前的数据进行重传。可是这个重传时间应该怎么选择呢?如果重传时间太短,那么会占有网络的带宽,使得网络的负荷增大。重传时间太长又会使得数据传输的效率非常低。此外,网络的情况是在不断变化的,有时比较畅通,有时又会比较拥堵,因此常数的重传时间显然是行不通的。重传时间的选择需要根据网络的情况实现自适应。 + +简单的想法是可以管理平均的往返时间(RTT, Rount Trip Time),取超时重传时间为 RTT_S + 4RTT_D, 其中RTT_S是平均的RTT值,而RTT_D往返时间的标准差。按照正态分布考虑的话,这个应该可以涵盖所有的正常情况。 + +上面的算法存在一个问题,即RTT值不好计算。这是因为如果一个报文段发送了两次,如何判断受到的确认信息是对第一个报文的确认,还是对第二次发送报文的确认? + +一个改进方法是,既然不能算,那我就不算了(?)。只要报文重传了,就不计算其RTT。但是这样的话,如果某一时间时,时延突然增大了很多,使得报文发生重传。这以后RTT时间就无法进行更新,因此报文就不断重传。 + +所以最终方案是,如果报文重传了,就不计算它的RTT,而是直接将之前的超时重传时间RTO加倍(真的简单粗暴) + +### TCP流量控制 + +首先明确一个问题:为什么TCP要进行流量控制? + +前面提到过,TCP的发送方和接收方分别会管理一个发送缓存和接收缓存。设想如果接收方的接收缓存大于发送方的发送缓存,那么发送方发送的部分数据就会被接收方所丢弃,从而不可避免地需要重传,提高了网络的负荷。所以前面说,发送缓存一定要小于接收缓存。 + +所以流量控制就是这样一个概念:让发送方的发送速率不要太快,以使得接收方来得及接受。所以很明显,这里可以直接利用前面提到过的_窗口_字段来进行流量控制--接受方向发送方发送自己当前的接收窗口的大小,从而使发送方可以调整自己的发送窗口值。 + +从上面讨论可以看到,流量控制是点对点通信量的控制,是端到端的问题。这有别于后面会提到的拥塞控制。 + +上面的利用窗口大小来进行流量控制的方法存在一个问题--设想如果某一时刻接收方将自己的接收窗口设置为零,并通知了发送方。于是发送方被禁止发送数据。这时由于某些原因,接收方又增大了自己的窗口值,但是这个新的窗口值报文在传输过程中丢失了。这以后发送方就一直在等待接收方非零窗口的通知,而接收方一直在等待发送方传来的数据。这种死锁局面将一直持续下去。 + +为了解决这个问题,TCP为每个连接都设置了一个持续计时器。只要连接的一方受到另一方的零窗口通知,就启动计时器。计时器的时间到期,就发送一个_零窗口检测报文段_,对方在确认这个报文段时给出当前窗口值。如果窗口仍是零,则重置持续计时器。若窗口不是零了,死锁的僵局就被打破了。 + +#### TCP报文段的发送时机 + +之前是提到过的,是说TCP是面向字节流的传输,他可以自己决定将多少个字节流打包发送出去。那么,究竟应该如何决定一个TCP报文的大小呢,这是TCP报文发送时机的问题。 + +存在一些比较想当然的机制: + ++ 维持一个变量,等于最大报文段长度(MSS, Maximum Segment Size)。只要缓存中的数据达到了MSS,就组装成一个TCP报文段发送出去。 ++ 基于时间的发送。发送方的一个计时器时限到了,就把当前缓存中的数据装入报文段(但长度不大于MSS),发送出去。 ++ 发送方的应用进程指明要求发送报文段。 + +上面的三种方法都有一个共同的问题:不够灵活。比如要是用户只发送很少的数据(如一个字节),使用第二三种方法传输效率会非常低,使用第一种方法会使数据迟迟得不到发送。 + +解决方案是把几种方法融合起来(就跟多级反馈队列调度算法似的),是叫Nagel算法。即: + ++ 应用进程把要发送的数据逐个字节给发送缓存 ++ 发送方把第一个数据字节发送出去,后面到达的数据都缓存起来 ++ 发送方收到对第一个字节的确认后,再把缓存中的所有数据组成一个报文段都发送出去。同时继续缓存后面到达的数据 ++ 只有受到对前一个报文段的确认后,才能发送后一个报文段(类似停止等待协议,但这是基于时间的发送) ++ 当到达的数据达到发送窗口的一半或已经达到最大报文段长度(MSS)时,立即发送(基于报文大小的发送) + +上述基于滑动窗口的流量控制还有一个叫_糊涂窗口综合征_的问题。 + +### TCP的拥塞控制 + +前面也说了,拥塞控制和流量控制是不一样的。流量控制是点对点的控制,而拥塞控制则是一个整体性的问题。简单说来,拥塞发生的条件为 + +

对网络资源的需求 > 可用资源

+ +这样的话,拥塞控制并不只是关乎某一个连接的流量控制,而是一个全局性的过程。需要协调网络中所有主机,所有路由器,以及降低网络传输性能有关的所有因素。 + +#### TCP的拥塞控制方法 + +TCP进行拥塞控制的算法有四种:慢开始,拥塞避免,快重传,快恢复。为了实现这四种算法,发送方会维持一个叫拥塞窗口的变量。发送方让自己的发送窗口等于拥塞窗口。而判断网络拥塞的依据就是出现了超时。 + ++ 慢开始 +当主机开始发送数据时,并不清楚当前网络的状态是怎么样的。较好的方法是探测一下,即由小到大逐渐增大发送窗口。 + + - 一开始,发送方将自己的拥塞窗口设置为1,发送一个报文段 + - 发送方收到对这个报文段的确认后,将拥塞窗口由1增大到2,发送两个报文段 + - 发送方没收到对一个报文段的确认将将拥塞窗口增大1。因此,没经过一个传输轮次,拥塞窗口就加倍。 + ++ 拥塞避免 + - 可以看到,采用慢开始算法后,拥塞窗口的大小呈现指数增长。为了防止慢开始算法使拥塞窗口增长太快,还需要设置一个慢开始门限。当拥塞窗口大于慢开始门限时,使用拥塞避免算法。 + + - 简单说来,拥塞避免算法就是减小拥塞窗口增大的幅度,使之缓慢增大。即没经过一个传输轮次,拥塞窗口增大1。这样,拥塞窗口就以线性规律缓慢增大。 + + - 使用慢开始与拥塞避免算法的流程为 + + 一开始将拥塞窗口设置为1,使用慢开始算法 + + 拥塞窗口以指数规律增大,达到了慢开始门限 + + 使用拥塞避免算法,使拥塞窗口缓慢增大 + + 出现了超时重传,调整门限值为当前拥塞窗口的一半,并设置拥塞窗口为1,回到了第一步 + ++ 快重传 +设想一种情况,在TCP传输过程中,某个报文段出现了超时重传,TCP因此重新开始慢开始算法。但是实际上这个报文只是在传输过程中丢失了,并不是因为网络出现了拥塞。此时采取慢开始算法反而会降低数据的传输效率。因此,需要有办法可以检测出个别报文丢失的情况。这就是快重传算法。 + + - 接收方在收到数据时要立即发送确认,不要等待自己要发送数据才进行捎带确认。 + - 在受到失序的报文段时要发送对已收到报文的重复确认。即如果接收方已经接受了M1, M2,并且发送了确认。这时没有受到M3,却收到了M4。此时,接收方应该发送对M2的重复确认。 + - 发送方在收到对某个报文的重复确认后,就知道某个报文段在传输过程中丢失了,立即进行重传。这样就不会出现超时,发送方也不会误以为出现了网络拥塞。 + ++ 快恢复 + + - 在收到对某个报文段的重复确认后,发送方知道只是丢失了个别报文段,并不是网络出现了拥塞。因此不启动慢开始,而是执行快恢复算法 + - 调整门限值为拥塞窗口的一般,同时设置拥塞窗口等于门限值 + - 执行拥塞避免算法 + +#### 主动队列管理 + +拥塞控制是一个全局的控制,需要协调网络中各个部分。所以这里考虑一下路由器的问题。 + +当网络中出现拥塞时,路由器的队列长度不足以容纳要转发的所有分组,因此会将尾部的分组丢弃,即_尾部丢弃策略_。这样的尾部丢弃会导致一连串分组的丢失,使发送方出现超时重传,这些发送法都进入了慢开始状态。这样一来,全网的通信量会突然下降很多,而网络恢复正常后,通信量又会突然增大很多。即TCP的_全局同步_。 + +为了避免全局同步,可以采取主动队列管理。主动是指路由器会主动的丢弃某一些分组,而不是被动等到队列满了以后才尾部丢弃。实现这个需要路由器维持两个参数:队列长度最小门限和最大门限 + ++ 若平均队列长度小于最小门限,则把新到达的分组放入队列 ++ 若平均队列长度大于最大门限,则把新到达的分组丢弃 ++ 若介于两者之间,则按照某一个概率随机丢掉到达的分组 + +这样,以一定概率随机丢掉新到达的分组,使得拥塞控制只在个别的TCP上进行,因而避免了全局同步的情况。 + +### TCP的连接 + +#### 连接的建立:三报文握手 + +之前谈过可靠传输的原理,依照自动重传请求,只需要两个报文就可以可靠地发送连接信息了,为什么需要第三个报文。 + +考虑一种情况: + ++ 客户端向服务器发送了两次请求报文段(ACK = 0, SYN = 1),第一次发送的请求报文段在某个网络结点长时间滞留了,因此发了第二次。 ++ 服务器收到了第二次的请求报文段,因此将客户端回送了确认连接(ACK = 1, SYN = 1)。若只需要两报文握手的话,此时连接已经建立 ++ 连接释放以后,客户向服务器发送的第一个请求报文段终于发到了服务器。服务器误以为客户端再次请求连接,因此再次回送确认连接报文。 ++ 由于客户端实际上并没有发送第二次连接请求,客户端会忽略服务器的确认报文。 ++ 但是服务器误以为连接已经建立,因此白白消耗了服务器资源。 + +因此,为了避免上面这种情况,需要客户端在收到从服务器发来的确认连接报文段后,再次向服务器发送确认。这样,在上面那种情况下,由于服务器收不到确认,就知道客户实际上并没有要求连接。 + +#### 连接的释放: 四报文握手 + ++ 在某一时刻,A和B处于TCP连接状态。若A已经没有要向B发送的数据了,因此A首先发出连接释放请求。 ++ B受到请求后即发出确认。 + +若此时就完成连接的释放,会存在一些问题--若此时B还有需要向A发送的数据,则这些数据得不到发送。因此,B发出确认后TCP的连接并没有释放,而只是释放A->B这个方向的连接。B->A方向仍然可以发送数据。此时TCP处于半关闭状态。故之后还有以下步骤: + ++ B也没有向A发送的数据后,向A发送连接释放请求。 ++ A在收到B发来的连接释放请求报文段后,对此发出确认。B收到确认后进入closed状态。 + +但是,此时TCP还没有释放掉。必须还要经过两倍的最长报文段寿命(MSL, Maximum Segment Lifetime)后,A才进入closed状态。这是由于考虑到A最后发送的这个确认报文段仍有可能丢失,如果等待两倍MSL,A就可以在收到B超时重传的连接请求报文段后及时向B重传确认。若A发送了确认报文后就进入closed状态,就无法收到B重传的连接释放请求,因而也无法重传确认报文。 + +此外,等待两倍MSL的时间,可以使本连接持续时间内所产生的所有报文段都从网络中消失,这样下一次新的连接中就不会出现这种旧的连接请求报文段。 + +TCP的连接释放还有一种问题,考虑客户和服务器建立连接后客户端因为某种原因而崩溃了,此时客户显然无法向服务器发送连接释放报文,这样服务器的资源就一直被白白占用。因此TCP还会设置一个保活计时器,服务器没收到一次客户端的数据就重新设置包活计时器。若一定时间内还没有收到来自客户的数据,服务器就发送一个探测报文段。若一连发送十个探测报文段还没有来自客户的相应,服务器就以为这个客户出现了故障,因而就关闭这个连接。 diff --git a/network/application layer.md b/network/application layer.md new file mode 100644 index 0000000..1a479c6 --- /dev/null +++ b/network/application layer.md @@ -0,0 +1,203 @@ +Conclusions on Application Layer +================================ + +## DNS + +在实际应用中,通过IP来访问是不太可能的,因为没人记得住那么多IP地址(据说曾经人们以记得住的IP地址数量,来衡量他们的学术水平)。所以就像要给变量和函数取名一样,给IP地址也可以取一个有意义的名字。这就是域名(Domian Name) + +### 域名的层次结构 + +最开始的时候,人们给IP地址取名也像给自己的孩子取名一样,反正就随便起。那时候网络还不是很多,所以问题不大。后来,互联网发展迅速,出现了很多很多IP,随便起名就容易重名,还不好管理。于是就像各个机构的科、处、室一样,出现了域名的层次结构。 + +那么问题来了,为什么IP的名字叫域名呢? + +这是因为域名的层次结构里面,每一层又称为一个域。如tongji.edu.cn中, + ++ `cn`是顶级域名 ++ `edu`是顶级域名`cn`下的二级域名 ++ `tongji`是二级域名`edu`下的三级域名 ++ 三级域名下面还可以有区分不同服务的域名,如`mail`, `www`, `dns`... + +简单说来,顶级域名(TLD, Top Level Domain)又分为国家顶级域名(nTLD)和通用顶级域名(gTLD) + ++ 国家顶级域名: 如`cn`, `us`, `uk`... ++ 通过顶级域名: + - `com`: 公司企业 + - `org`: 非盈利机构 + - `net`: 网络服务机构 + - `edu`: 美国专用的教育机构 + - ... + +一旦某个单位拥有一个域名,就可以在其下进一步划分子域名,如我国就可以自己划分二级域名为类别域名和行政区域名: + ++ 类别域名: + - `ac`: 科研机构 + - `edu` + - `net` + - `gov` + - ... ++ 行政区域名 + +而像是同济大学拥有了自己的域名`tongji.edu.cn`后,就可以在其下划分下一级的`www`域名、`mail`域名、`dns`域名等 + +最后要说明的是,域名的层次结构和IP地址的子网没有任何关系,尽管有时为了管理两者往往是匹配的。如`www.tongji.edu.cn`的IP地址并不一定是`tongji.edu.cn`对应IP下的子网。这是因为域名是按照机构的组织来进行划分的,而IP地址是按照ISP来划分的。 + +### 域名服务器的层次结构 + +我们平时网上冲浪时,都是通过域名来访问各个网站的(当然喜欢装逼直接输IP地址的除外)。那么就产生了一个问题,如何完成从域名到IP地址的解析呢? + +答案当然是域名服务器,但是这里的问题就像路由器一样,正如路由器不可能装下全互联网所有的路由表,域名服务器也不可能装下全网的域名与IP的对应关系。 + +所以这里采用的是分层次的域名服务器,而这就构成了一个联机分布式数据库系统。一般说来,域名服务器分为以下的层次: + ++ 根域名服务器: 是最高层次的域名服务器。查询根域名服务器也并不能让你知道所有域名所对应的IP地址,而只是告诉你下一步应该查询的域名服务器地址。这是因为,根域名服务器只知道所有的顶级域名服务器的域名和IP地址。 ++ 顶级域名服务器:负责管理在该顶级域名下注册的所有二级域名。查询顶级域名服务器可以返回下一步应该查找的域名服务器的IP地址。 ++ 权限域名服务器:是各个机构负责一个_区_(zone)的域名服务器。这里的_区_和_域_并不是一个概念。例如`tongji.edu.cn`是一个域,在其下可以划分多个区,像是汽车学院一个区(`auto.tongji.edu.cn`),数学院一个区(`math.tongji.edu.cn`),这些区都可以有自己的域名服务器,即权限域名服务器。很明显,这里的_区_是_域_的子集。 ++ 本地域名服务器:也叫默认域名服务器。请求DNS服务时,首先访问的就是本地域名服务器。 + +在以上层次结构的基础上,给出域名解析的过程 + ++ 主机向本地域名服务器的查询: 递归查询。即如果本地域名服务器不知道被查询的IP地址,则作为DNS客户再向其他根域名服务器发出查询报文(基于UDP)。 ++ 本地域名服务器向根域名服务器的查询: 迭代查询。即根域名服务器只是告诉本地域名服务器下一步要查询的域名服务器的IP,然后让本地域名服务器自己进行下一步的查询。 + +此外,为了提高效率,肯定有用到高速缓存的思想--本地域名服务器以及主机本地都缓存了一段时间内查询过的域名。 + +## 文件传输协议 + +文件传输协议有两个: 基于TCP的FTP(File Transfer Protocol)以及基于UDP的简单文件传输协议(TFTP, Trivial File Transfer Protocol) + +### FTP + +FTP是使用客户服务器的方式,服务器端拥有一个主进程,用于监听接受新的客户连接请求。此外还有若干从属进程,负责处理单个请求。 + ++ 主进程只负责接受新的连接请求,端口号21 ++ 接收到新的连接请求后,启动一个新的从属进程,负责处理单个请求。这里使用一个新的端口,用于建立数据传送连接。 ++ 这样,服务器和客户之间就存在了两个并行的TCP连接: 一个控制连接和一个数据连接。由于FTP使用了两个不同的端口号,因此数据连接和控制连接不会发生混乱。 + +### TFTP + +TFTP是基于UDP的文件传输协议。可以用于下面的情况: + ++ 由于UDP多播的特点,当需要将程序或文件同时向许多机器下载时往往使用TFTP ++ TFTP更简单,不需要硬盘存储程序。只需要固化了TFTP, UDP, IP的小容量只读存储器即可。 + +TFTP的工作过程很像停止等待协议,即发送完一个文件块后,须要等待确认。 + +TFTP的工作过程中也类似FTP,需要两个不同的端口。一个端口(端口号69)用于接受新的TFTP请求,之后TFTP服务器选择一个新的端口和客户进程完成通信。 + + +## `TELNET` + +TELNET是一个简单的远程终端协议。通过TELNET用户可以远程登录到另一台主机上(类似ssh,但是ssh由于有加密算法,所以更安全)。 + +TELNET使用端口号23,ssh(Secure Shell, 安全外壳协议)使用端口号22。 + +TELNET要实现将用户在本地的击键传到远地主机,同时使远地主机的输出通过TCP连接返回用户屏幕上。由于不同计算机操作系统的差异,两台计算机的控制命令等处理往往是不一样的,如不同主机对换行符的处理方式也不同。TELNET就需要适应各种计算机和操作系统之间的差异。 + +TELNET使用了网络虚拟终端(NVT, Network Virture Terminal)定义了数据和命令应该怎样通过互联网。 + ++ 客户击键时,将命令转换成NVT格式,并送交服务器 ++ 服务器将收到的数据和命令个从NVT格式转换成远地主机所需的格式 ++ 向用户返回数据是上述过程的逆过程 + +## 万维网 + +万维网是一个非常伟大的思想。上世纪60年代就有人提出万维网的思想了:创建一个全球化的大文档,文档的各个部分分布在不同的服务器中。通过激活称为链接的超文本项目,例如研究论文里的参考书目,就可以跳转到引用的论文。现在,万维网是一个大规模联机式的信息储藏所。 + +考虑要实现上面的功能,需要的一些底层接口。 + +首先,既然文档的各个部分分布在不同的主机中,我们需要一种方式来唯一定位不同主机中的不同文档以及相同主机中的不同文档(超文本)。这种方式最好是具有一定格式的,像IP地址或是dns域名一样,以便于实现。 + +在定位各个主机中的文档(超文本)后,需要有一种统一的协议来传输这种超文本。这有点类似于FTP,却又不尽相同。ftp传输的文件只是改文件本身,而传输超文本时,超文本中链接的图片等数据也需要一同传输。倘若使用ftp,传输的结果只是链接的锚(anchor),并不具有什么意义。 + +最后还有一个问题,不同的人编辑的超文本会具有不同的风格,会采用不同的文件格式。这些不同格式的文件解析方式显然是不一样的(比如你也可以.doc文件中插入超链接,这从广义来讲也是一个超文本了)。这对于万维网客户端的显示显然是不方便的。因此,万维网传输的超文本文档需要一个统一的格式。 + +此外,由于超文本文档是由用户编辑,并放在自己的服务器上(或是博客等网站上)。怎么让其他用户找到这些文档也是一个问题。 + +上面提到的几个问题,就构成了万维网的核心内容: + ++ 为了定位不同主机中的文档,万维网采用统一资源定位符(url, Uniform Resources Locator) ++ 为了传输超文本文档,万维网采用超文本传输协议(HTTP, HyperText Transfer Protocol) ++ 为了客户端可以方便地显示不同的超文本文档,万维网定义了超文本文档的格式为超文本标记语言(HTML, HyperText Markup Language) ++ 为了用户找到分布在不同地区的超本文文档,出现各种各样的万维网搜索工具。 + +### 统一资源定位符 + +为了唯一的标记互联网上不同主机的不同文件,一个很自然的想法是首先利用IP地址,唯一地标记互联网上某台主机,然后利用该主机上端口号和文件路径,来唯一标记某个文件。 + +此外,由于访问不同对象的协议不同,url还需要指定当前使用的协议类型。这就组成了url的基本格式: +

<协议>://<主机>:<端口>/<路径>

+ +实际上,不只是超文本传输协议使用url定位资源,使用ftp等其他应用程序时,也使用url来定位需要获取的资源 + +### 超文本传输协议 + +HTTP定义了万维网客户进程(浏览器)怎么向万维网服务器请求万维网文档,以及服务器如何将文档传输给浏览器。 + +HTTP是基于TCP的,这样可以做到数据的可靠传输。 + ++ 万维网服务器进程不停地监听TCP的80端口,以便发现是否有浏览器发出连接请求。 ++ 一旦建立的TCP连接,浏览器向服务器发出浏览某个页面的请求,服务器接着返回请求的页面 ++ 释放TCP连接 + +HTTP具有以下的特点: + ++ 面向事务的(抄书,不是很懂 ++ 无连接的。是说虽然HTTP下面的TCP是有连接的,但是HTTP的确是无连接的。通信的双方在不需要先建立HTTP连接的情况下就可以进行通信。 ++ 无状态的。同一个客户第二次访问同一服务器上的页面时,服务器的响应与第一次相同。因为服务器不记得这个客户(cookie除外 + +HTTP/1.0 与 HTTP/1.1 + ++ HTTP/1.0 采用非持续连接。客户端每请求一个万维网文档,需要建立连接的三报文握手(请求报文作为第三个报文的数据)以及服务器端响应的文档,共两倍的往返时间RTT。这样如果一个主页上有多个链接的对象,如一个超文本文档链接了很多图片,所有这些链接对象的下载都需要两倍RTT时间的开销。 + ++ HTTP/1.1 改进了这点,采用持续的连接。即客户端和服务器在一次响应结束后,仍然在一段时间内保持连接,这样,同一个客户可以继续在这条连接上传送后续的HTTP请求报文和响应报文。HTTP/1.1 的持续连接还有非流水线方式以及流水线方式。(显然流水线方式效率最高) + +### HTML + +(这tm是个花瓶? + +### 一些其他问题 + +万维网的请求是非常频繁的。所以可以猜到,万维网的实现中,有利用高速缓存的思想。 + +万维网高速缓存又称为代理服务器。简单说来,代理服务器可以为一个网络中的用户存储一段时间内访问过的资源,用户在访问互联网时,首先访问代理服务器,只有当代理服务器中没有存储该请求与响应,才代表用户去访问互联网中的源点服务器。 + +在校园网这种带宽受限的网络中,使用代理服务器可以使相当大一部分通信量局限在校园网内部,减小了互联网的时延。 + +## 电子邮件 + +一个电子邮件系统需要三个主要组成构件 + ++ 用户代理 ++ 邮件服务器 ++ 邮件发送协议以及邮件读取协议 + +在邮件发送的过程中,邮件服务器往往要同时充当客户和服务器。如我(使用163邮箱)要发送邮件给同济大学的邮箱(mail.tongji.edu.cn) + ++ 首先发送邮件给163邮箱的邮件服务器(端口号25) ++ 163邮箱的邮件服务器收到我的邮件,将邮件临时存在在邮件缓存队列中。等待发送给接收方的邮件服务器 ++ 163邮箱的邮件服务器作为SMTP的客户,发送邮件给同济邮箱的邮件服务器。这里,邮件不会在互联网中的某个中间邮件服务器落地 ++ 同济邮箱的服务器将收到的邮件放入收件人的用户邮箱中,等待用户读取。 + +### 邮件发送协议SMTP(简单邮件传送协议) + +使用SMTP发送邮件的过程 + ++ 建立连接。首先需要在客户端和邮件服务器之间建立TCP连接。SMTP不使用中间的邮件服务器,连接总是在发送方和接收方之间直接建立。 ++ 邮件传送。 + - 从MAIL命令开始。若服务器已准备好接收邮件,则回答'250 OK' + - 之后跟着一个或多个REPT(recipient)命令,指明收件人。若指定的收件人在接收方的系统内,则返回'250 OK'。若无此用户,则返回'550 No such user here'。其目的是为了节约通信资源,防止出现发送了很长的邮件后才知道地址错误的情况。 + - DATA命令传输邮件的内容。 ++ 连接释放: SMTP发送QUIT命令。 + +### 邮件读取协议 + ++ POP3(邮局协议, Post Office Protocol version 3)。只要用户从POP3服务器读取了邮件,POP3服务器就把该邮件删除了。因此,就会比较不方便。 + ++ IMAP协议(网际报文存取协议, Internet Message Access Protocol)。与POP3不同,是一个联机协议,用户操作邮件服务器的邮箱就像在自己的电脑上一样。只有用户需要打开某个邮件时,该邮件才传到用户的计算机上。缺点是如果用户不删除,它就一直保存在服务器邮箱中。 + +### 基于万维网的电子邮件 + +实际上,我们收发邮件时,都是使用浏览器,而没有安装特定的电子邮件用户代理。 + +而用户在浏览器中浏览各种信息时需要使用HTTP协议,因此在浏览器和互联网上的邮件服务器之间传送邮件时,仍然使用HTTP协议。只有在各个邮件服务器之间传送邮件时,才使用SMTP协议。 diff --git a/thu_dsa/chp5/chp5.md b/thu_dsa/chp5/chp5.md index 7d0b17f..991dd3a 100644 --- a/thu_dsa/chp5/chp5.md +++ b/thu_dsa/chp5/chp5.md @@ -119,7 +119,7 @@ $$ > 先序遍历递归算法。 -这个就不多说了。关键还是体会它的思想,即首先访问根节点,再访问左子树,最后再访问右子树,这个顺序递归地进行。根据这样的思想,可以构思出一种迭代进行先序遍历的方法。 +这个就不多说了。关键还是体会它的思想,即首先访问根节点,再访问左子树,最后再访问右子树,这个顺序递归地进行。根据这样的思想,可以构思出一种递归进行先序遍历的方法。 > 先序遍历迭代算法。 @@ -129,7 +129,7 @@ $$ ```cpp template template -void preOrder_It1(BinNodePosi(T) x, VST& viist){ +void preOrder_It1(BinNodePosi(T) x, VST& visit){ Stack S; S.push(x); while(!S.empty()){ @@ -182,7 +182,7 @@ void preOrder_It2(BinNodePosi(T) x, VST &visit){ > 中序遍历递归版算法。 -这个也直接略过了。反正就是说有限访问左子树,再访问根节点,最后访问右子树。通过这样的思路,要能设计出迭代版的中序遍历算法。 +这个也直接略过了。反正就是说优先访问左子树,再访问根节点,最后访问右子树。通过这样的思路,要能设计出递归版的中序遍历算法。 > 中序遍历迭代版算法。 @@ -190,7 +190,7 @@ void preOrder_It2(BinNodePosi(T) x, VST &visit){ 同样是从根节点开始,不断地沿着左子树向下走。不同的是,这里向下行进的过程中不能访问当前结点,只有俟到当前结点的左子树完成访问时,才能轮到当前结点,因此想到引入一个栈来实现延迟缓冲的功能。走到最左侧的第一个没有左子树的叶子结点时,没有左子树也相当于已经完成了左子树的访问,于是随后便访问当前结点x,然后转入到x的右子树。 -当x的右子树完成访问时,即标志着以x为根的字数访问完毕,随机访问x的父亲结点,然后访问x的父亲的右字结点。x的右兄弟结点访问完毕时,即标志着以x的父亲的根的子树访问完毕,随机访问x父亲的父亲,然后是x父亲的父亲的父亲...整个访问过程如下图所示: +当x的右子树完成访问时,即标志着以x为根的子树访问完毕,随机访问x的父亲结点,然后访问x的父亲的右子结点。x的右兄弟结点访问完毕时,即标志着以x的父亲的根的子树访问完毕,随机访问x父亲的父亲,然后是x父亲的父亲的父亲...整个访问过程如下图所示: ![中序遍历的访问次序](images/inOrder.png)