图解 | 你管这破玩意儿叫TCP?

原标题:图解|你管这破玩意儿叫TCP?
你是一台电脑 , 你的名字叫A

图解 | 你管这破玩意儿叫TCP?
文章图片
经过《如果让你来设计网络》这篇文章中的一番折腾 , 只要你知道另一位伙伴B的IP地址 , 且你们之间的网络是通的 , 无论多远 , 你都可以将一个数据包发送给你的伙伴B

图解 | 你管这破玩意儿叫TCP?
文章图片
这就是物理层、数据链路层、网络层这三层所做的事情 。
站在第四层的你 , 就可以不要脸地利用下三层所做的铺垫 , 随心所欲地发送数据 , 而不必担心找不到对方了 。

图解 | 你管这破玩意儿叫TCP?
文章图片
虽然你此时还什么都没干 , 但你还是给自己这一层起了个响亮的名字 , 叫做传输层 。
你本以为自己所在的第四层万事大吉 , 啥事没有 , 但很快问题就接踵而至 。
问题来了
前三层协议只能把数据包从一个主机搬到另外一台主机 , 但是 , 到了目的地以后 , 数据包具体交给哪个程序(进程)呢?

图解 | 你管这破玩意儿叫TCP?
文章图片
所以 , 你需要把通信的进程区分开来 , 于是就给每个进程分配一个数字编号 , 你给它起了一个响亮的名字:端口号 。

图解 | 你管这破玩意儿叫TCP?
文章图片
然后你在要发送的数据包上 , 增加了传输层的头部 , 源端口号与目标端口号 。

图解 | 你管这破玩意儿叫TCP?
文章图片
OK , 这样你将原本主机到主机的通信 , 升级为了进程和进程之间的通信 。
你没有意识到 , 你不知不觉实现了UDP协议!
(当然UDP协议中不光有源端口和目标端口 , 还有数据包长度和校验值 , 我们暂且略过)
就这样 , 你用UDP协议无忧无虑地同B进行着通信 , 一直没发生什么问题 。

图解 | 你管这破玩意儿叫TCP?
文章图片
但很快 , 你发现事情变得非常复杂......
丢包问题
由于网络的不可靠 , 数据包可能在半路丢失 , 而A和B却无法察觉 。

图解 | 你管这破玩意儿叫TCP?
文章图片
对于丢包问题 , 只要解决两个事就好了 。
第一个 , A怎么知道包丢了?
答案:让B告诉A
第二个 , 丢了的包怎么办?
答案:重传
于是你设计了如下方案 , A每发一个包 , 都必须收到来自B的确认(ACK) , 再发下一个 , 否则在一定时间内没有收到确认 , 就重传这个包 。

图解 | 你管这破玩意儿叫TCP?
文章图片
你管它叫停止等待协议 。 只要按照这个协议来 , 虽然A无法保证B一定能收到包 , 但A能够确认B是否收到了包 , 收不到就重试 , 尽最大努力让这个通信过程变得可靠 , 于是你们现在的通信过程又有了一个新的特征 , 可靠交付 。
效率问题
停止等待虽然能解决问题 , 但是效率太低了 , A原本可以在发完第一个数据包之后立刻开始发第二个数据包 , 但由于停止等待协议 , A必须等数据包到达了B , 且B的ACK包又回到了A , 才可以继续发第二个数据包 , 这效率慢得可不是一点两点 。
于是你对这个过程进行了改进 , 采用流水线的方式 , 不再傻傻地等 。

图解 | 你管这破玩意儿叫TCP?
文章图片
顺序问题
但是网路是复杂的、不可靠的 。
图解 | 你管这破玩意儿叫TCP?】有的时候A发出去的数据包 , 分别走了不同的路由到达B , 可能无法保证和发送数据包时一样的顺序 。

图解 | 你管这破玩意儿叫TCP?
文章图片
在流水线中有多个数据包和ACK包在乱序流动 , 他们之间对应关系就乱掉了 。
难道还回到停止等待协议?A每收到一个包的确认(ACK)再发下一个包 , 那就根本不存在顺序问题 。 应该有更好的办法!
A在发送的数据包中增加一个序号(seq) , 同时B要在ACK包上增加一个确认号(ack) , 这样不但解决了停止等待协议的效率问题 , 也通过这样标序号的方式解决了顺序问题 。

图解 | 你管这破玩意儿叫TCP?
文章图片
而B这个确认号意味深长:比如B发了一个确认号为ack=3 , 它不仅仅表示A发送的序号为2的包收到了 , 还表示2之前的数据包都收到了 。 这种方式叫累计确认或累计应答 。