3.1说说TCP的头部格式

image.png
(1)序列号(32位):在连接建立时,由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机,每发送一次数据,就累加一次该数据字节数的大小,主要是用来解决网络包乱序问题。
(2)确认应答号(32位):指下一个期望收到的数据的序列号,发送端接收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收,用来解决不丢包的问题。
(3)控制位:
ACK:该位为1时,确认应答的字段有效,TCP规定除了最初建立连接时的SYN包之外,该位必须设置为1。
RST:该位为1时,表示TCP连接中出现异常必须强制断开。
SYN:该位为1时,表示希望建立连接,并在其序列号字段进行序列号初始值的设定。
FIN:该位为1时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方主机之间就可以相互发送FIN位为1的TCP段。

3.2为什么需要TCP协议?TCP工作在哪一层?

因为IP层是不可靠的,它不保证网络包的交付、不保证网络包的按序接收、也不保证网络包中的数据完整性。
如果需要保障网络数据包的可靠性,那么就需要传输层的TCP协议来负责。因为TCP是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是不重复、不丢失、按序接收、可靠传输的。

3.3什么是TCP?

TCP是面向连接的、可靠的、基于字节流的传输通通信协议。
(1)面向连接:一定是一对一才能连接,不能像UDP协议那样可以一个主机向多个主机发送消息,一对多是无法做到的。TPC四元组可以唯一确定一个连接。源地址和目标地址的字段(32)是在IP头部,作用是通过IP协议发送给对方主机,源端口和目标端口的字段(16位)是在TCP头部中,作用是告诉TCP协议应该把报文发给哪个进程。
image.png
(2)可靠的:无论网络链路中出现了怎样的链路变化,TCP都可以保证一个报文一定能够到达接收端。
(3)字节流:无论我们消息有多大都可以进行传输。并且消息是有序的,当前一个消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对重复的报文会自动丢弃。

3.4有一个IP的服务器监听了一个端口,它的TCP最大连接数是多少?

服务器通常固定在某个本地端口上监听,等待客户端的连接请求。因此,客户端IP和端口是可变的。
image.png
对IPV4,客户端的IP数最多为2^32,客户端的端口数最多为2^16,也就是说服务器单机最大TCP连接数,约为2^48。
当然,服务器最大并发TCP连接数不能达到理论上限,因为每个TCP连接都要占用一定内存,操作系统的内存是有限的。

3.5UDP和TCP有什么区别?分别的应用场景是什么?

UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。UDP协议也非常简,头部只有8个字节(64位)。16位源端口、16位目的端口、16位包长度、16位校验和。
image.png
目的端口和源端口:告诉UDP协议应该把报文发给哪个进程。
包长度:保存了UDP首部的长度与数据长度之和。
校验和:为了提供可靠的UDP首部和数据而设计。
TCP的头部有一个首部长度字段,这是因为TCP有可变长度的[选项]字段,UDP头部长度是不会变化的,所以不需要首部长度的字段来记录。
区别:
(1)连接:
TCP是面向连接的传输层协议,传输数据之前需要先建立连接。
UDP不需要连接,即刻传输数据。
(2)服务对象:
TCP是一对一的两点服务。
UDP支持一对一、一对多、多对一的交互通信。
(3)可靠性:
TCP是可靠交付数据,数据可以无差错、不丢失、不重复、按序到达。
UDP是尽最大努力交付,不保证可靠交付数据。
(4)拥塞控制、流量控制
TCP有拥塞控制和流量控制机制,保证数据传输的安全性。
UDP没有,即使网络拥堵,也不会影响UDP的发送速度。
(5)首部开销
TCP首部较长,会有一定的开销,首部在没有使用[选项]字段时是20字节,如果使用了会更长。
UDP首部只有8个字节,并且是固定不变的,开销较小。
(6)传输方式
TCP是流式传输,没有边界,保证顺序和可靠。
UDP是一个包一个包的发送,是有边界的,有可能丢包和乱序。
(7)分片不同
TCP的数据大小如果大于MSS,则会在传输层进行分片,目标主机收到后,也同样在传输层组装TCP数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
UDP的数据大小如果大于MTU,则会在IP层进行分片,目标主机收到后,在IP层组装完数据,接收再传给传输层,但是如果中途丢了一个分片,在实现可靠传输的UDP时则就需要重传所有的数据报,这样传输效率非常差,所以通常UDP的报文应该小于MTU。
【应用场景】:
由于TCP是面向连接的,能保证数据的可靠性交付,因此经常用于:FTP文件从传输、HTTTP和HTTPS。
由于UDP是面向无连接,它可以随时发送数据,再加上UDP本身的处理简单又高效,因此经常用于:
对网络通讯质量要求不高,要求网络通讯速度能尽量的快,QQ语言、QQ视频
1.数据包总量较小的通信,如DNS、SNMP等;
2.视频、音频等多媒体通信;
3.广播通信;

3.6说说TPC是如何计算负载数据长度的

image.png
其中IP的总长度和IP首部长度,在IP首部格式中是已知的。TCP首部长度,在TCP首部格式的字段上也是已知的,所以就可以求出TCP数据的长度。

3.7说说TCP连接建立的过程

TCP是面向连接的协议,所以使用TCP前必须先建立连接,而建立连接是通过三次握手来进行的。
image.png
(1)一开始,客户端和服务器都处于Closed状态,服务器如果主动监听某个端口的话,就处于Listen状态。
(2)客户端会随机初始化序列号(client_isn),将序列号置于TCP首部的[序号]字段中,同时把SYN标志位置为1,表示SYN报文。接着把第一个SYN报文发送给服务器,表示向服务器发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT状态。
(3)服务器收到客户端的SYN报文,首先服务器也随机初始化自己的序号(server_isn),将此序号填入TCP首部的序列号字段,同时把SYN标志位置为1,另外把接收到的客户端的client_server+1置于TCP首部的确认应答号字段,把ACK标志位置为1,最后把该报文发送给客户端。该报文也不包含应用层数据,之后服务端处于SYN-RCVD状态。
(4)客户端接收到服务端的报文后,还要向服务端发送最后一个应答报文,首先该应答报文TCP首部ACK标志位置为1,其次确认应答号字段的值填入server_isn+1,最后把报文发送给服务端,这次报文可以携带到服务端的数据,之后客户端处于Established状态。
(5)服务器收到客户端的应答报文后,也处于Established状态。

3.8如何在Linux系统中查看TCP状态:

image.png 通过Linux指令netstat-napt可以查看。

3.9为什么是三次握手?不是两次、四次?

(1)三次握手才可以阻止重复历史连接的初始化。
网络环境是错综复杂的,往往并不是像我们期望的一样,先发送的数据包,就先到达目的主机。可能会由于网络拥堵等乱七八糟的原因会使得旧的数据包,先到达目的主机。打个比方:客户端连续发送多次SYN建立连接的报文,在网络拥堵的情况下:
一个旧的SYN报文比最新的SYN报文早到达服务端;
此时服务端就会回复一个SYN+ACK报文给客户端;
客户端收到后可以根据自身的上下文来判断,这是否是一个历史连接,如果是历史连接的话,客户端就可以发送RST报文给服务端,表示中止这一次连接。如果不是历史连接,第三次发送ACK报文,通信双方就能够成功建立连接。
(2)三次握手才可以同步双方的初始序列号。
TCP协议的通信双方,都必须维护一个序列号,序列号是可靠传输的一个关键因素,它的作用是:
【1】接收方可以去除重复的数据;
【2】接收方可以根据数据包的序列号按序接收;
【3】可以表示发送出去的数据,哪些是已经被对方收到的;
三次握手,才能确保双方的初始序列号能被可靠的同步。
(3)三次握手才可以避免资源浪费。
如果只有两次握手,当客户端的SYN请求连接在网络中阻塞的时候,客户端没有接收到ACK响应报文,就会重新发送SYN,由于没有第三次握手,服务端也不清楚客户端是否接收到了自己发送的建立连接的ACK确认信号,所以每收到一个SYN就建立一次连接,就会造成建立很多个冗余的无效连接,造成不必要的资源浪费。
image.png

3.10为什么客户端和服务端的初始序列号ISN是不相同的?

如果是一个失效的连接被重用了,但是该旧连接的历史报文还残留在网络中,如果序列号相同的话,即无法分辨出该报文是不是历史报文,如果历史报文被新的连接接收了,就会造成数据错乱。
所以每次建立连接前都需要重新初始化一个序列号,主要是为了通信双方能够根据序号的不同将不属于本次连接的报文段丢弃。

3.11初始化序列号是如何产生的?

ISN=M+F(localHost,localPort,remoteHost,remotePort)
M是一个计时器,这个计时器每隔4毫秒+1。
F是一个Hash算法,根据源端口、源IP、目的IP、目的端口生成一个数值,然后根据md5算法加密。

3.12既然IP层会分片,为什么TCP层还需要MSS呢?

image.png
MTU:一个网络包的最大长度,以太网中一般为1500字节。MTU=IP头部+TCP头部+数据部分
MSS:MTU-IP头部-TCP头部,一个网络包能容纳的TCP数据的最大长度。
如果不在传输层进行分片,当IP层由一个超过MTU大小的数据要发送的话,就需要IP层来分片,把数据分成若干片,保证每一个分片都小于MTU。把一份IP数据报进行分片以后,由目的主机的IP层进行重新组装再交付给上层的TCP传输层。
这看起来井然有序,但是这是存在隐患的,如果一个IP分片丢失,整个IP报文的所有分片都需要重传。因为IP层本身没有超时重传的机制,需要传输层的TCP来负责超时和重传。当接收方发现TCP报文的某一片丢失去,就不会响应ACK给对方,或者响应重复的ACK给对方。那么发送方的TCP层在经过超时或多次重复ACK的确定以后,就会重发整个TCP报文。所以为了达到最佳的传输效率,TCP协议在建立连接的时候通常需要协商双方的MSS值,当TCP层发现数据超过MSS时,就会先进行分片,由于交付给下层IP层以后大小不会超过MTU,就不需要IP层再来分片了。

3.13什么是SYN攻击?

SYN攻击就是通过TCP建立连接需要三次握手,攻击者在短时间内伪造不同IP地址的报文,服务器每接收到一个SYN报文,就进入SYN_RCVD状态,但服务端发送出去的SYN+ACK报文,无法得到为止IP主机的ACK应答,久而久之就会沾满服务端的SYN未连接队列,使得服务端不能为正常用户服务。
解决方案:
(1)修改Linuxs内核参数,控制队列大小和当队列满时应该做什么处理。
(2)正常流程是:
1.当服务端接收到客户端的SYN报文后,会将其加入内核的SYN队列;
2.接着发送SYN+ACK报文给客户端,等待客户端回应ACK报文;
3.服务端接收到ACK报文后,从SYN队列中移除并加入Accept队列;
4.应用通过调用accept() socket接口,就可以从Accept队列中取出连接;
当应用程序过慢会导致Accept队列被占满,当受到Syn攻击会导致Syn队列被占满。
处理SYN功能的方式,可以修改内核参数tcp_syncookies。
1.当Syn队列满了以后,后续服务器接收到SYN包,不进入SYN队列;
2.计算出一个cookie值,再以SYN+ACK中的序列号返回客户端
3.服务端接收到客户端的应答报文后,会检查这个ACK包的合法性。如果合法,直接放入到Accept队列中。

3.14说说TCP连接断开的过程

image.png
(1)客户端打算关闭连接,此时会发送TCP首部FIN标志位为1的报文,FIN报文,之后客户端进行FIN_WAIT_1状态。
(2)服务端接收到该报文后,就向客户端发送ACK应答报文,接着服务端进入Closed_wait状态。客户端收到服务端的ACK应答报文后,之后进入FIN_WAIT_2状态。
(3)等待服务端处理完处理数据以后,也向客户端发送一个FIN报文,之后服务端进入LAST_ACK状态,等待最后一次客户端的应答报文。
(4)客户端收到了FIN报文后,回一个ACK应答报文,之后进入Time_wait状态,服务器收到了ACK报文后,就进入了Closed状态,服务端已经完成连接的关闭。客户端在经过2MSL一段时间后,自动进入Closed状态,客户端也完成连接的关闭。
每个方向有一个FIN和一个ACK报文,所以是四次挥手。并且主动关闭连接的一方,才会有Time_wait状态。

3.15为什么挥手需要四次?

关闭连接时,客户端发送FIN时,仅仅表示客户端不再发送数据了但还能接收数据。
服务端收到客户端的FIN报文后,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文表示同意现在关闭连接。所以服务端的ACK报文和FIN报文一般都会分开发送,从而比三次握手多一次。

3.16为什么Time_wait等待的时间是2MSL?

MSL是报文最大生存时间:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接受方处理后又会向对方发送响应,这样一来一回需要等待两倍的时间。
打个比方,比如被动关闭方没有收到断开连接的ACK报文,就会触发重传FIN报文,主动关闭方接受到重复的FIN报文后,就知道之前发送的ACK报文对方没有接收到,就需要重新发送ACK报文后被动关闭方,这样正好是2个MSL。并且2MSL是在主动关闭方发送ACK报文后开始计时的,如果接收到被动关闭重传的FIN报文后,Time_wait的时间会重新开始计时。
原因1【防止旧连接的数据包】
如果是旧连接是数据包在服务端关闭之前因为网络延迟的原因,一直没有到达接收方,但是FIN包又先到达了接收方,接收方回复了ACK之后,等待极少时间或者不等待就关闭连接,处于closed状态,服务端也处于closed状态。如果相同端口的TCP连接又被复用后,就会导致在三次握手以后有可能接收方会接收到旧连接因网络延迟的数据包,导致数据错乱的问题。而2MSL的等待时间足以让两个方向的数据包都被丢弃,使得旧连接的数据包在网络中自然消失,再出现的数据包一定都是新连接的数据包产生的。
原因2【主动关闭方必须帮助被动关闭方完成关闭连接】
也就是说,主动关闭方发送了ACK报文后,需要等待2MSL这个时间,让被动关闭方正确关闭,因为有可能会由于网络延迟的原因导致这个ACK报文被动关闭方并没有正确接收到触发重传机制。

3.17如果time_wait过多会有什么危害?

如果是客户端的time_wait状态过多,如果占满所有端口,会导致无法创建新连接。因为端口就65536个,被占用就无法创建。
如果是服务端time_wait状态过多的话,说明主动关闭的一方是服务端,第一是占用本地内存资源,第二是占用端口资源,一个TCP连接至少消费一个本地端口,服务端线程池处理不了那么多一直不断开的连接,导致系统资源被占满。

3.18说说TCP的重传机制

image.png
但在错综复杂的网络环境中,不是每一次都能顺利地进行数据传输,万一数据在传输过程中丢失了呢?所以针对TCP数据包丢失的情况,会用重传机制解决。重传机制有超时重传、快速重传、Sack、D-Sack。
【超时重传】
重传机制的一种,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发该数据,也是我们常说的超时重传。如果是发送方的SYN数据包丢失,会触发数据包重传;如果是接收方的ACK数据包丢失,会触发确认应答重传
image.png

3.19超时重传的时间应该设置为多少?

超时时间可以设置成稍微大于报文往返RTT的值。
image.png
当超时时间RTO较大时,重发就慢,丢了老半天才重发,没有效率性能差;
当超时时间RTO较小时,会导致可能还没有丢就重发,于是重发得越快,会增加网络拥塞,导致更多的超时,更多超时导致更多的重发。

3.20说说TCP的快速重传机制

TCP还有另外一种快速重传机制,它不以时间为驱动,而是以数据驱动重传。
image.png
比如上图,第一份seq先送到了,但是ack回2;结果Seq2因为某些原因没有收到,Seq3、Seq4、Seq5都到达了,但ack还是回2;发送端收到了三个Ack=2的确认,知道了Seq2还没有收到,就会在定时器过期之前,重传丢失的Seq2。

3.21说说Sack方法

Sack是实现重传机制的一种方式,选择性重传。这种方式需要在TCP头部选项的字段里面加一个Sack的东西,它可以将缓存的地图发生给发送方,这样发送就可以知道哪些数据收到了, 哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
image.png
通过Sack的信息,发送方就能知道接收方200-299的这段数据丢失了,则重发时,就会只选择这个TCP片段进行重发。

3.22说说D-Sack方法

ACK包丢失:
image.png
接收方发给发送的两个ACK报文都丢失了,所以在发送方超时重传以后,重发第一个数据包(3000-3499);
于是接收方发现数据是重复收到的,于是回了一个Sack=3000-3500,告诉接收方3000-3500的数据早已被接受了,因此ack都到4000了,意味着4000之前的所有数据都已收到,所以这个Sack就代表着D-Sack。
网络延迟:
image.png
数据包(1000-1499)被网络延迟了,导致发送方没有收到ACK=1500的确认报文。
而后面的报文到达的三个相同ACK确认报文,就触发了快速重传机制,但是在重传后,被延迟的数据包又被接受到了,就代表收到了重复的包。这样发送方就知道快速重传触发的原因不是发出去的包丢失了,也不是因为回应的ACK包丢了,而是因为网络延迟了。
D-Sack的好处:
(1)可以让发送方知道,是自己发出去的包丢失了,还是接受方的ACK包丢失了。
(2)可以知道是不是发送方的数据包被网络延迟了;

3.23说说TCP滑动窗口的机制

TCP是每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了,再发送下一个。这个模式有点像面对面聊天,你一句我一句,但这种方式的缺点是效率比较低。
image.png
引入了滑动窗口的机制,可以指定窗口的大小,无需等待确认应答就可以继续发送数据的最大值。假设指定窗口大小为3个TCP段,那么发送方就可以连续发送3个TCP段,并且中途如果有ACK丢失,可以通过下一个确认应答进行确认。
image.png
如上图,即使ACK600丢失了,但是只要ACK700到达了,发送方就可以认为ACK700之前的数据都已到达。

3.24TCP窗口大小是由哪一方决定的?

TCP头部里有一个字段叫Window,也就是窗口大小。这个字段是接收方告诉发送端自己还有多少缓冲区可以接受数据,于是发送方就可以根据接收方的处理能力来发送数据,而不是导致接收端处理不过来。所以,通常窗口的大小是由接收方的窗口大小来决定的,发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

3.25发送方的滑动窗口

image.png
发送方的滑动窗口可分为四段:
#1:已发送并收到ACK确认的数据;(已接收窗口)
#2:已发送但未收到ACK确认的数据;(已发送但未接收窗口)
#3:未发送但大小在接收方的处理范围之内的数据;(可用窗口)
#4:未发送但总大小超过接收方处理范围的数据;(暂不可用窗口)
如果发送方一下可用窗口的6字节数据全部发送出去,那么发送窗口将占满32-51,可用窗口为0,但发送方接收到ACK=36的确认应答报文后,会把窗口整体向右滑动5字节,可用窗口又变成了5字节。
image.png
TCP滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节,其中两个指针是绝对指针(指向特定的序列号),另外一个是相对指针(需要做偏移)。
snd.wnd:表示发送窗口的大小,大小是由接收方指定的。
snd.una:是一个绝对指针,它指向已发送但未确认的第一个字节的序列号;
snd.nxt:也是一个绝对指针,它指向未发送但发送范围的第一个字节的序列号;
最后一个相对指针指向未发送窗口的第一个指针,计算方式是(snd.wnd-(snd.next-snd.una)),是一个偏移量。

3.26接收方的滑动窗口

image.png
接收窗口主要是由三部分来组成的:已接收窗口、可用窗口、暂不可用窗口
rcv.wnd:表示接收窗口的大小,它会通告给发送方;
rcv.net:是一个指针,它指向期望从发送方发送过来的下一个数据字节的序列号,也就是#3的字节。
#4也是一个相对指针,rcv.wnd+rcv.net,通过计算偏移量可用指向#4的第一个字节。

3.27接收方的滑动窗口与发送方的滑动窗口相等吗?

并不是完全相等的,接收窗口的大小是约等于发送窗口的大小的。
因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据非常快的话,这样接收窗口可以很快就空缺出来。那么新的接受窗口大小,是通过TCP报文的windows字段来告诉发送方。这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

3.28说说TCP的流量控制机制

发送方不能无脑的发数据给接收方,要考虑接收方处理能力。如果一直无脑得发数据给对方,但对方处理不过来,那么就会导致触发重传机制,从而导致网络流量的无故浪费。为了解决这种现象的发生,TCP提供一种机制可以让发送方根据接收方的实际接收能力控制发送的数据量。
举个例子,客户端是接收方,服务端是发送方。假设接收窗口和发送窗口相同,都是200。
image.png
image.png
image.png

3.29操作系统缓冲区与滑动窗口的关系

实际上,发送窗口和接收窗口中所存放的字节数,都是放在操作系统的内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。当应用进程没有办法读取缓冲区的内容时,也会对我们的缓冲区造成影响。
image.png
另外一种情况是,当服务端资源非常紧张的时候,操作系统可能会直接减少接收缓冲区的大小,这时应用程序又无法及时读取缓存数据,就会造成数据包丢失现象。
image.png
为了防止这种情况发生,TCP规定是不允许同时减少缓存又收缩窗口的,而是先收缩窗口,过段时间再减少缓存,这样就避免了丢包情况。

3.30窗口关闭

TCP通过让接收方向发送方说明自己能够接收的数据大小,来进行流量控制。如果窗口大小为0,就会阻止发送方给接收方传递数据,直到窗口变为非0为止,这就是窗口关闭。
那么,当发送窗口关闭时,处理方式处理完数据后,会向发送方通告一个窗口非0的ACK报文,如果这个通告窗口的ACK报文在网络中丢失了,那麻烦就大了。发送方一直等待接收方的非0窗口,接收方也一直等待发送方发送来的数据。
image.png

3.31TCP是如何解决窗口关闭时潜在的死锁现象呢?

为了解决这个问题,TCP为每个连接设有一个持续定时器,只要TCP连接一方收到对方的0窗口通知,就会启动持续计时器。如果持续计时器超时,就会发送窗口探测报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
image.png
如果接收窗口依然为0,那么收到这个报文的一方就会重新启动持续计时器;
如果接收窗口不是0,发送方就又可以发送数据给接收方了。
另外窗口探测的次数一般是3次,每次大约30-60s。如果3次后接收窗口还是0的话,有的TCP实现就会发送RST报文来中断连接。

3.32说说什么是糊涂窗口综合症

如果接收方太忙了,来不及取走接收窗口的数据,那么就会导致发送方的窗口越来越小。
到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节的数据。这就是糊涂窗口综合症。要知道我们的TCP头部+IP头部有40个字节,为了传输那几个字节的数据,要搭上这么大的开销,就太不划算了。这就好像是一辆大巴车每次来一个人就发车。所以要解决这个问题,需要从根源下手:1.阻塞接收方通告一个小窗口 2.阻塞发送方发送小数据。
【怎么让接收方不通告小窗口】
当窗口大小小于Math.min(MSS,缓存空间/2),也就是小于MSS与1/2缓存大小的最小值时,就会向发送方通告窗口为0,阻塞发送方再发送数据过来。等到接收方处理了一些数据后,窗口大小>=MSS,或者接收方缓存空间有一般可以使用,就可以把窗口打开让发送方发送数据过来。
【怎么让发送方避免发送小数据】
使用Nagle算法,该算法的思路是延时处理,它满足两个要求之一才可以发送数据:
1.要等到窗口大小>=MSS或者是数据大小>=MSS
2.收到之前发送数据的ack回包。

3.33说说既然有流量控制了,为什么还要有拥塞控制?

流量控制是避免发送方无脑发送数据,填满接收方的缓存,但是不知道网络中发生了什么。
一般来说,计算机网络都处在一个共享的环境中。因此也有可能会因为其他主机之间的通信使得网络拥堵。
在出现网络拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时TCP就会重传数据,但是一重传就会导致网络的复旦更中,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大。所以TCP不能忽略网络上发生的事,当网络发送拥塞时,TCP会自我牺牲,降低发送的数据量。所以拥塞控制的目的是为了避免发送方的数据填满整个网络。

3.34什么是拥塞窗口?和发送窗口有什么关系呢?

拥塞窗口cwnd是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化。其实发送方的发送窗口=Math.min(cwnd,rwnd)。也就是拥塞窗口和接收窗口中的最小值。只要网络中没有出现拥塞,cwnd就会增大;但是一旦网络中出现了拥塞,cwnd就会减少。
只要发送方没有在规定的时间内接收到ACK应答报文,也就是触发了超时重传机制,就可以认为网络中出现了拥塞。

3.35拥塞控制有哪些控制算法?

拥塞控制主要是四个算法:慢启动、拥塞避免、拥塞发生、快恢复。
【慢启动】
TCP在刚建立连接完成后,首先是有个慢启动的过程,这个慢启动的意思就是一点一点的提高发送数据包的数量,如果一上来就发大量的数据,这不是给网络添堵吗?慢启动的算法就是:当发送方每收到一个ACK,拥塞窗口cwnd的大小就2;假设拥塞窗口cwnd和发送窗口swnd相等。
image.png
(1)连接刚建立完成后,一开始cwnd=1,可以发送一个MSS大小的数据;
(2)当发送方收到一个ACK确认应答后,cwnd就
1,于是就能一次发送2个MSS大小的数据;
(3)当收到第2个ACK确认应答后,cwnd再*2,就可以发送4个MSS大小的数据;1 2 4 8就这样指数增加
当cwnd达到慢启动门限状态变量的时候就会启动拥塞避免算法。所以当cwnd=ssthresh时,就会使用拥塞避免算法。
【拥塞避免算法】
一般来说ssthresh的大小是65535字节。那么当拥塞避免算法发生后,它的规则是每收到一个ACK,cwnd就+1。
举个例子:比如现在的ssthresh的大小是8,cwnd也是8就达到了慢启动门限,进入拥塞避免算法。这时候当8的确认应答到来的时候。cwnd就会+1,这次就能发送9个MSS大小的数据,变成了线性增长。
image.png
所以我们可以发现,拥塞避免算法就是将原本慢启动算法的指数增长变成了线性增长,但还是增长阶段,但是增长的速度就缓慢了一些。就这么一直增长下去,网络慢慢就会进入拥塞的状况了,于是就会出现网络延时丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了拥塞发生算法。
【拥塞发生算法】
重传机制主要有两种超时重传和快速重传,所以对应的拥塞发生算法也有两种。
(1)触发超时重传时
慢启动门限ssthresh变成现在的cwnd/2,cwnd重新置为1。
image.png
打个比方,如果在cwnd为12的时候出现了网络丢包现象,触发了发送方的超时重传。慢启动们门限的阈值就变成现在的cwnd/2=6,拥塞窗口重新置为1,接着又回到慢启动的过程。这种方式的反映太激烈了,会造成网络卡顿。
【快恢复算法】
快速重传机制一般和快恢复算法同时使用,快恢复算法认为,发送方还能接收到三个重复的ACK确认说明网络也不是那么糟糕,所以没有必要像拥塞发生算法那样反映激烈。
image.png
在进入快恢复算法算法以后,cwnd和ssthresh已被更新了:cwnd=cwnd/2+3(表示有3个ack确认应答已收到了),也就是设置为原来的一半,ssthresh=发生重传时cwnd的一半;接着进入拥塞避免算法,不进入慢启动算法。之后还是线性增长的阶段。

3.36什么是TCP半连接队列和全连接队列

在TCP三次握手的时候,LInux内核会维护两个队列,分别是半连接队列(SYN队列)和全连接队列(accept队列);
服务端收到客户端的发起的SYN请求后,内核会把该连接存储到半连接队列,并向客户端响应SYN+ACK,接着客户端会返回ACK,服务端收到第三次握手的ACK后,内核会把连接从半连接队列中移除,然后把这个连接放入到全连接队列中,等待进程调用accept函数时把连接取出来。不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或者返回RST包。