ant命令

TCP是一个比较复杂的协议,包含的内容很多,面试也很常见。很多新手对这种面试很迷茫,不知道怎么开始,不知道从哪里开始。他们拿起TCP/IP就找不到重点,看了两页就困了。

为了解决大家的困惑,我花了两天时间帮大家梳理了作为开发者应该重点学习哪些TCP知识。当然,因为只有一篇,不可能面面俱到,不然就爆了!所以,我选择了重点来介绍。

关于参考书:

TCP/IP 详解卷一(作者:W.Richard Stevens)TCP/IP 协议簇(作者:Behrouz A.Forouzan)

是的,你可以在上面两本书里找到这篇文章的几乎所有内容(实验除外)。当然,我不是在抄书。那有什么意义?这篇论文的目的是整理,就像老师画的重点一样,让你有了复习的方向,尤其是对于那些时间紧迫,却没有时间把书完整看完的同学。

提示:

在学习之前,请确保自己有 >2 小时的连续空闲时间,走马观花,很难消化哦!准备一台 Linux 服务器,如果没有云主机,那就自己安装一个 Linux 虚拟机来做服务器吧。准备好本地 Linux 客户机,虚拟机创建一个就行。

接下来,让我们拿起大刀——TCP dump,开始TCP之旅。

1. 三次握手和四次挥手

在这里,我打算使用三次握手和四个波形来熟悉tcpdump工具。您将在以后多次使用这个工具。没用过的同学一定要认真看,工作中你会用到,尤其是分析网络数据传输异常的时候!

ant命令

图1 三次握手与四次挥手图1三次握手和四次挥手。

上图是我用nc命令和tcpdump抓包工具演示的一次三次握手四次挥手的过程。这个实验很简单,只要你有一台运行Linux或者MacOS的主机,就可以很轻松的进行这个实验。实验步骤如下:

Step.1 打开三个终端。Step.2 下面窗口输入 sudo tcpdump -# -S -n -i lo0 tcp and host 127.0.0.1 and port 8000 启动抓包程序。Step.3 左上窗口输入 nc -l 8000 表示在端口 8000 启动一个 TCP 服务程序。Step.4 右上窗口输入 nc localhost 8000 表示向 localhost:8000 这个地址发起 TCP 连接请求。

毫不奇怪,您可以在下面的窗口中看到从三次握手中捕获的消息。接下来,你可以顺便观察四次挥手的过程。步骤如下:

Step.5 右上窗口按下 CTRL C 组合键,退出客户端程序。

一旦退出客户端,可以看到四次挥手的过程。如果你有阿里云或者腾讯云的服务器,那就更好了。你可以用nc命令在你的服务器上启动一个TCP服务器,用nc命令本地连接,这样更真实!

好了,我们来简单分析一下tcpdump命令的参数含义和消息的含义。

1.1 tcpdump

Tcpdump是一个类Unix环境中的包抓取工件。它默认安装在您的Linux或MaxOS系统上。它可以很容易地抓取网卡上的数据包,并根据您指定的参数进行过滤。在上述实验中,每个参数的含义如下:

有关更多参数,可以使用man tcpdump查阅文档。它的文档非常详细,你可以找到关于tcpdump的一切。另外,Linux上的MacOS和tcpdump有一点区别,但是这些影响并不大。

1.2 报文含义

结合图1,分析每个包的含义。注意这里用ack表示标志位,小写ACK表示序列号。另外,C代表客户端,S代表服务器(使用nc -l 8000的那个)。

上表显示了图1中的简化消息,其中提取了一些关键数据。

在 tcpdump 中,标志位都放在 [] 中,比如 [S.],其中 S 就表示 SYN,F 表示 FIN,而 . 号表示 ACK。可以看出,除了第一包握手报文外,其它所有报文都带有 ACK 标志。带有 ACK 标志的报文,表示收到了 ack-1 号报文,并且接下来期望收到对方序号为 ack 的报文。第 4 包是一个重传报文,很容易发现,这一包啥也没干,就是把 ACK 重传了一遍。由于在实验里,我们先退出的客户端,因此是客户端先断开连接,因此客户端发送 FIN 到服务器端(对应的第 5 包)。

关于三次握手,后面第5节会有更重要的内容!这里只是让你习惯tcpdump工具,放松一下。

2. Delay ACK

接下来的事情就好玩了,这是很少有人提的一件很重要的事情,叫做Delay ACK。不管怎样,这到底是什么?重要吗?事不宜迟,我们先做个实验。

图2延迟确认

我们来看一个发生在网上的例子。这次我的服务器位于腾讯云上。建立TCP连接后,我从客户端向一个名为mars的服务器发送了四次数据。第一次发字母A加字节回车\n,第二次发字母B加回车\n,后面是C和d。

Echo.go是我用golang写的一个简单的TCP服务器。默认情况下,这个服务器除了接收数据什么都不做,就像第2节中使用的nc命令一样,但是echo.go接收到的数据直接被丢弃,甚至不显示在屏幕上。

如果你还没有安装golang编译器,我给你编译了一个echo,放在代码库中,你就可以直接运行了。但是,强烈建议您自己安装golang。请参考文末。我没有在这里贴链接,因为我希望你不要现在就尝试安装,然后在你看完文章后再做。

这次抓包程序tcpdump运行在服务器上,因为我想观察服务器如何回复ack段。

这次不分析三次握手和四波。从图2看太简单了吧?重点是服务器四次接收数据的行为,正好对应TCP消息4、5、6、7、8、9、10、11。同样,这里我们用表格来记录一些关键信息。

一些观察到的现象:

客户端发送 a(第 4 包)到服务器后,服务器立即返回了 ACK(第 5 包) 到客户端,没有经过任何等待。客户端发送 b(第 6 包)到服务器后,服务器没有立即返回 ACK,而是等待了约 40ms 才返回 ACK(第 7 包)到客户端。第 8、9、10、11 包也是一样。

这里你需要注意的问题是,服务器收到数据后,为什么有时会等待40ms而不是立即返回一个ACK?很好,希望你能看到这个现象,这不是巧合,而是TCP的一个特点,一种机制,就是Delay ACK,也就是延迟确认。

为什么我没有继续做一个使用nc的服务器?因为在我的Linux系统上,这个机制默认是关闭的。如果使用nc命令,可能看不到这种现象,所以我用golang写了一个简单的TCP服务器,打开了Delay ACK的机制。当然,我希望你可以在你的服务器上使用数控工具来尝试一下。您可能会看到,也可能看不到,这取决于您机器的内核版本。

TCP为什么要引入这个机制?目的是减少网络中TCP段的数量。以前带宽挺贵的,现在不便宜了。您知道,一个TCP报头至少需要20个字节(稍后我会帮助您整理TCP报头字段),通过引入Delay ACK,您可以做两件事:

累积确认:服务器极有可能在这 40ms 里又收到了客户端发送过来的多个 TCP 报文段,40ms 后就可以对这些报文进行累积确认,也就是只返回一个 ACK 报文就行。这样就能减少网络中 ACK 报文的数量。捎带确认:40ms 里,服务器也可能会返回数据给客户端,如果服务器有数据返回给客户端,那不如把这个 ACK 连同数据一起返回给客户端吧,等一下下是值得的。就好比你要出门和朋友吃饭,但是你女朋友可能也想和你一起去,然而你女朋友要化妆什么的,速度非常慢,于是你想了个策略,你等她 30 分钟,如果 30 分钟内她搞定了,你就带她一起去,如果她搞不定,你就不带她去了。(什么?程序员不可能有女朋友,其实男朋友也可以的^_^)。

好了,我们来说说echo是如何开启延迟确认机制的。很简单。在man手册里查man7TCP,可以看到一些关于TCP机制的文档,其中一个就是tcp pquickack。

图3 TCPQUICKACK

但是这个选项的名字和Delay ACK的意思是相反的。这意味着如果你想打开延迟确认,你必须设置这个选项为假。Linux提供setsockopt系统调用来帮助你设置。可以使用man setsockopt来查找这个函数。

int opt = 0;setsockopt(sockfd,IPPROTO_TCP,TCP_QUICKACK,&opt,sizeof(int));另外,你需要在每次recv数据后调用这个设置函数,因为man手册中明确说明了这个选项设置不是永久的。

在一些较低版本的内核中,延迟确认机制是默认开启的!但是你需要自己做实验。

3. Nagle

Nagle也是TCP协议中常用的算法,在采访中也经常被问到。Nagle算法的目的也是为了减少网络中TCP段的数量。你看,为了减少网络中的消息段数量,TCP协议做了很多机制,包括上一节学习的Delay ACK。)

Nagle算法的发明者John Nagle当时发明了这种算法,主要是为了解决福特汽车公司的网络拥塞问题。

Nagle算法的原理相当简单:

一个 TCP 连接上最多只能有一个未被确认的未完成的小分组,在它到达目的地前,不能发送其它分组。在上一个小分组未到达目的地前,即还未收到它的 ACK 前,TCP 会收集后来的小分组。当上一个小分组的 ack 收到后,TCP 就将收集的小分组合并成一个大分组发送出去。

上面的包只是一个TCP段,只有一个意思。但值得注意的是,Nagle算法关注的是小群体,即不关心大群体。分组有多小?一个字节?两个字节?一般来说,只要数据量小于MSS,就是小包。MSS的大小是在三次握手的时候协商的。不信你回去看看图1或者图2(虽然图1的MSS大小有点过分,毕竟是环回网卡上的消息)。

为了验证Nagle算法的存在,我们再做一个实验。

3.1 实验一(观察 Nagle 算法的存在)

这个实验似乎不那么容易做。如何在极短的时间内发送多个小包?可以继续使用nc命令吗?第一次发送a,第二次输入B回车,第三次输入C回车发送出去。可惜就算单身30年,手速也不能超过100ms。在你进入B之前,A的ACK已经收到了,所以如果你做这个实验,你永远看不到Nagle是如何合并小群体的。

这种情况下手速不够快,C/C++写起来太麻烦。我们直接用Python来学习网络编程的神器,方便快捷。

开三个窗口,一个抓包,一个服务器,还有一个客户端,写 Python 脚本。Python 只要写 4 行就行了,重点在于使用 for 循环连续调用 5 次 send 的过程。

图4是实验的结果。具体代码我就不贴了,你可以自己打,记得更清楚。

图4 Nagle算法观察

3.2 结果分析

简单看一下图4中的结果。

第 4 包是第一次调用 send 发送的数据,只有一个字节的 a,中间经过了约 30ms 后,收到了第 5 包,也就是 ACK 报文。第 6 包是后 for 循环的后 4 次合并的数据,一共是 4 个字节,即 aaaa,一次全部发送出去了。所以可以看出来,Nagle 算法默认就是开启的。后面我们要想办法把它关闭。

实验很简单,验证Nagle算法非常容易。它确实存在,不是很幸福吗?但是在服务器开发中,我们通常会想关掉Nagle算法,因为现在互联网技术这么发达,网速够快,也没那么拥堵。开启Nagle会影响程序的响应速度。

如果此时对方再次启动延迟ack机制,发送方收到ACK的时间会延迟40 ms(延迟ACK简直就是猪队友),这在某些场景下几乎是不可接受的。想想你玩王者荣耀的时候,就是在和时间赛跑。从60ms改成100ms可能是个人问题。

3.3 实验二(关闭 Nagle)

实验二自然是关闭Nagle算法,很简单。只需设置TCP_NODELAY选项,继续在刚才的Python终端中输入命令s . setsockopt(socket . IP proto _ TCP,socket.tcp _ nodelay,1),然后连续发送5次消息。

图5关闭Nagle

从上面的结果可以看出,已经有了非常明显的变化。第8到第12个数据包显然是直接从客户端发送的,没有合并消息。总包数(8号~ 17号报文)比开Nagle(4号~ 7号报文)多6。如果忽略那1个字节的数据,只看TCP头的20个字节,也就是网络中多了120个字节。

从这一点来看,我们可以发现Nagle对网络非常友好,关闭Nagle可能会造成网络拥塞(网络充斥着小数据包)。

3.4 Nagle 算法的影响

我们来看一个实际场景(这个例子来自Unix网络编程这本书)。假设你设计了一个基于TCP协议的应用层协议。该协议共有400个字节的数据,其中前4个字节为消息类型,其余396个字节为数据。

如果客户端发送两次数据,第一次发送4个字节的数据类型,第二次发送396个字节的数据,即连续两次调用send函数。服务器收到了这4个字节,但是除了等待剩余的396个字节之外什么也做不了。假设网络延迟(RTT)为1ms,如果服务器启动延迟确认机制,客户端启动Nagle算法,会是这样的情况:

客户端发送 4 字节出去,经过 41ms 后(服务器的 Delay ACK 40ms + 网络延时 1ms),收到了服务器返回的 ACK。客户端继续发送剩余的 396 字节。

差不多有40ms的时间几乎浪费在了Delay ACK上,这是无法忍受的!如果关闭Nagle算法,程序的性能会大大提高,因为剩下的396个字节可以直接发送,不用等待服务器的ACK。

但是《Unix网络编程》这本书(7.9节TCP套接字选项)提到关闭Nagle并不是最好的解决方案。笔者认为这是一种对网络有害的做法。正确的做法是应用层自己合并消息,调用一次write函数。我觉得作者说的没有错,但是在这个背景下,我觉得放在网络延迟长的环节(比如RTT在60ms以上)更合适。通常在局域网中,网络延迟几乎可以忽略不计,Nagle基本处于关闭状态,这主要是为了防止猪队友Delay ACK恶化你的网络程序性能。

在服务器端,最好开启TCP_QUICKACK选项(现在Linux默认开启),关闭Delay ACK机制。

4. 流量控制与拥塞控制

在TCP协议的面试中,经常会看到这两个术语。面试官基本会检查你是否真的熟悉TCP,会简单的问,TCP流控算法是做什么的?拥塞控制呢?

其中一种算法直接体现在TCP头字段,就是流量控制算法,对应的字段就是窗口大小。很多人分不清两种算法的异同。以下是简要总结:

相同点它们都是为了控制发送数据量的大小。不同点流量控制,是根据接收者能力情况,来控制发送数据量。拥塞控制,是根据网络的拥塞状态,来控制发送数据量。

最后,实际要发送的数据量取决于流量控制和拥塞控制计算的较小的数据量。Rwnd通常用来表示接收端的接收能力,cwnd用来表示链路仍能承载的数据容量。要发送的最终数据是:

在流控算法中,对等体的接收能力是指对等体能接收多少数据,体现在对方发给你的TCP报文的头字段winsize中。比如对方说只能接收100字节,那么你只能发送不超过100字节。如果你要发送的数据超过100字节,对不起,除了100字节,其余的只能暂存在本地发送缓冲区。

流量控制的经典算法是滑动窗口算法,限于篇幅,这里不做详细介绍。可以参考这篇文章《滑动窗口算法》。我们需要在拥塞控制算法上花更多的时间。

4.1 拥塞控制算法

拥塞控制算法的目的是防止网络在拥塞的情况下疯狂地向网络发送大量数据。那么这里就有一个值得关注的问题:发送方是如何知道网络拥塞的?

4.1.2 慢启动

连接建立后,发送方有没有办法知道网络拥塞的情况?显然不是,那我们怎么知道?我相信你能猜到,是的,你只能测试。TCP采用的策略是探测,这种方法被称为慢启动。

如果在传输过程中遇到重复的ACK或超时,则需要降低传输速度:

连续收到 3 次对方重复的 ACK 确认这意味着对方极有可能没有收到数据,几乎可以认为丢包了。但这并不代表网络拥塞,甚至网络状况还不错呢。(稍后解释。)如果超时未收到 ACK,说明极有可能拥塞对方可能没收到报文对方收到报文,但 ACK 丢了

一定要严格区分重复ACK和超时,影响TCP拥塞算法做出什么决定!!!

图6缓慢启动(1)

图7慢启动(2)

图6和图7显示了我捕获的一条消息。可以看出,一开始客户端发送的速度并不是很快。一次发两条,过了一段时间,一次发10多条。不过从抓取的数据包来看,没有丢包,网络情况很好。也可以找自己的机器进行实验。实验过程很简单,只需写4行Python语句。

如果TCP在传输中途遇到丢包或超时,就必须减慢传输速度,一次少发几段。比如一次发送16条消息段,出现异常(重复ACK三次或超时),那么下次发送时数量减半,一次发送8条消息。

关于慢启动的实验,这篇文章《慢启动》做的实验比较清晰,容易观察,可以参考一下。

4.1.3 慢启动算法

经典且原始的慢启动算法如下:

程序中维护了一个变量cwnd,表示拥塞窗口的大小,单位是字节。一开始cwnd是有初始值的,RFC 2581规定其大小不超过2MSS。为了后面描述方便,我说cwnd = 2,其实是指cwnd = 2MSS,后面的MSS就省略了。(MSS后面会解释,表示最大消息段长度,一般在1400字节左右。)

为了方便描述这个算法,可取的做法是同意cwnd的初始值为1(其实你看到的大部分是2)。

首先发送方发送一个 cwnd = 1 的报文。发送方每收到一个确认,就把 cwnd 值加 1。

详情请参考图8中的时序图。

图8慢速启动

4.1.4 拥塞避免算法

为了防止cwnd在慢启动过程中增加过多,TCP中还维护了另一个变量ssthresh,以字节为单位。它被称为慢启动阈值,这是一个阈值。当cwnd超过这个值时,慢启动算法结束,进入拥塞避免算法!

此时,TCP发送cwnd消息后,如果收到所有确认消息,cwnd的总值只增加1,而不是增加一倍(即每收到一条确认消息,cwnd增加1/cwnd)。这样,拥塞窗口cwnd将按照线性规律缓慢增长。

一些文献将这一过程称为“加性增加”。

图9慢启动(中间超时)

4.1.5 拥塞检测过程

无论是在慢启动阶段还是拥塞避免阶段,只要发送方判断网络可能发生拥塞(基于没有按时收到确认或者收到三次重复的ack),就需要在发生拥塞时将ssthresh设置为cwnd值的一半)。

超时和接收三个重复的ack需要分别考虑。两者是有区别的,需要严格区分。

A.超时(图9)

如果定时器超时,就有很大的可能出现拥塞(连重复的ACK都收不到),TCP此时反应强烈。

这时候把 ssthread设置为当前 cwnd 的值的一半.cwnd 值再设置成 1,接下来重新从慢启动开始。

这样做的目的是快速减少主机发送到网络的数据包数量,让拥塞的中间设备有足够的时间处理缓冲区中积压的数据包。参考图9。

B.收到三个连续重复的ack(图10)

最初可以判断网络不拥塞,但有很大概率丢失一条消息。为什么可以判断没有拥堵?因为对方收到乱序消息会立即返回ACK(这种情况不受延迟ACK机制影响,注意是立即返回。既然对方可以连续返回三次重复的ack,那就说明对方应该是连续收到了三次乱序消息。能连续三次收到乱序消息,说明网络还不错。

无序消息:例如,接收方预期接收100号消息,但接收到具有其他序列号的消息(顺序不正确)。

此时,发送方收到三个重复的ack,应该立即重传丢失的消息,而不是等待重传定时器超时。这种策略被称为快速重传。

当发生快速重传时,虽然网络可能不拥塞,但需要降低数据传输速率,但TCP响应较弱,实现快速恢复算法:

这时候把 ssthread设置为当前 cwnd 的值的一半。cwnd 值设置成 ssthread(也有些实现设置成 ssthread + 3)。进入拥塞避免阶段。

图10快速恢复算法

下面是小伙伴做的一个拥塞控制算法快速恢复算法的流程曲线,哈哈。

5. 三次握手是必须的吗?

让我们从另一个角度来分析这个问题。用UDP协议实现可靠传输该怎么办?

最重要的是解决可靠性问题,那么如何保证可靠传输呢?

超时重传确认机制

只要有了这两种机制,就可以实现最基本最简单的可靠传输。为了支持这两点,你发送的所有信息都需要编号。接收方按照编号顺序重新组装消息,最终应用层可以读取排序后的数据。

那么问题来了,接收方如何知道哪个包是第一个包?让我们约定第一个包的编号为0,这样我们就可以愉快地使用UDP+确认机制+超时重传来保证数据的可靠传输。

好像我们也没去三次握手啊!可靠传输还是可以实现的,那么TCP的三次握手有什么意义呢?敲黑板!!!

协商第一包数据编号。这个第一包编号,称为 ISN (Initial Sequence Number,初始序号),一定要记住这个名词,很重要,默念几遍。刚刚我们约定的策略是,第一包数据是编号是 0,那黑客就可以利用这个漏洞,伪造 TCP 报文,篡改数据,有了 ISN 后,双方就可以通过 ISN 来协议第一包数据编号了。通常实现上,ISN 的初始值是一个随机数(看你怎么猜!)。协商 MSS 以及确认双方数据接收能力。

通俗地说,三次握手的目的是建立信任关系,检测对方背景,确认对方能力,同时防止被黑客攻击。

6. MSS 与 MTU

MSS的全称是Maximum Segment Size,即最大段长。之前不止一次提到过,第7节也提到过,三次握手的目的之一就是协商MSS的大小。

一般来说,TCP数据段承载的数据越多越好。

如果一个TCP段传输的数据只有一个字节,那么IP层传输的数据报大小就是40+1 = 41个字节(至少20个字节的IP头+20个字节的TCP头+1个字节的数据)。这样网络的利用率只有1/41。传输nn字节的数据利用率为n/(40+n)n/(40+n)。显然,TCP段传输的数据越大,网络利用率越高。

但事实并非如此。因为网络传输数据的时候,数据最终是交付给链路层协议的,也就是说,最终是封装成一个“帧”。以太网Type 2规定帧长不能超过1518字节(14字节帧头+4字节帧校验和+最多1500字节数据)。因此,如果IP数据报的大小超过1500字节,则必须将其“分段”才能传送到链路层。

“碎片化”是指一个IP数据报太大,需要分割成小块才能成为多个IP数据报。这种碎片化显然是不利的,而且有一定的开销。为了避免碎片开销,我们希望IP数据报的大小不超过1500字节。除去IP数据报的前20个字节,希望TCP段不要超过1480个字节。减去TCP段的前20个字节,即TCP携带的数据不超过1460个字节。

实际上链路层对这一帧数据长度的限制称为最大传输单位,MTU)。不同的链路层协议也规定了MTU的值,通常这个值是可以改变的。使用命令netstat -i查看您的网卡的MTU值。

图11查看网卡的MTU

有些同学对MSS和MTU的概念还比较模糊,分不清两者的关系。

MSS 是软件层的概念,它是由软件控制的MTU 是硬件(比如网卡出口)的属性,是指二层链路层帧携带的数据最大大小。

再比如,MTU的大小就好比一座桥的承载吨位,桥就相当于一块网卡。预先给定MSS,您可以防止部分运输,因为您的卡车超载。如果不指定MSS,一旦你的货车超载,吨位超过了桥的承载能力,你就得把货物拆成几批运到那里。运完了还要组装,得不偿失。

7. TIME_WAIT

图12时间等待

TIMEWAIT是TCP协议中的一种状态,非常容易观察到。上面的实验使用nc命令发送数据,然后在客户端执行netstat -ant命令,可以看到TIMEWAIT状态。

既然这种状态很容易观察到,那就说明TCP一定是长时间停留在这种状态了。

TIMEWAIT状态,也称为2MSL等待状态。只有主动关闭聚会,才能进入TIMEWAIT状态。MSL(最大段生命周期)表示消息段的最大生命周期,它表示任何消息段被丢弃之前在网络中的最长时间。其实这个时间和TTL有关(TTL是IP协议中的一个概念,表示路由器可以经历的跳数,这个跳数是有限的,最多255次)。

然而,MSL用的不是啤酒花,而是时间。MSL的大小在不同的系统中是不同的。根据RFC,MSL = 2分钟,但在实际实现中,通常是30秒1分钟。尽管MSL的单位是时间而不是跳数,我们仍然假设具有最大跳数(255)的消息不能在网络中存在超过MSL秒。

当TCP协议进入TIME_WAIT状态时,它必须在此状态停留2次MSL。那为什么还要等这个2MSL的时间呢?目的是什么?

图13 TCP三次握手和四波状态转换。

有两个主要原因:

可以防止连接终止的最后一个 ACK 丢失。假设最后一个 ACK 在到达对端时恰好消失,此时对端已经等待了一个 RTT(报文段往返时间),于是进行重传最后一个 FIN,经过 0.5RTT 后到达对端。 等待 2MSL 的第二个原因

假设ip1:port1和ip2:port2之间建立了连接A,发送完数据后关闭连接A。如果没有TIME_WAIT状态,我们立即在ip1:port1和ip2:port2建立连接B(虽然这种事情发生的概率很小,但还是存在的)。

不幸的是,连接A有一个连接B接收到的重复的TCP段,但是连接B不知道这个TCP段是连接A中的旧消息,这样会出错!

如果有TIMEWAIT状态,等待2MSL足以使连接A中重复的消息在网络中消失;另一方面,根据TCP协议,不可能为处于TIMEWAIT状态的端口建立新的连接。这确保了每次成功建立新连接时,旧连接中重复的TCP段都已消失。

7.1 TIME_WAIT 影响

如果TCP处于TIME_WAIT状态,将等待2MSL,在此期间定义此连接的本地端口不能再次使用。例如,有一种联系:

(本地192.168.80.130: 5050,外地192.168.166.107: 40891)状态:time _ wait然后对于主机192.168.80.130,5050端口在2MSL内不能再次使用。

一般来说,主动关机方是客户端,客户端建立连接时的端口号是系统自动分配的,没有影响。此端口已被占用,请指定另一个端口。

但是,对于服务器来说,它使用的端口是一个众所周知的端口(开放端口)。如果它主动关闭聚会,进入TIME_WAIT状态,服务器在2MSL内无法启动。如果您再次启动它,您将被提示一个地址已被使用的错误。

事实上,今天的Linux通过为socket指定选项SO_REUSEADDR,提供了一种允许端口重用的方法。

需要注意的是,即使服务器或客户端绑定了2MSL的端口,RFC规定它们也不能建立连接。不幸的是,大多数系统实现不符合这一规定:

如果服务器重用了处于 2MSL 端口,它仍然可以接收连接请求并连接成功(这违反了协议)如果客户端重用了处于 2MSL 端口,它建立连接时仍然会失败。(没有违反协议)8. TCP 首部字段有哪些?

经过几个小时的实验和学习,我试着不看图片分析TCP报头字段。

这个问题显然不是死记硬背。我们试着从TCP的特点和相关算法来分析,作为一个TCP协议,它至少有哪些领域。

端口(共 4 字节 )

TCP是一种端到端协议。我们知道,要建立TCP连接,必须知道对端的IP地址和端口。所以“端口”一定是TCP报头中的必选字段,而IP地址不是,因为IP地址在第三层的网络层。Port包含源端口和目的端口,所以这两个字段在TCP报头中是必不可少的,也就是源端口&目的端口。

序号(4字节)

TCP是一种面向字节流的协议,无论你发送数据的顺序是什么,对方都会接收到数据。为了确保这一点,TCP需要对发送的数据的每个字节进行编号,因此在TCP报头中必须有一个关于数据编号的字段。因为一个TCP数据包一次会携带几个字节的数据,所以不可能对每个字节进行编号。在实际协议中,TCP只对数据的第一个字节进行编号,其余的可以根据第一个字节的编号进行计算。

关于数据包的长度。TCP头中不需要包的长度,因为这个长度已经在IP报文中携带了,可以根据IP报文头中的长度计算出TCP包的长度。也就是说,IP报文的长度减去TCP报头的长度就是实际数据的长度。

数据偏移(首部长度,4bit)

上面我们谈到了TCP报头长度,一般来说,报头长度是20字节。有时候,TCP报头还可以携带一些额外的可选,这样报头就超过了20个字节。正常吗?作为软件开发人员,我们都希望我们的协议非常灵活。为了计算TCP承载的数据的起始位置,数据偏移字段是必不可少的。当然,你也可以把数据偏移量称为头长度。一点问题都没有。这两者是一回事。还应该注意,该数据偏移的单位是4字节。例如,偏移量为5,这意味着报头长度为20个字节。

确认号(4字节)

我们知道TCP是一种可靠的协议。所谓可靠性是指TCP保证发送的消息能够被对方收到,保证可靠性的方法是基于超时重传算法和确认机制。TCP协议要求对方回复收到了什么数据,用之前的序列号就可以了。因此,TCP报头也需要一个确认号来实现可靠传输。

标志位(12bit)

TCP包的类型有很多种,比如三次握手使用的同步包,四波使用的结束包,确认机制使用的确认包。因此,TCP报头中必须有一些字段来描述TCP包的类型。在TCP报头中,一些特殊的位用于标记消息的类型。如果该比特位置为1,则意味着它是对应于该类型的分组。目前有9种类型的消息(仍有3个保留位,将来可能会添加新的标志位)。TCP消息可以同时属于许多类型,例如,它们可以同时是同步分组和确认分组。

目前常见的标记有syn、ack、psh、fin、rst、urg,这些你在书上也能找到。还有NS,CWR,ECE,只要你知道他们的存在。如果想了解更多,可以自己查阅资料。

窗口大小(2字节)

了解TCP的朋友一定听说过滑动窗口算法,主要用于流量控制。你什么意思?实际上,窗口大小是一个用来描述接收机接收能力的参数。比如我现在最多只能收到1024字节,所以你发的时候不要给我发超过1024字节的,不然我装不下,也没地方存放。因此,TCP报头中有一个16位字段来记录窗口大小。

校验和(2字节)

这个领域即使你不知道也没多大关系。但是对于网络传输来说,校验和是一个很常见,应用也很广泛的东西,TCP头有也不奇怪。

紧急指针(2字节)

这大概是目前存在感最低的领域,现实中很少用到。在套接字编程中,它被称为OOB(out-of-band data),意思是连接外的数据,但实际上它并没有真正提到连接外的数据,它是和正常数据一起发出去的。“应急指针”其实不急。发送紧急数据时,只需将TCP标志位的紧急标志位置1,同时紧急指针指向对应的紧急数据号并发送出去。当接收器接收到带有紧急标志位的数据时,它会进行一些特殊的处理。

我们来看一下全貌:

最后,记住头字段是不需要记忆的,一定要结合TCP本身的特点去思考TCP应该包含哪些字段才能实现这些功能。

好了,现在,TCP报头中应该有哪些字段?

9. 总结

TCP内容真的太多了。本文选取了一些重要的核心内容。在你掌握了这些基础之后,进一步学习更多的内容应该是没有问题的。再复习一遍我们的知识点:

三次握手与四次挥手三次握手的目的Delay ACKNagle 算法拥塞控制MSS/MTUTIME_WAITTCP 首部字段tcpdump 工具的基本使用学会使用 python 进行简单的 socket 编程,用其来学习 tcp 协议

最后我想说的是,TCP是一个一直在发展的协议,它内部的算法不是一成不变的,尤其是拥塞控制算法。现在Linux内核中集成的拥塞控制算法是立方算法,最近还有一个很火的BBR算法(值得仔细阅读)。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论