再谈TCP

作者: | 更新日期:

几年来TCP我写了很多次了,再写一次吧。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

大家好,这里是tiankonguse的公众号(tiankonguse-code)。
tiankonguse曾是一名ACMer,现在是鹅厂视频部门的后台开发。
这里主要记录算法,数学,计算机技术等好玩的东西。

这篇文章从公众号tiankonguse-code自动同步过来。
如果转载请加上署名:公众号tiankonguse-code,并附上公众号二维码,谢谢。

零、背景

TCP协议很久之前在个人博客上就写过很多次,17年年初重新开始写公众号后也介绍过几次。
相关度最高的是什么是协议和上篇文章五层网络通信模型了。
在上篇文章里也说了,计划分四篇文章来介绍:五层网络通信模型、TCP通信,TCP网络流量控制,TCPCOPY原理。
但是后来思考了一下,话题拆分不是我的风格,这里直接把后三个话题放在这篇一下全写了,所以文章可能很长。
大部分人应该都看不完,如果计划收藏的话建议不需要收藏了,不信你现在去看看你的收藏列表,是不是大部分都没有回头看过?

这篇文章会以问题的形式展开,提出一个问题,然后回答对应的解决方案。
然后再提一个问题,再尝试回答对应的结局方案。
好了,下面开始吧。

PS: 考虑到大家对技术不敢兴趣,专业的段落我又都删了,现在文章断了,大家可以勇敢的看下去了。

一、TCP解决什么问题

在之前的通信协议中,比如IP协议,是不保证数据是否可靠达到的,相同主机中也不能区分数据是哪个服务的。
对于UDP协议,有了端口的概念,所以可以用来区分数据是谁的,但是不能保证数据可靠,发送的数据大小也有限制。

这个时候,假设没有其他协议了。
则对于应用程序,为了保证数据不丢失且可以发送大数据,需要自己实现这两个功能。

由于这两个功能是如此的重要,TCP协议就来承担这个工作了。
所以现在可以暂时理解为TCP提供了数据可靠性和发送大数据两个功能。

二、TCP怎么做到数据可靠

答案大家很容易想到。
数据发给对方了,让对方确认一下不就行了。

具体怎么确认呢?
大概是下面的样子。

A: B注意了,我给你发送了一个数据,收到了给我回复SEQ这个数字。
B: 我收到了,给,这是你给我的SEQ。

如果仅仅考虑可靠性,大概就是这个样子。

三、TCP怎么传输大数据

数据太大,底层传不了,那自然是拆分成几个小数据了。
这里就面临一个问题:怎么保证有序?

于是想到一个答案:分配有序的编号。
比如一个1万数据单位的数据,拆分成10份,分别分配1到10是个编号。

分配编号后,遇到了另一个问题:拆分的小数据在传输过程中在某些设备上还会太大,还需要拆分。
而我们编号不具备可拆分性。

于是这个编号就需要有讲究了。
既然任何大小的数据都可能被拆分,那编号只能以数据大小来标识了。
什么意思呢?

假设其实编号是10001,第一份数据大小是100,则第二个数据编号从10101开始,第三个数据从10201开始。
这样的话编号相当于数据的偏移,可以无限拆分下去,直到最小单元。

四、TCP为什么需要有握手

上面提到了传大数据时存在数据包拆分,这里就会面临一个问题。
拆分后编号多了,怎么对数据进行确认?
先来看看具体的问题场景吧。

由于传输通道中有特殊问题,导致数据包拆的特别小,出现了下面的情况。

A: B注意了,我给你发送了一个数据,其实位置SEQ是10001,数据包大小100(此时期望收到101001的确认)。
中间设备:发生特殊情况,进行拆包,拆成100个。 B: 收到了SEQ-10001,大小1个单位,回复10002收到了。
B: 收到了SEQ-10002,大小1个单位,回复10003收到了。 B: …

由于数据包可能拆分的很多,我们需要做一些优化。比如在B上收到很多包后,回复一次就行了。

但是怎么一次回复呢?
我们只能回复一个位置,但是我们不知道数据的其实位置是多少。
比如我们没有收到SEQ-10001-1单位大小,后面的99个都收到了,我们如何回复?
如果能够回复10003-101001收到了那就没问题了,但是不能这样回复。

所以我们需要一个起始位置,这个起始位置就是TCP握手。
比如下面的例子:

A: B在吗?我需要给你发数据,SEQ其实位置是10001。
B: 在的,收到你的SEQ了,是10001,你发数据吧。 A: B注意了,我给你发送了一个数据,其实位置SEQ是10001,数据包大小100(此时期望收到101001的确认)。
中间设备:发生特殊情况,进行拆包,拆成100个。 B: 收到了SEQ-10001,大小1个单位。
B: 收到了SEQ-10002,大小1个单位。
B: … B: 怎么回事?拆分了100个包。A注意了,SEQ小于101001的数据我都收到了。

传数据之前有了握手确认,后续回包就可以批量回复一次就行了。

五、TCP为什么是三次握手

上面的小节谈为什么需要握手时看到了,A向B发数据至少需要两次传输数据。
那B向A发数据呢? 当然也需要这两的流程了。

所以正常情况下建立连接应该是四次握手的。
流程像下面的样子:

A: B在吗?我需要给你发数据,SEQ起始位置是10001。
B: 在的,收到你的SEQ了,是10001,你发数据吧。 B: A还在吗?我也要向你发送数据,我随机生成的SEQ起始位置是24001。
A: 一直都在的,收到你的SEQ了,是24001。

看到上面的流程,很容易发现B的两次通信可以合并成一个,也就是下面的样子。

A: B在吗?我需要给你发数据,SEQ起始位置是10001。
B: 在的,收到你的SEQ了,是10001,你发数据吧。对了,我也要发数据,我随机生成的SEQ起始位置是24001。 A: 好的,你的SEQ我也收到了,是24001。

看到这里,大家应该知道为什么是三次握手了吧。
理论上四次握手也是可以得。

六、TCP为什么需要分手

前面说了通信前建立了一个约定链接,双方各自存在约定好的SEQ。
保存这些都是需要资源的,TCP虽然有连接是否正常的检查机制,这个不传输数据了,连接还是正常的。
所以检查是否正常机制不能来关闭不在使用的连接,这样就导致资源浪费了。
每个请求都浪费这样一个资源,那久了机器的资源肯定就会被消耗完了。

所以这里需要我们主动关闭连接。
更简单的例子是打电话,你们都不说话了,不点击中止通话,运营商还会一直维持这个连接浪费运营商的资源的,当然这个资源由你的花费来买单。

由于双方什么时候不需要纯数据是各自控制的,所以这里需要分别来告诉对方自己不需要接受或者发送数据了。

七、TCP的流量控制是什么鬼

上面只提到A把数据发给B,B把数据发给A。
数据之间是怎么传输的呢?我们可以理解为一种管道吧,就像水管那样。
简单的说就是这个管道可以传输的数据是有上限的。

既然传输的管道有资源上限,那发数据端肯定不能盲目的发数据了,管道满了发了也是失败。
所以这个发送数据需要有策略的发,这个策略就是TCP的流量控制。

目前广为使用的流量控制策略是慢启动策略。
刚开始一次发送的数据很小,然后正常收到回包了,数据量就翻倍,依旧正常,就再翻倍。
如果发现丢包了,就单个数据包大小减半,还丢包了就再减半。

看到这么暴力的策略,很多人肯定跳出来了。
我们谈这个策略之前,先看看另一个问题。

八、TCP怎么定义丢包

这个问题看着很奇怪。
发送者给接受者发数据,接受者没收到不就是丢包吗?

问题是发送者怎么知道接受者没有收到?
答案显然是没有收到接收到的回包确认。

这里面其实有一个时间的概念。
等待多久没收到回包才算丢包呢?

所以在流量控制里面,时间也是一个很重要的因素。
时间这个因素说的更专业点叫做RTT,而数据大小称为窗口。

这个RTT和窗口都是动态根据是管道的检查进行调整的。
所以这里需要一些调整公式。

于是以前的linux中就不断的测试,调得一手好参数,代码中注释者:nobody knows why, it just works。
(此处应该是一个笑脸。)

这个公式的理念是:TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每个车都应该把路让出来,而不要再去抢路了。
说的专业点是:如果网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风暴”,TCP这个协议就会拖垮整个网络。这是一个灾难。

简单来说这个公式分为四个步骤:慢启动,拥塞避免,拥塞发生,快速恢复。
慢启动前面已经提到了,窗口慢慢增加。
拥塞避免公式算是一个特殊逻辑分支,当窗口大于一个值时,就不翻倍增大窗口了,而是慢慢的增加。
拥塞发生这里大家可以简单的理解为RTT时间到了,丢包了。公式自然是窗口减半。
快速恢复指的是管道不丢包了,我们应该根据公式快速的增大窗口。

对于这个窗口与RTT的策略,由于是动态的,所以人们一直在努力寻找更优的公式。
比如后来提出了1994年的Vegas算法,1996年的FACK算法,rfc3649中的HSTCP算法,2004的 BIC 算法等等。
不过几个月前看新闻,linux内核已经大量采用了google的BBR算法,网速可以提升好几倍,值得可喜可贺。

九、TCPCOPY还有时间讲吗

没时间了,不讲了。

十、总结

好了,这里简单的介绍一下TCP的东西,由于时间关系,TCPCOPY就不讲了,就这样吧。
这篇文章为了让普通的人也看懂,可能缺乏严谨性,就这样吧。

对了现在开通了博客、公众号、算法小密圈、IT技术交流微信群。
要加微信群的可以加我微信,我拉大家进群。
比较好玩的算法放在小密圈发布。
欢迎大家加入看各种算法的思路。

长按图片关注公众号,阅读不一样的技术文章。

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越