Linux| |对于TCP的学习

对于TCP的学习


前言

TCP称为“传输控制协议”。也就是要对数据的传输进行一个详细的控制

1. TCP的特点及其目的

为了通过IP数据报实现可靠性传输,需要考虑很多事情,例如数据的破坏、丢包、重复、以及分片顺序混乱等问题。如不能解决这些问题,也就无从谈起可靠传输

TCP通过检验和、序列号、确认应答、重发机制、连接管理以及窗口控制等机制实现可靠传输

有连接:使用TCP协议进行通信时,需要先建立连接

可靠传输:具有确认应答机制,超时重传等机制保证数据的可靠传输

面向字节流:传输的数据是字节流,没有长度的限制

连接的概念

  • 在内核中有一个结构体来描述连接TCB,在通过队列将其管理起来

  • 操作系统维护一个连接是需要成本的(时间成本和空间成本)

  • 必须要使用合理的方式管理连接否则会导致服务器挂掉

  • 连接的数量是有上限的

2. TCP协议段格式

插图:TCP协议报文格式

2.1 16位源端口号

表示数据从哪个进程来

2.2 16位目的端口号

要到那个进程去

2.3 32位序号

序号是可靠传输的关键因素。TCP将要传输的每个字节都进行了编号,序号是本报文发送的数据段的第一个字节的编号,序号可以保证传输信息的有效性。

  • 序列号是按顺序给发送数据的每一个字节都标上号码的编号。接收端查询接收数据TCP首部中的序列号和数据的长度,将自己下一步应该接收的序号作为确认应答(ACK)返送出去

    • 【注意】:给每一个字节都标上号码也就是,对于头部数据也要标上号码,也就是对于ACK和FIN这种标志也会有着自己的序号

  • 序列号的初始值并非是0,而是在建立连接以后由随机数生成,而后面的计算则是对每一字节加一

  • 序号也指字节与字节之间的间隔

  • 假设主机A的一个进程向主机B的一个进程发送一个数据流,主机A将隐式的对数据流中的每一个字节进行顺序编号。假定数据流有一个包含大于2000字节的文件组成,其MSS(最大报文长度)为1000字节,数据流的首编号是1,如下图:

  • 插图:发送数据

  • 给第一个报文段分配序号1,第二个报文段分配序号1001,依次类推,每一个序列号被填入到相应TCP报文段首部的序号字段中

  • 插图:序号和确认序号

  • TCP的数据长度没有写入TCP的首部。实际通信中求得TCP包的长度的计算公式是: IP首部中的数据报长度 - IP首部长度 - TCP首部长度

2.4 32位确定序号

每一个ACK(确认应答)对应着这一个确认号,他指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认序号只有当ACK标志为1时才有效。

  • 确定序号是根据接收数据首部中的序号和数据的长度来计算得到的。

  • 确定序号 = 首部序号 + MSS(最大消息长度)

  • TCP是全双工的,即主机A在向主机B发送数据的同时,也许也在接收来自主机B的数据。从主机B到达的每个报文段中都有一个序号用于从B流向A的数据。主机A填充进报文段的确认号是主机A期望从主机B收到的下一字节的序号,

  • 举例子说明:

假设主机A已经收到了来自主机B的编号为0-535的所有字节,同时假设它打算发送一个报文段给主机B,主机A等待主机B的数据流中字节536及其后的所有字节,所以主机A会在它发往主机B的报文段的确认号字段中填上536。

  • 再举一个例子

有关失序到达,并且TCP提供的是累计确认

  • 累计确认

    • 累计确认是一种差错控制技术,用于对接收报文的确认。在累计确认技术中,如果收到了后面报文的确认信息,前面的报文肯定已经接收正确,即便以后再收到前面报文的确认信息,也不需要处理了

假设主机A已收到主机B的包含字节0-535字节的报文段,以及另一个包含字节900-1000的报文段。由于某种原因,主机A还没有收到字节536-899的报文段。在这个例子里,主机A为了重新构建主机B的数据流,仍在等待字节536(和其后的字节)。因此,A到B的下一个报文段将在确认号字段中包含536。因为TCP只确认该流中到第一个丢失字节为止的字节,所以TCP提供的是累积确认

主机A虽然收到了字节900-1000的报文段,但是并不会在在一个发往主机B的报文段的确认号字段填1001,因为535后面的字节还没有得到确认,而受到的900-1000字节的报文段属于失序到达,对于失序到达的报文段的方法由TCP编程人员去具体实现,有两个基本选择:一是丢弃失序报文段,而是保留失序字节并等待缺少的字节以填补该间隔

2.5 4位首部长度(数据偏移)

表明该TCP头部有多少个32位bit(有多少个4字节),所以TCP头部最大长度是15 * 4 = 60。该部分可以将包头和有效载荷进行分离,TCP报文默认首部长度是20字节。所以选项可变长度最大是40字节

2.6 6位保留位

该字段是为了保留为了以后使用,一般设置为0,但即使收到的报文段中该字段不为0,也不丢弃

2.7 6位标志位

  • URG:标志紧急指针是否有效,URG为1表示包中有需要紧急处理的数据

  • ACK:确认应答是否有效,为1则有效(确认号)

  • PSH:提示接收端应用程序立刻从TCP缓存区把数据读走。PSH为1是就是将缓存区中的数据交给上层应用,PSH为0,就是数据不需要立即传输然进行缓存

  • RST:为了处理异常连接的,告诉连接不一致的一方,我们的连接还没有建立好,要求对方重新建立连接。我们把携带RST标识的称为复位报文段

  • SYN:请求建立连接。我们称携带SYN标识的称为同步报文段

  • FIN:通知对方,本端要关闭了,我们称携带FIN表示的为结束报文段

2.8 16位窗口大小

如果发送方发送大量数据或者发送数据的速度过快,接收端来不及接收就会导致数据的大量丢失,所以接收端会和发送端协调,使其发送的数据能够来得及被处理。然后接收端可以发送给发送端消息让发送端发慢一点,这就是流量控制。

接收方将自己接收缓存区剩余空间的大小告诉发送方。这个接收端缓存区剩余容量的大小就是16位窗口大小。发送方可以根据窗口大小来适配发送的速度和大小,窗口大小最大是2的16次方即64KB,但也可以根据选项中的某些位置扩展,最大扩展1G

2.9 16校验和

发送端填充,CRC校验。如果接收端校验不通过,则认为数据有问题(此处不仅校验TCP首部也校验TCP数据部分)

  • TCP与UDP校验和相似,只是TCP的校验和无法关闭,两者校验都要使用伪首部

  • TCP的伪首部和UDP基本相似,只不过UDP伪首部中的八位传输层协议号是17,而TCP是6

  • 插图:伪首部

  • 伪首部的作用:

    1. 仅在校验时使用

    2. 通过目的IP地址检验主机是否收错了报文

    3. 通过检验协议号查看是否交付给了正确的上层协议

  • IP首部可以检验IP地址为何还要伪首部进行检测

    • 当IP数据报在路由器中间进行转发时,可能会修改IP首部和其中的校验和,IP的首部可能会发生变动,所以需要进行二次检测,如果接收端校验和与发送端校验和所得出的结果不一致就会被丢弃

  • 检验方式:

    1. 把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP首部中的校验和字段置为0

    2. 用反码相加法累加所有的16位字(进位也要累加)

    3. 最后,对计算结果取反,作为TCP的校验和

2.10 16位紧急指针

按序到达是TCP协议保证可靠性的一种机制,但是也存在一些报文想要优先处理,这是就可以设置紧急指针,指向该报文即可,同时将紧急指针有效位(URG)置1。紧急指针指向了紧急数据的结尾部分的下一个字节。

  • 因为只有一个紧急指针,这也意味着他只能标识一个字节的数据,这个指针指向紧急数据最后一个字节的下一个字节。紧急指针也用作表示数据流分流的标志

  • 插图:紧急指针

  • 我们知道TCP传输数据时是有顺序的,他有字节号,URG配合紧急指针,就可以找到紧急数据的字节号。紧急数据的字节号公式如下:

  • 紧急数据字节号(urgSeq)= TCP报文序号(seq)+ 紧急指针(urgpoint)- 1

  • 如上图的例子:seq = 10,urgpoint = 5;那么自己序号urgSeq = 10 + 5 - 1 = 14

  • 一旦 TCP 知道了你要发送紧急数据,那么在接下来的数据发送中,TCP 会将所有的 TCP 报文段中的 URG 标志置位,哪怕该报文段中不包含紧急数据,这个行为会持续到紧急数据被发送出去为止。

  • 紧急指针会产生覆盖如果发送方多次发送紧急数据,最后一个数据的紧急指针会将前面的覆盖。比方说你发送了一个字节的紧急数据 'X',在 'X' 尚未被 TCP 发送前,你又发送了一个紧急数据 'Y',那么在后面的 TCP 报文中,紧急指针都是指向了 'Y' 的。

2.11 选项

用于提高TCP的传输性能,因为数据偏移(首部长度)进行控制,所以其长度最大是40字节。另外选项长度尽量调整为32位(4字节)的整数倍

  • 0,NOF选项表示结束选项,表明首部已经没有更多的消息了,数据从下一个32位开始,每个报文段只用一次,放在末尾用于填充

  • 1,NOP操作选项,一般用于将TCP选项的总长度填充为4字节的整数倍

  • 2,MSS(最大消息长度)4字节,用于连接时决定最大段长度的情况

  • 3,窗口扩大选项3字节,用来改善TCP吞吐量的选项。TCP首部中窗口字段只有16位。因此在TCP报的往返时间(RTT)最大只能发送64KB的数据。窗口的最大值可以扩展到1G字节。由此在一个RTT较长的网络环境中,也能达到较高的吞吐量

    • 3个字节中的一位表示移位S,新的窗口大小的值为(16+S),相当于把窗口左移S位,移位的上限为14,所以可以扩大的窗口的大小为65535 * 2 ^ 14即1GB

    • 窗口大小在连接建立的时候就确定了,连接后无法改变,如果已经实现了窗口的扩大,发送S = 0就可以恢复到16位的大小了

  • 4,选择性的应答2字节,TCP通信时,如果数据丢失,发送端会重发最后被确认序号后序的所有报文端,但这样会重复发送。使用选择性确认选项可以只发送丢失的数据,而不用重发所有未被确认的。在初始化连接时,可以选择是否选择SACK技术

  • 5,SACK的实际选项,该选项参数告诉发送端已经接收并且缓存了的数据块(已经接收的),可以是发送端并据此进行重发丢失的数据块,最大为40个字节,4组

  • 8,时间戳选项,当网络在传输大量的数据时,32位序号很快就会用完,用完序号就会重新开始,但当网络阻塞或者延迟较高是,就会造成新老序号在同一个网络中,就会造成混淆。时间戳就是为了区分新老序列号

3. TCP以段为单位发送数据

在建立TCP连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS:Maximum Segment Size)。最理想的情况是,最大消息长度正好是IP中不会被分片处理的最大数据长度

TCP在大量传输数据的时候,是以MSS的大小将数据进行分割发送的。进行重发也是以MSS为单位

MSS是在三次握手的时候,在两端主机之间计算得出。两端主机在发出建立连接请求时,会在TCP首部中写入MSS选项,告诉对方自己的接口能够适应的MSS的大小。然后会在两者之间选一个较小的值投入使用。

TCP协议传输时的MSS = Min(主机A中的MSS, 主机B中的MSS)

为附加MSS选项,TCP首部将不再是20字节,而是4字节的整数倍。

如下图:TCP的首部是24字节

插图:具有MSS的TCP的报文首部

【特例】

  • 在建立连接时,如果某一方的MSS选项被忽略,可以选为IP包的长度不超过576字节的值(例:IP首部 20 字节,TCP首部 20 字节,MSS 536 字节

4. 确认应答机制

在TCP中,当发送端的数据到达接收主机时,接收端主机会返回一个已经收到的消息通知,这个消息就叫做确认应答(ACK)

例子:在两个人谈话的时候,在谈话的停顿处可以点头或者询问以确认谈话内容。如果对方迟迟没有收到任何反馈,说话的一方还可以再重复一遍以保证对方确认收到。因此,对方是否听到了此次对方的内容要靠对方的反应来判断。

当对方听懂对话内容时会说:“嗯”,这相当于返回了一个确认应答(ACK)。而当对方没有理解对话内容或没有听清回问一句“咦”,这就好比是一个否定确认应答(NACK)

对于正常的数据传输:

插图:正常的数据传输

TCP通过肯定的确认应答(ACK)实现可靠的数据传输,当发送端发送数据之后会等待对方的确认应答,如果有确认应答说明数据成功到达对面,否则有可能数据丢失。

  • 该图就是主机A给主机B发送了序列号为1-1000的数据,ACK应答就会返回1001序列号,告诉主机A,已经收到了1-1000的数据,下一次从序列号为1001的字节开始发送数据

  • 为什么每次发送1000个字节的数据,这是由MSS(最大消息长度决定的)

对于发送失败有两种情况:

  • 数据包丢失的情况:也就是发送端给接收端发送消息,没有发送过去

插图:数据包丢失

  • 确认应答丢失的情况:也就是接收端接收到消息了,给发送端发送确认应答(ACK)消息,ACK丢失

插图:确认应答丢失

为了异常情况的产生之后还可以进行正常的通信就有了确认应答机制

5. 超时重传机制

对于上面产生的异常情况之后,主机A就会向主机B重发数据报。

重发超时是指在重发数据之前,等待确认应答到来的那个特定时间间隔.如果超过了这个时间仍未收到确认应答,发送端将进行数据重发。那么这个重发超时的具体时间长度是多少呢?

  • 最理想的就是找到一个最小的时间,他能保证“确认应答一定能在这个时间内返回“

  • 但是这个时间的长度会随着网络环境的变化而变化

    • TCP要求无论处在何种网络环境中都要提供高性能通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。为此,他在每次发包时都会计算往返时间(RTT)和偏差。将这个往返时间(RTT)和偏差相加,重发时间就是比这个总和要稍微大一点的值

  • 如果超时时间设的太长,会引起整体的重传效率

  • 如果超时时间设的太短,有可能频繁的发送重复的数据报

Linux(BSD的Unix和Windows)中,超时都以500ms(0.5秒)为单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。(对于最初的数据包还不知道往返时间,所以其重传时间一般设置为6秒左右

如果重发一次还得不到应答则进行再次发送,等待应答的时间将会以2倍,4倍的指数函数延长

另外数据也不会被无限、返回的重复重发。达到一定重发次数之后,如果仍没有确认应答(ACK)返回,TCP就会判定网络或者对端主机发生了异常强制关闭连接,并且通知应用通信异常强行终止

6. 连接管理机制

TCP是面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好通信两端的准备工作

  • 正常情况下,TCP要经历三次握手建立连接,四次挥手释放连接

7. 理解TIME_WAIT状态

首先我们做一个测试,首先启动server然后启动client,然后使用ctrl + c是server终止掉,这时再次运行server,结果是:

插图:端口号绑定出错

当我们将一个服务器关闭掉,再次重新启动服务器就会发现一个问题:就是不能马上再次绑定这个端口号,需要等一会儿才可以再次重新绑定。其实等的这一会儿就是断开连接的一方处于TIME_WAIT状态。所以会出现绑定失败。

TCP协议规定,主动关闭连接的一方要出现TIME_WAIT状态,等待两个MSL(Maximum Segment Lifetime)的时间后才能回到CLOSED状态

我们使用ctrl + c终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口

MSL在RFE1122中规定为两分钟,但是各操作系统的实现不同,在Centos7上默认配置是60s

  • 可以通过cat /proc/sys/net/ipv4/tcp_fin_timeout查看MSL的值

  • 首先解释一下MSL:MSL是报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

  • 而对于网络层来说,对于IP协议报文首部中有一个TTL(time to live),中文可以译为生存时间,这个生存时间是由源主机设置初始值但不是生存的具体时间,而是一个存储了IP数据报可以经过我的最大路由数,没经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。

  • TLL与MSL是有关系的,但不是简单的相等的关系,MSL要大于等于TTL

为什么TIME_WAIT的时间是2MSL?

  • 保证客户端发送的最后一个ACK(确认应答)报文段可以到达服务器端,这个ACK报文段可能丢失,因而是处于LAST_ACK端的服务器收不到ACK报文。这个时候服务器端就会给客户端重传FIN报文段,而客户端就能在2MSL时间内收到这个重传的FIN报文段,接着客户端继续发送ACK报文段交给服务器。然后客户端进入TIME_WAIT状态,重新启动2MSL。最后客户端和服务器一定都会进入到CLOSED状态。如果客户端没有2MSL,而是在发送完ASK(确认应答)报文后就直接进入到CLOSED状态,那么如果ACK在传输失败后,客户端就接收不到服务器重新发送的FIN报文段了,也就不会再给服务器端发送ACK(确认应答)报文段,这样就会导致服务器端,无法进入到CLOSED状态

  • 保证两个传输方向上尚未被接收到或者迟到的报文都已经消失。(否则服务器重启,可能收到来自上一个进程迟到的数据(FIN报文),但是这种数据很可能是错误的)。例:比如客户端发送第一个请求连接报文段丢失而未收到确认,客户端就会重传一次连接请求,第二次服务器端收到了确认,建立了连接。数据传输完毕后就释放了连接。客户端一共发送了两个连接请求报文段,其中第一个丢失了,第二个到达了服务器。假如客户端发送第一个连接请求报文段没有丢失,而是在某些网络结点长时间逗留了,以至于延误到连接释放后的某个时间才到达服务器端,这本来是已失效的报文段,但是服务器并不知道,就会又建立一次连接。而等待的2MSL就是为了解决这个问题,客户端在发送完最后一个确认应答(ACK)之后,在经过2MSL时间,就可以是本次连接诶持续时间内所产生的的所有报文段都从网络中消失,这样就可以使下一个新的连接不会出现这种旧的连接请求报文段了。

8. 解决TIME_WAIT引起的bind失败的方法

在2MSL时间内这个地址上的连接(客户端的端口和服务器端的端口)不能被使用。

这是因为在这个2MSL时间内,客户端和服务器端的连接还是存在的所以不能给这个端口在此绑定上一个应用程序

在server的TCP连接没有完全断开之前不允许重新绑定,也就是TIME_WAIT时间没有过,但是这样不允许绑定在某些情况下是不合理的:

  • 服务器需要处理大量的客户端连接(每个连接的生存时间可能很短,但是每秒都有很大数量的客户端来请求),这个时候如果由服务器主动关闭连接(比如某些客户不活跃,就需要被服务器daunt主动清理掉),这样服务器端就会产生大量TIME_WAIT状态。

  • 如果客户端的请求量很大,就可能导致TIME_WAIT的连接数很多,每个连接都会占用一个通信五元组(源IP,源端口,目的IP,目的端口,协议)。其中服务器的IP和端口以及协议是固定的,如果新来的客户端连接的IP和端口号处于TIME_WAIT状态而无法连接到服务器。

解决方法:

使用setsockopt()函数就可以了。设置socket第三个参数设置为SO_REUSEADDR为1,表示允许创建端口号一样但IP地址不同的多个socket描述符

使用方法:https://blog.csdn.net/qq_40399012/article/details/85983221

9. 理解CLOSE_WAIT状态

如果客户端是主动断开连接的一方,客户端给服务器发送一个FIN报文段,服务器端就要给客户端发送一个ACK(确认应答),但是在服务器端假设没有关闭连接(也就是没有调用close()函数去关闭socket描述符),这是服务器就会产生一个CLOSE_WAIT状态,因为服务器没有去关闭连接

大家可以去看一下这份代码:

https://github.com/YKitty/LinuxDir/tree/master/LinuxCode/netWork/TCP/ThreadPoolTCP

中的TcpServer.hpp将其中的Service成员函数中的close(new_sock)取消掉就可以看到服务器端进入到了CLOSE_WAIT状态了

操作顺序:编译运行服务器,启动客户端连接查看TCP状态,客户端和服务器端度为ESTABLELISHED状态,没有问题,然后我们关闭客户端程序,观察TCP状态,看到结果如下图。

观察TCP状态使用命令netstat:https://blog.csdn.net/qq_40399012/article/details/85983221#t19

运行结果:

插图:服务器端进入到CLOSE_WAIT状态

此时服务器进入到了CLOSE_WAIT状态,结合四次挥手的流程图,可以认为四次回收还没有正确完成

【注意】:对于服务器出现大量的CLOSE_WAIT状态,原因就是服务器没有正确关闭socket,导致四次回收没有完成这是一个BUG,只需要加上对应的close()函数就好了

10. 滑动窗口

确认应答策略的每一个发送的数据段都要给一个ACK确认应答,接收方收到ACK后再发送下一个数据段,但是这样做有一个比较大的缺点,就是性能差,尤其是数据往返的时间较长的时候

既然一收一发的方式性能较低,那么我们考虑一次发送多条数据,就可以大大的提高性能(其实就是将多个段的等待时间重叠在了一起)

为解决这个问题。TCP引入了窗口这个概念。即使在往返时间较长的情况下,他也能控制网络性能下降。

如图:确认应答不再是一个段了,而是一个更大的单位(窗口)。也就是说发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送

插图:用滑动窗口并行处理

窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。如上图:窗口大小分为4个段(4000字节)。

插图:滑动窗口方式

如上图,在窗口的数据即使没有收到确认应答也可以发出去。当收到第一个ACK(确认应答)之后滑动窗口向右移动,继续发送第五个段的数据,然后依次类推。操作系统内核为了维护这个滑动窗口,需要开辟发送缓存区来记录当前有哪些数据没有应答,只有应答过的数据才能从缓存区中删除

滑动窗口越大,网络的吞吐率越高

滑动窗口左边表示已经发送过并且确认的数据,可以从缓存区中删除,滑动窗口里面表示可以发送的数据或者发送出去但是还没有确认,滑动窗口右边代表还没有发送的数据

如果使用窗口控制中,出现段丢失该怎么办呢?

情况一:数据报已经到达,ACK丢失了

数据已经达到了对端,是不需要再进行重发的。然而在没有使用窗口控制的时候,没有收到确认应答的数据会被重发,而使用了窗口控制,某些确认应答即便丢失了也不需要重发

如图:

插图:没有确认应答也不受影响

如图对于ACK(确认应答)1001序号的应答没有发送到客户端,但是只要1001序号后面的确认应答发送到了客户端,前面的确认应答没有到客户端也没有关系,客户端依旧是正确发送数据的。这是因为:只有服务器端接收到了前面的报文段才会给客户端发送自己希望下一次发送过来的序列号增加,否则的话就会一直放松希望下一个序列号是前面的。

情况二:某个报文段丢失的情况

插图:高速重发控制

接收主机如果收到一个自己应该受到的序号以外的数据时,会针对当前为止收到数据返回确认应答。

如图当某一段报文段丢失后,发送端会一直收到序号为1001确认应答,这个确认应答好像是在提醒发送端“我想接收的是从1001开始的数据”。在窗口比较大,又出现报文丢失的情况下,同一个序号的确认应答将会被重复不断的返回。而发送端主机如果连续三次收到同一个确认应答,就会将其所对应的数据进行重发,这时接收端收到1001序号开始的报文段了,之后,再次返回的就是ACK(确认应答)为7001了,因为2001 - 7001接收端已经收到了,被放到接收端操作系统内核的接收缓存区了。这种机制高速重发机制(也叫“快重传”)

快重传要求接收方在收到有一个失序的报文段后就立即发出重复确认(为的是使发送方鸡早知道有报文段没有到达对方)而不需要等到自己发送数据时捎带确认。快重传规定,发送方只要一连收到三个重复确认应答就应当立即重传对方尚未收到的报文段,而不必等待设置的重传计时器时间到期。由于不需要等待设置的重传计时器到期,能尽早重传未被确认的报文段,能提高整个网络的吞吐量。

快重传:

  • 发送端收到三个连续的确认应答,就立即重发数据,不需要等待重传时间

  • 接收端只要收到一个失序的报文段就立即发出三次确认应答,不需要等待捎带应答

11. 流量控制

出现的原因:

  • 发送段根据自己的实际情况发送数据。但是接收端可能收到的是一个毫无关系的数据包有可能会在处理其他问题上花费一些时间。因此在为这个数据包作其他处理耗费一些时间,甚至在高负荷的情况下无法接受任何的数据。如此一来如果接收端将本该接收的数据丢弃的话,就又会触发重发机制,从而导致网络流量的无端浪费

为了防止该现象的产生TCP提供了一种机制可以让发送端的根据接收端的实际接受能力控制发送的数据量。这就是流量控制。

接收端主机向发送端主机通知自己可以接受数据的大小,于是发送端会发送不超过这个限度的数据。该限度就是窗口大小。前面的窗口大小就是由接收端主机决定的

TCP首部中有一个专门的字段(窗口)存储窗口大小。接收主机将自己的接收缓存区剩余的大小放到该字段中通过ACK(确认应答)发送给客户端

窗口大小字段越大,说明网络吞吐量越高

接收端一旦发现自己的缓存区快满了,就会将窗口大小设置成一个更小的值通知给发送端

发送端主机会根据接收端主机的提示,对发送数据的量进行控制,减慢自己的发送速度

如果接受缓存区满了就会将窗口置为0,这时发送端不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端

插图:流量控制

如图:当接收端收到从3001号开始的数据段后,其缓存区即满,不得不暂时停止接收数据。之后,在收到窗口更新通知后通信才得以继续进行。如果这个窗口的更新通知在传输途中丢失,可能会导致无法继续通信。为避免这类问题的发生,发送端主机会时不时的发送一个叫做窗口探测的数据段,此数据端仅包含一个字节以获取最新的窗口大小

什么时候发送端发送窗口探测数据段?

  • 过了重发超时的时间还没有收到接收端窗口更新的通知,发送端就会发送一个窗口探测更新

12. 拥塞控制

虽然TCP有了窗口控制,收发主机即使不再以一个数据为单位发送确认应答,也能够连续发送大量数据包。然而在通信刚开始的时候就发送大量的数据包,也可能会引发其他的问题。

一般来说计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信是的网络拥堵。在网络出现拥堵时,如果突然发送一个较大量的数据,极可能会导致整个网络的瘫痪。

TCP引入慢启动机制,先发送少量的数据,探探路,摸清楚当前网络拥堵状态,再决定按照多大的速度传输数据,首先为了在发送端调节所要发送的数据量,定义了一个叫做“拥塞窗口”的概念。于是在慢启动的时候将这个拥塞窗口的大小设置为1个数据段。

最初发送端的窗口(拥塞窗口)设置为1,。每收到一个确认应答,窗口的值会增加1个段,在发送数据包时,将拥塞窗口的大小和接收端主机的通知的窗口大小作比较,然后按照它们当中的较小的那个值发送比其还要小的数量

像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始慢,但是增长速度非常快。

插图:TCP窗口的变化

为了防止增长的这么快,拥堵状况激增甚至导致网络拥塞的发生。引入了慢启动阈值的概念。当拥塞窗口超过这个阈值的时候,不在按照指数增长,而是按照线性方式增长,

当TCP开始启动的时候,并没有设置相应的慢启动阈值。而是在超时重发的时候,慢启动阈值等于当时拥塞窗口一半的大小

在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1

重复确认应答(也就是网络堵塞了,没有滑动窗口内部的一个数据段没有接收到从而引发多次确认应答)而触发的高速重发控制时,慢启动阈值的大小被设置为当前窗口大小的一半(实际已发送但未收到确认应答的数据量),然后将窗口的大小设置为该慢启动阈值+3个数据段的大小。

当TCP开始通信的以后,网络吞吐量会逐渐上升,但是随着网络拥堵的发生吞吐量也会急速的下降。于是会再次进入吞吐量慢慢上升的过程,因此所谓TCP的吞吐量的特点就好像是在逐步占领网络带宽的感觉。

(吞吐量是指在没有帧丢失的情况下,设备能够接受的最大速率 )

少量的丢包我们仅仅是触发超时重传,大量的丢包,我们就认为网路堵塞

拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大的压力的折中办法

13. 延迟应答

如果接收数据的主机每次如果都立刻回复确认应答(ACK),可能会返回一个较小的窗口,这是因为刚接收完数据这个时候接收缓存区已满

假设:接收端缓存区为1M,一次收到了500K的数据,如果立刻应答,返回的窗口就是500K;但实际上可能处理端得速度很快,10ms之内就把500K数据从缓存区消费掉了,这种情况下,接收端处理数据还远远没有到达自己的极限,即使这个窗口再放大一些,也能处理过来,如果接收端稍微等一会儿应答,比如等200ms再应答,那么这个时候返回的窗口就是1M

一定要记得,窗口越大,网络吞吐量越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。

为此引入一个方法,那就是接收到数据以后并不立即返回应答,而是延迟一端时间的机制

  • 再没有收到2 * 最大段长度的数据为止不做确认应答(根据操作系统的不同,有时也有不论数据大小,只要收到两个包就即可返回确认应答情况)

  • 其他情况下,最大延迟0.5秒发送确认应答(很多操作系统设置为0.2秒左右)

事实上,不必为每一个数据段都进行一次确认应答,TCP采用滑动窗口的控制机制,因此通常确认应答少一点也无妨。TCP文件传输中,绝大多数是每两个数据段返回一次确认应答

如图:

插图:延迟确认应答

每收到两个数据段发送一次确认应答。不过等待0.2秒后还没有其他数据包到达的情况下才会发送确认应答

14. 捎带应答

根据应用层协议,发送出去的消息到达对端,对端进行处理以后,会返回一个回执

在延迟应答的基础上,客户端和服务器在应用层也是“一发一收的”。在此类通信中,TCP的确认应答和回执数据可以通过一个包发送。这种方式叫捎带应答。通过这种机制可以是收发数据量减小

注意:接收数据以后如果立刻返回确认应答,就无法实现捎带应答。而是将所接受的数据传给应用处理生成数据以后将要回执的数据和确认应答一起发送给对端。

如果没有启用延迟确认应答就无法实现捎带应答。延迟确认应答是能够提高网络利用率从而降低计算机处理负荷的一种较优的处理机制

如图:

插图:捎带应答

15. 面向字节流

当我们创建一个TCP的socket,同时在内核中创建一个发送缓存区和一个接收缓存区

  • 调用write时,数据会先写到发送缓存区中

  • 如果发送的字节数太长,会被拆分成多个TCP的数据报发出

  • 如果发送的字节数太短了,就会先在缓存区里等待,等到缓存区长度差不多了,或者其他合适的时机发送出去

  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓存区

  • 然后应用程序可以调用read从接收缓存区拿数据

  • 另一方面,TCP的一个连接,既有发送缓存区,也有接收缓存区,那么对于一个连接,既可以读数据,也可以写数据。这个概念叫做全双工

由于缓存区的存在,TCP程序的读和写不需要一一匹配

例如:

  • 写100个字节的数据,可以调用一次write写100个字节,也可以调用100次write,每次写一个字节

  • 读100个字节数据,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以一次read一个字节,重复100次

16. 粘包问题

粘包问题中的包,是指的是应用层的数据包

在TCP的协议头中没有如同UDP一样的“报文长度”这样的字段,但是有一个序号这样的字段

站在传输层的角度,TCP是一个报文一个报文过来的,按照序号排序在缓存区中

站在应用层的角度,看到的只是一串连续的字节数据

那么应用程序看到这一连串的字节数据,就不知道从哪里到哪里是一个完成的应用层数据包

如何解决粘包问题呢?明确两个包之间的边界

  • 对于定长的包,保证每次都按固定大小读取即可

  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而知道包的结束位置

  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议是程序员自己来定义的,只要保证分隔符不和正文冲突即可)

对UDP协议如果还没有给上层交付数据,UDP的报文长度仍然还在。同时UDP是一个一个把数据交付给应用层,这样就有存在明确的数据边界。站在应用层的角度,使用UDP的时候要么收到完整的UDP报文要么不收,不会出现“半个“的情况

17. TCP连接异常情况

  • 进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有区别。机器重启和进程终止一样

  • 机器掉电/网线断开:接收端认为连接还在,一旦接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP自己也在内置了一个保活定时器,,会定期询问对方是否还在。如果对方不在,也会把链接释放。应用层的某些协议, 也有一些这样的检测机制.例如HTTP长连接中, 也会定期检测对方的状态.Q在QQ 断线之后, 也会定期尝试重新连接

18. TCP小结

18.1 可靠性

  • 检验和

  • 序列号

  • 确认应答

  • 超时重发

  • 延迟重发

  • 连接管理

  • 流量控制

  • 拥塞控制

18.2 提高性能

  • 滑动窗口

  • 快速重传

  • 延迟应答

  • 捎带应答

18.3 基于TCP的应用层协议

  • HTTP

  • HTTPS

  • TELNET

  • SSH

  • FTP

18.4 TCP和UDP的区别

  • TCP:可靠的,面向连接的协议(打电话),传输效率低全双工通信(发送缓存&接受缓存),面向字节流。使用TCP的应用:Web浏览器,电子邮件,文件传输程序

  • UDP:不可靠的,无连接的服务,传输效率高(发送前时延小),面向数据报,尽最大努力交付,无拥塞控制。使用UDP的应用:域名系统(DNS),视频流,IP语音(VoIP)

  • 每一条TCP连接只能是点到点的,但是对于UDP来说,支持一对一,一对多,多对一,以及多对多的交互通信

  • 对于TCP来说是可靠的全双工但是对于UDP来说是不可靠的全双工

  • TCP是基于字节流的,看成无结构的字节流进行传输,当应用程序交给TCP的数据长度太长,超过MSS是,TCP就会对数据进行分段,因此TCP的数据是无边界的;而UDP是面向报文的,无论应用程序交给UDP层多长的报文,UDP都不会对数据包进行任何拆分处理,因此UDP保留了应用层数据的边界

  • UDP可以进行广播,但是对于TCP不可以进行广播

  • 网络就是生产者和消费者模型

猜你喜欢

转载自blog.csdn.net/qq_40399012/article/details/87819605