计算机网络自顶向下方法:第三章 运输层

先根据书中的复习题巩固一遍, 最后回答常见问题TCP三次握手和四次挥手, 如有错误, 欢迎指出~

目录

点击下面跳过复习题

TCP3次握手: 为什么需要初始序号? 为什么需要3次握手, 而不是两次握手
TCP4次挥手

 

第二章: 运输层

3.1~3.3节

R1. 假定网络层提供了下列服务.

  • 在源主机中的网络层接收最大长度1200字节和来自运输层的目的主机地址的报文段. 网络层则保证将该报文段交付给位于目的主机的运输层. 假定在目的主机上能够运行许多网络应用进程.
    a. 设计可能最简单的运输层协议, 该协议将使应用程序数据到达位于目的主机的所希望的进程. 假设在目的主机中的操作系统已经为每个运行的应用进程分配了一个4字节的端口号.
    b. 修改这个协议, 使它向目的进程提供一个的"返回地址".
    c. 在你的协议中, 该运输层在计算机网络的核心中"必须做任何事"吗?

 
答:

  • a. 我设计的最简单运输层协议将包括两部分: 目的端口号和报文. 因为该协议只要使应用程序数据到达位于目的主机所需要的进程就可以了, 它将会在下发到源主机的网络层时加上目的主机的IP地址, 保证了主机之间的逻辑通信. 到达目的主机的网络层后会把该运输层报文段提取上交到运输层, 运输层根据目的端口好在本机的网络进程中找到有相同的端口号的目的进程, 并交付应用数据.
  • b. 为了提供让目的进程返回的地址, 在需要加上一个源端口号字段. 这样目的进程就能通过把源端口号设置为目的端口号, 进而向源进程传输数据. (再加上一些其他字段就是个UDP协议了)
  • c. 它并不需要做"任何事", 目前它只提供交付数据的功能, 不具备诸如拥塞控制, 确保数据完整性等功能.

 

R2. 考虑有一个星球,

  • 每个人都属于某个六口之家, 每个家庭都住在自己的房子里, 每个房子都有一个唯一的地址, 并且某给定家庭中的每个人有一个独特的名字. 假定该星球有一个从源家庭到目的家庭交付信件的邮政服务. 该邮件服务要求: 1. 在一个信封中有一封信; 2. 在信封上清楚地写上目的家庭的地址(并且没有别的东西). 假设每个家庭有一名家庭成员代表为家庭中的其他成员收集和分发信件. 这些信件没有必要提供任何有关信的接收者的提示.
    a. 使用对上面复习题R1的解决方案作为启发, 描述家庭成员代表能够使用的协议, 以从发送家庭成员向接收家庭成员交付信件.
    b. 在你的协议中, 该邮政服务必须打开信封并检查信件内容才能提供它的服务吗?

 
答:

  • a. 这个问题考察的是运输层协议与上层应用层和下层网络层之间的关系. 下面描述一次收发邮件的过程.
场景 映射
每个家庭住自己的房子, 房子有唯一地址 每一台主机都有自己的IP地址
家庭中每个人都有自己独特的名字 主机中运行的每个进程都有自己唯一的端口号
家庭A一名成员代表家庭A收集全家庭的信件 某个运输层协议, 通过套接字从应用进程获取应用报文
家庭A代表将收集的所有邮件交给邮政服务 运输层协议将运输层报文段交付给网络层 (多路复用)
信封上除了目的家庭地址没有别的东西, 邮政服务把邮件传输到目的家庭B 网络层会将报文段与目的地址IP封装成数据报, 进行主机之间的网络传输
家庭B的代表从邮政服务获得邮件 目的主机的运输层从网络层中接收数据, 抽取出报文段
家庭B的代表把邮件分发给自己家里的成员 目的主机的运输层协议把数据通过套接字上交给应用进程 (多路分解)
  • b. 邮政服务不需要打开信封并检查信件内容, 因为它只负责把邮件从一个家庭住址传送到另一个家庭. 它并不关心信件的内容. 好比网络层所提供的服务, 它会将传输层报文段封装起来, 报文段的具体内容与它无关.

 

R3. 考虑在主机A和主机B之间有一条TCP连接.

  • 假设从主机A传送到主机B的TCP报文段具有源端口号x和目的端口号y. 对于从主机B传送到主机A的报文段, 源端口号和目的端口号分别是多少?
  • 答: 源端口号将设置为y, 目的端口号设置为x.

 

R4. 描述应用程序开发者为什么可能选择在UDP上运行程序而不是在TCP上运行的原因.

  • 这个问题问的是在什么情况下, UDP的优点明显盖过TCP的缺点.
  • UDP协议是一种极为简化的运输层协议, 它不提供不必要的服务, 大概就是在IP协议上加上源和目的地的端口后等信息, 所以它可以随时地, 以任何速率向其他端系统发送数据.
  • TCP协议有许多优良特性, 它确保数据完整性, 提供拥塞控制, 但这些特性会增加端到端通信的时延.
  • 如果一个应用程序需要提供实时服务, 而且能够容忍一定的分组丢失, 那么UDP协议是更好的选择.

 

R5. 在今天的因特网中, 为什么语音和图像流量常常是经过TCP而不是经UDP发送.

  • (提示: 我们寻找的答案与TCP的拥塞控制机制没有关系)
  • 答: 为了确保数据的完整性, 分组丢失可能会对语音和图像质量产生影响.

 

R6. 当某应用程序运行在UDP上时, 该应用程序可能得到可靠的数据传输吗? 如果能, 如何实现?

  • 可以的.
  • 需要通过应用层协议实现. 比如谷歌的Chrome浏览器中所使用的QUIC协议在UDP之上的应用层协议中实现了可靠性.

 

R7. 假定在主机C上的一个进程有一个具有端口号6789的UDP套接字.

  • 假定主机A和主机B都用目的端口6789向主机C发送一个UDP报文段. 这两台主机的这些报文段在主机C都被描述为相同的套接字吗? 如果是这样的话, 在主机C的该进程将怎样知道源于两台不同主机的这两个报文段?

 

  • 答: 这两台主机的这些报文段在主机C会被描述为相同的套接字. 因为在传输UDP包的时候, 网络层会附带上源和目的的IP地址的, 主机C的程序可以通过不同的源IP地址判别.
  • 毕竟主机A和B在选端口的时候不知道彼此具体会选什么, 肯定会有选用一样端口号的情况, 主机IP能把它们区分开.

 

R8. 假定在主机C端口80上运行一个Web服务器.

  • 假定这个Web服务器使用持续连接, 并且正在接收来自两台不同主机A和B的请求. 被发送的所有请求都通过位于主机C的相同套接字吗? 如果它们通过不同的套接字传递, 这两个套接字都具有端口80吗? 讨论和解释之.

 

  • 答: 这里有个巧妙的关系为题目带来歧义.
  • A和B的请求会通过80端口找到服务器进程, 就这里而言它们通过为与C的相同套接字, 这个套接字具有端口80.
  • 当它们与服务器进程建立连接的时候, 服务器进程会单独为它们分配套接字, 通过专门的套接字响应客户端的请求. 这两个套接字就不具有80端口了.

 

R9. 在我们的rdt协议中, 为什么需要引入序号?

  • 如果不引入序号会有什么问题? 描述: 一个初始的rdt停等协议是这样的: 发送方发送一个分组, 发送方进入等待状态, 不能从上层接收分组, 接收方接收到分组后如果分组正常则回复ACK, 否则回复NAK, 发送方接收到ACK则回到初始状态等待上层调用, 如果收到NAK则重传该分组继续等待.
  • 问题来了: 如果接收方的ACK, NAK分组在传输过程中受损, 发送方该如何处理? 最简单实用的方法就是重传了, 也就是当发送方不确定接收方是否收到分组就把分组重新发送一遍.
  • 但是重发分组会带来新的问题, 接收方在接收到重发的分组时, 它并不知道这是一个新的分组还是一个重传的分组(对上一个分组已经确认过了).
  • 于是便引入序号, 这里的序号用一个比特位表示0和1就能解决. 加入当前发送方发送带有0序号的分组, 那么接收方会进行响应. 发送方如果接收到损坏的确认分组, 那么它重传一次带有0序号的分组. 否则传送带1序号的下一个分组. 接收方根据分组的序号进行判断, 如果当前分组的序号和上一个接受到的分组的序号相同, 说明这是一次重传, 如果不相同说明是一个新的分组.
  • 以上基于一个简单的停等协议进行描述.

 

R9. 在我们的rdt协议中, 为什么需要引入定时器?

  • 在上面一题中了解引入序号是为了解决分组在传播过程中因分组受损, 发送方重传分组而带来的冗余分组问题. 但是在因特网中, 分组除了会受损, 还可能丢失. 这里探讨该通过一个怎样的机制解决分组丢失问题.
  • 如果发送方发送的分组或者接收方响应的确认在传输过程中丢失, 发送方将收不到确认. 解决这问题的办法仍是重传分组, 但是应该在什么时候进行重传是值得商榷的. 网络中的延时具有非常大的不确定性, 如果等待足够大的时延才重传分组显然会降低效率. 应该定一个固定的时间, 只要过了这个时间就认为分组丢失(尽管可能没有丢失).
  • 这里便引入了定时器, 在每传输一个分组时开启一个定时器, 而且让发送方响应定时器计时后产生的中断, 还有要关闭计时器的机制.

当集齐检验和, 序号, 定时器, 肯定和否定确认分组这些技术后, 一个基本的可靠数据传输协议已经构建好了.

 

R10. 假定发送方和接收方之间的往返时延是固定的并且为发送方所知. 假设分组能够丢失的话, 在协议rdt3.0中, 一个定时器仍是必需的吗? 试解释之.

  • 如果发送方知道了双方固定的往返时延, 那么就可以不需要定时器了. 因为定时器的存在就是为了估计一个双方的往返时延值, 超过了就进行重传. 现在知道具体且固定的往返时延, 那么就可以准确地得出接受到确认分组的时间, 如果超过了该时间还没有接收到确认就进行重传.

 

R12. 在配套网站上使用Go-Back-N(回退N步)Java小程序.

a. 让源发送5个分组, 在这5个分组的任何一个到达目的地之前暂停该动画. 然后毁掉第一个分组并继续该动画. 试描述发生的情况.
b. 重复该实验, 只是现在让第一个分组到达目的地并毁掉第一个确认. 再次描述发生的情况.
c. 最后尝试发送6个分组. 发生了什么情况?

 

R13. 重复复习题R12, 但是现在使用Selective Repeat(选择重传)Java小程序. 选择重传和回退N步有很么不同?

 

  • 上面的题目由于无法访问到该小程序暂时跳过, 但是下面的补充会覆盖掉上面两个问题的知识点.

补充:

回退N步

  • 在R11过后已经基本建立起了一个可靠数据传输协议. 但是它传输分组的形式相当于串行传输, 对链路的利用率极低. 可不可以在等待收到确认分组时继续发送分组?
  • 答案是可以的. 但是会引入新的问题, 这样像流水线一样地发送分组, 如何处理丢失, 损坏及延时过大的分组?
  • 解决流水线的差错恢复有两种基本方法是: 回退N步和选择重传.
  • 回退N步从字面上是很好理解的, 先发送一个分组a, 在启动计时器后陆续发送b, c, d ... n, 如果a分组在传输过程中出现了问题, 那么就从a开始重新传输n个分组.
  • 口头描述一下回退N步的流程, 下面的FSM图描述的更清晰. 首先会发送base序号的分组, 并启动计时器, 然后继续按序发送窗口内的分组, 如果到达了窗口的边界base + N - 1处的分组就停下来, 也就是说发送方需要维护发送窗口的上下边界. 而接收方接收到base分组后会根据base分组的序列号响应回去, 因此接收方只需要维护一个记录分组n被接收到的变量.
  • 当base分组的计时器计时完还没有收到确认, 认为出现分组丢失, 这时不管窗口有边界扩到哪里, 都从base开始重传分组. 所以如果接收方接在收到base分组之前收到后面的分组(失序), 可以直接把失序的分组丢弃掉, 不需要缓存, 因为发送方必定会重传一次.
  • 从这也能得出发送方与接收方都是采用累计确认的方式处理分组.

 

选择重传

  • 在引入选择重传之前, 我们先看看回退N步协议会带来什么问题. 显然回退N步是一个在流水线式分组传输下一个可靠的协议, 但是如果窗口比较大, 当base分组丢失后, 后面所有的分组都要进行重传, 这会不会比较浪费呢? 考虑每个分组都丢失一下, 那么重传的次数将达到二次方的数量级.
  • 可不可以哪个分组丢失了就重传哪个分组, 而不是重传全部呢? 可以的, 这就引入了选择重传协议. 在理解了回退N步后选择重传应该不是个大问题.

 

  • 对于发送方, 在send_base分组被确认后它窗口才会往左移动, 移动的长度取决于send_base分组旁边有多少个连着的已经确认的分组(选择重传, 允许失序的分组被确认后缓存起来, 等待被确认).
  • 对于接收方, 当接收到rcv_base分组后才会移动窗口, 移动的长度也取决于旁边有多少个连着的失序(已缓存)但未被确认的分组.
  • 从这能看出, 发送方与接收方的窗口并不是同步移动的. 这里会引入一个大问题. 见下图:

 

  • 上图描述的问题是现在分组的序列号的取值范围是0~3, 也就是大小为4(之前讲过0~1的), 窗口大小为3.
  • 假设发送方发送了分组0, 1, 2, 发送方全部接收到, 发送方的窗口移动3格, 等待3, 4, 5分组.
  • 不巧的是0, 1, 2三个分组的ACK分组都丢失了, 发送方将重传0, 1, 2三个分组.
  • 这时问题就来了, 假设0, 1, 2分组对应的序列分别为0, 1, 2. 但是4, 5分组对应的序列号也为0, 1. 为了确保发送方的窗口能够滑动, 接收方会缓存rcv_base - N的分组(已经确认), 以便再次收到这个范围上的冗余分组时能够发送一个对应的ACK.
  • 这时发送方发送的分组0带有序号0, 而接收方的既期待分组4(序号为0), 又缓存有分组0(序号为0), 接收方将不知道收到的是分组0还是分组4.
  • 显然, 如果窗口的长度比序号空间小1时, 可能出现接收方无法确定接受到的分组是一次重传还是一个新的分组. - 书上给出的窗口取值范围为: 窗口长度必须小于或等于序号空间大小的一半.

 

继续是复习题

3.5节

R14. 是非判断题

a. 主机A经过一条TCP连接向主机B发送一个大文件. 假设主机B没有数据发往主机A. 因为主机B不能随数据捎带确认, 所以主机B将不向主机A发送确认.
答: 错误. 主机B将不向主机A发送确认这句话违背了一个可靠数据传输协议的基本原则. 首先要明确为了确保可靠的数据传输, 接收方向发送方发送确定报文是协议的一部分. 再分析推导出这一错误结论的原因: 主机B不能随数据捎带确认. 书本上提出捎带的概念时, 给的是一个具体场景, 发送方要发送一个字符, 接收方要返回该字符进行回显, 所以顺便把确认信息放入到发给发送方的数据的报文段中. 要区分清楚确认和捎带确认之间的关系.

b. 在整个连接的过程中, rwnd的长度决不会变化.
答: 错误. rwnd = RcvBuffer - [LastByteRcvd - LastByteRead]. rwnd表示的是接收窗口的大小, 这取决于接收方应用程序从缓存中读取数据的速率和发送方发送的速率, 是会变化的.

c. 假设主机A通过一条TCP连接向主机B发送一个大文件. 主机A发送但未被确认的字节数不会超过接收缓存的大小.
答: 正确. 讲的就是TCP的流量控制.

d. 假设主机A通过一条TCP连接向主机B发送一个大文件. 如果对于这条连接的一个报文段的序号为m, 则对于后继报文段的序号将必然是m + 1.
答: 错误. 序号是根据TCP数据的字节流决定的, 而不是建立在报文序列之上. 后继报文段的序号应该是m + n, 而n是最大报文段长度.

e. TCP报文段在它的首部中有一个rwnd字段.
答: 错误. TCP报文段有接收窗口字段, rwnd存放在接收窗口字段中.

f. 假定在一条TCP连接中最后的SampleRTT等于1秒, 那么对于该连接的TimeoutInterval的当前值必定大于等于1秒.
答: 正确. TimeoutInterval = EstimatedRTT + 4 * DevRTT.

g. 假设主机A通过一条TCP连接向主机B发送一个序号为38的4个字节的报文段. 在这个相同的报文段中, 确认号必定是42.
答: 错误. 概念性错误, 确认号是期待接收方发送的序号.

 

R15. 假设主机A通过一条TCP连接向主机B发送两个紧挨着的TCP报文段. 第一个报文段的序号为90, 第二个报文段序号为110.

a. 第一个报文段中有多少数据?
答: 110 - 90 = 20个字节.

b. 假设第一个报文段丢失而第二个报文段到达主机B. 那么在主机B发往主机A的确认报文中, 确认号应该是多少?
答: 90. 在建立连接的时候主机B就知道要先接收90.

 

R16. 考虑在3.5节中讨论的Talnet的例子.

  • 在用户键入字符C数秒之后, 用户又键入字符R. 那么在用户键入字符R之后, 总共发送了多少个报文段, 这些报文段中的序号和确认字段应该填入什么?
  • 答: 由于用户在键入C数秒后键入R, TCP没有断开连接. 所以键入R后有用户到服务器和服务器到用户两个报文段. 用户到服务器: 序号44, 确认80, 服务器到用户: 序号80, 确认45

 

3.7节

R17. 假设两条TCP连接存在于一个宽带为Rbps的瓶颈链路上.

  • 它们都要发送一个很大的文件(以相同方向经过瓶颈链路), 并且两者是同时开始发送文件. 那么TCP将为每条连接分配什么样的传输速率?
  • 答: 这里涉及到2条TCP连接的公平性问题, 很难保证两条TCP连接分配均等的传输速率. 但是2条TCP连接的速率之和是会在R/2~R之间浮动的. 至于哪条快一点, 哪条慢一点是不确定的.

 

R18. 是非判断题. 考虑TCP的拥塞控制. 当发送方定时器超时时, 其ssthresh的值将被设置为原来值的一半.

  • 错误. 当发送方定时器超时时, TCP发送方将cwnd设置为1并重新开始慢启动过程. 它还将第二个状态变量的值ssthresh设置为cwnd/2, 即当检测到拥塞时将ssthresh置为拥塞窗口值的一半.

 

R19. 在3.7节的"TCP分岔"讨论中, 对于TCP分岔的响应时间, 断言大约是4 * RTT(FE) + RTT(BE) + 处理时间. 评价该断言.

  • 我认为该断言是合理的. 它给出的是一条包含重要时延参数的公式. 在实际情况中, 某些参数可能可以省略, 但是在省略之前还是要经过考虑的.

 

插入对TCP协议的总结

  • 3.4节讲的是如何从零创建起一个可靠高效的数据传输协议, 而3.5节基于3.4节的基础具体讲述TCP的实现, 这里有必要对TCP协议进行一个简单的总结.

 

TCP三次握手

> 在讲述三次握手之前先看看TCP报文段结构

 

  • 对于三次握手问题, 我们暂时不考虑报文段中的其他字段, 只看报文段中序号和确认号字段. 对这两个字段有印象后, 下面开始描述一次完整的TCP三次握手过程.
  • 故事开始...
  1. 第一步: 客户端TCP向服务器端的TCP发送一个特殊的TCP报文段, 该报文段不包含应用层数据. 报文段首部中的标志位SYN置1, 简称为SYN报文段. 同时客户端随机选取一个初始序列号client_isn, 放置于SYN报文段的序号字段中, 最后把该报文段经下层封装发送给服务器. SYN的意思是: xxx服务器, 我想向你发起TCP连接, 我的初始序号为client_isn.
  2. 第二步: 服务器收到SYN报文段后, 响应一个SYNACK报文段. SYNACK报文段的SYN标志位置1, 确认号字段设置为client_isn + 1, 序号字段由服务器选择自己的初始序号server_isn. SYNACK报文段的意思是: 我收到了你的SYN报文段, 序号为client_isn, 我同意该连接, 我自己的序号为server_isn.
  3. 第三步: 客户端接收到SYNACK后要告知服务器自己收到了. 于是发送最后一个报文段, SYN标志位置0, 把确认字段设置为server_isn + 1, 并设置自己的序号. 这个报文意思是: 好的, 我知道你同意了, 我们开始传输数据吧.

 

在解释序号和确认号之前先解释一下为什么是三次握手而不是两次握手.
  • 其实如果上面的过程理解了, 就能回答这个问题了, 不过这里有个漫画帮助理解.
  • 先演示的是三次握手.

 

  • 接着演示两次握手.
明白了为什么是三次握手而不是四次挥手后, 再来看TCP报文段中的序号和确认号.
  • 序号:
  • 序号是建立在传送的字节流之上, 而不是建立在传送的报文段的序列之上的. 意思是说如果把传输的数据看作是一个字节文本, 序号则是对文本的首字节进行编号.
  • 我知道还是很抽象, 看具体例子. 现在要通过TCP发送一个500000字节的大文件, 最大报文长度为1000(一个报文段最多只能装1000个字节). 那么这次TCP传输就要分为500个报文段. 第一个报文段序号为0, 因为在500000字节中首字节的序号为0; 第二个报文段的序号为1000, 因为第一个报文段装了1000个字节, 第二个报文段从第1000个字节开始装起; 同理, 第三个报文段的序号为2000...
  • 为什么要使用序号?
  • 发送序号是为了告诉接收方, 下一次我将从哪个地方开始传数据给你. 接收方同时也会期待下次从下一个字节序列的位置开始接收发送方的数据. 接收方会把这个位置写到确认号中, 比如上面的例子, 接收方在接收到0序列分组后, 会在确认字段填入1000, 下一次期待接收1000位置的字节.
  • 由上可见: 主机A填充进报文段的确认号是主机A期望从主机B收到的下一字节的序号.

 

TCP四次挥手

- 四次挥手指的是TCP的两端断开连接时的四次报文段传输.

 

  1. 首先客户端TCP向服务器发送一个特殊的TCP报文段, 其中FIN标志位被置1.
  2. 服务器收到该报文段后就向发送方发送一个确认报文段.
  3. 然后服务器发送自己的终止报文段, 同样是把FIN位置1.
  4. 最后客户端对服务器的终止报文段发送确认响应.
  • 两次挥手行不行? 就是客户端提出关闭, 服务器响应后TCP就结束.
  • 答: 不行, 因为客户单方面提出关闭的话, 服务器还是可以向客户端发送数据, 必须双方都提出关闭并得到确认后TCP连接才算关闭.

往期推荐:

  1. 写代码解释什么是api,什么是sdk
  2. 飞机大战小游戏全制作过程分享
  3. 仿flappy bird小游戏制作分享
  4. 如何开始编写技术博客?markdown语法入门,分享使人进步
  5. 请求参数、表单参数、url参数、header参数、Cookie参数有什么区别?
posted @ 2019-04-08 22:51  胡涂阿菌  阅读(12197)  评论(0编辑  收藏  举报