linuxtcp图解
- tcp头部(20-60字节)
-
TCP端口号
TCP的连接是需要四个要素确定唯一一个连接:
(源IP,源端口号)+ (目地IP,目的端口号)
所以TCP首部预留了两个16位作为端口号的存储,而IP地址由上一层IP协议负责传递
源端口号和目地端口各占16位两个字节,也就是端口的范围是2^16=65535
另外1024以下是系统保留的,从1024-65535是用户使用的端口范围 -
TCP的序号和确认号:
32位序号 seq:Sequence number 缩写seq ,TCP通信过程中某一个传输方向上的字节流的每个字节的序号,通过这个来确认发送的数据有序,比如现在序列号为1000,发送了1000,下一个序列号就是2000。
32位确认号 ack:Acknowledge number 缩写ack,TCP对上一次seq序号做出的确认号,用来响应TCP报文段,给收到的TCP报文段的序号seq加1。 -
TCP的标志位
每个TCP段都有一个目的,这是借助于TCP标志位选项来确定的,允许发送方或接收方指定哪些标志应该被使用,以便段被另一端正确处理。
用的最广泛的标志是 SYN,ACK 和 FIN,用于建立连接,确认成功的段传输,最后终止连接。-
SYN:简写为
S
,同步标志位,用于建立会话连接,同步序列号; -
ACK: 简写为
.
,确认标志位,对已接收的数据包进行确认; -
FIN: 简写为
F
,完成标志位,表示我已经没有数据要发送了,即将关闭连接; - PSH:简写为
P
,推送标志位,表示该数据包被对方接收后应立即交给上层应用,而不在缓冲区排队; - RST:简写为
R
,重置标志位,用于连接复位、拒绝错误和非法的数据包; - URG:简写为
U
,紧急标志位,表示数据包的紧急指针域有效,用来保证连接不被阻断,并督促中间设备尽快处理;
-
SYN:简写为
TCP三次握手
-
客户端向服务器发来请求,SYN为1(表示建立连接),seq随机生成一个序列号J,客户端进入SYN_SENT状态。
-
服务器在收到请求时,发出应答包,SYN=1,ACK=1(表示已接受到数据包),返回确认号ack=J+1,生成一个序列号seq=K
-
客户端收到数据包,判断ack=J+1,ACK=1是否正确,正确返回ACK为1,ack为K+1的数据包,进入
ESTABLISHED(已连接)。服务器判断发来数据包是否正确,然后也进入ESTABLISHED状态
TCP四次挥手
两端都可以发送断开请求,以客户端为例
- 客户端发送一个FIN=M(finish请求断开),进入FIN_WAIT1状态
- 服务器收到断开请求,发一个ack=M+1,等待自己确认服务端发送完数据,进入CLOSE_WAIT,客户端收到进入FIN_WAIT2
- 服务端发送完数据,发一个FIN=N,进入LAST_WAIT
- 客户端收到之后,进入TIME_WAIT状态,发一个ack = N+1,ACK = 1.服务端接收到断开连接,客户端等待2MSL后没有收到数据,就断开连接,(MSL是数据包在网路中失效的最大时间单位)
为什么要等待2MSL?
有以下两个原因:
-
第一点:保证TCP协议的全双工连接能够可靠关闭:
由于IP协议的不可靠性或者是其它网络原因,导致了Server端没有收到Client端的ACK报文,那么Server端就会在超时之后重新发送FIN,如果此时Client端的连接已经关闭处于CLOESD
状态,那么重发的FIN就找不到对应的连接了,从而导致连接错乱,所以,Client端发送完最后的ACK不能直接进入CLOSED
状态,而要保持TIME_WAIT
,当再次收到FIN的收,能够保证对方收到ACK,最后正确关闭连接。 -
第二点:保证这次连接的重复数据段从网络中消失
如果Client端发送最后的ACK直接进入CLOSED
状态,然后又再向Server端发起一个新连接,这时不能保证新连接的与刚关闭的连接的端口号是不同的,也就是新连接和老连接的端口号可能一样了,那么就可能出现问题:如果前一次的连接某些数据滞留在网络中,这些延迟数据在建立新连接后到达Client端,由于新老连接的端口号和IP都一样,TCP协议就认为延迟数据是属于新连接的,新连接就会接收到脏数据,这样就会导致数据包混乱。所以TCP连接需要在TIME_WAIT状态等待2倍MSL,才能保证本次连接的所有数据在网络中消失。
确认应答机制
根据前面的知识,就是ack+1 的应答,当发数据端发1000数据,接收端就会告诉对方我需要发1001数据,下一次就从1001继续发送。
滑动窗口
如果是一发一收的性质的话,效率太差,tcp提供滑动窗口就是实现快速发送。
就是开辟了缓冲区,来确认那些数据没有发送,哪些数据可以从缓冲区删除了。
如果在这种情况中出现了丢包现象,应该如何重发呢?
数据到达接收方,但是应答报文丢失:可以更具后边的ACK确认。假设发送方发送1-1000的数据,接收方收到返回确认ACK,但是返回的ACK丢失了,另一边发送1001-2000收到的确认ACK 2001,就可以认为1-1000数据接收成功 。
数据包之间丢失: 当某一段报文段丢失之后,发送端会一直收到 1001 这样的ACK,就像是在提醒发送端 "我想要的是 1001" 一样,如果发送端主机连续三次收到了同样一个"1001" 这样的应答,就会将对应的数据 1001 - 2000 重新发送,这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了。因为2001 - 7000接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中。这种机制被称为 “高速重发控制”(也叫 "快重传")。
快恢复(与快重传配合使用)
采用快恢复算法时,慢开始只在TCP连接建立时和网络出现超时时才使用。
当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。
考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。
超时重传机制
发送端没有收到应答的数据,就会重新发送,有两种情况
1、接收端就没有收到数据,这时候发送端看时间到了,就会重新发送数据给接收端
2、当接收端收到数据,在应答包发送的时候出现了丢包,这个时候发送端没有收到应答包还是会重新发送,但是接受端已经有数据了,这是就根据序列号来识别是否是重复的数据。
如何界定这个特定的时间重传
最理想的情况下,找到一个最小的时间保证确认应答一定能在这时间内返回
但是这个时间的长短,随着网络环境的不同,也是有差异的。
如果超时时间设得太长,会影响整体的重传效率
如果超时时间设的太短,有可能平凡的发送冲符的数据包。
TCP为了抱枕个无论在何种环境下都能比较高性能的通信,因此会动态计算这个最大超时时间:
Linux中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
如果重发一次,任然得不到应答,就等待2500ms后在进行重传
如果还得不到应答,等待4500ms再重传,一次类推,以指数形式递增。
累积到一定的重传次数,TCP认为网络或者对端主机出现异常,就会强制关闭连接。
流量控制
当发送端发送速度过于快速,接收端缓冲区满的时候,这是在发送数据就会出现丢包的现象,tcp会自动根据接收端的窗口大小,进行数据的大小的调整,这时候就要用到tcp报头中的窗口的16字节的大小了,窗口数字越大,就证明接收端数据吞吐量越大。如果接收缓冲区满了,就会将窗口置为0,这时发送方不再发送数据。但是需要定期的发送一个试探窗口,目的是为了取探测数据段,是接收端把窗口大小告诉发送端。
拥塞控制
如果网路中的网络状态不好,这是服务器发送大量的数据就会使网路跟家的阻塞,这是tcp就引入了慢启动机制。先发少量的数据,后面在逐渐增加。
这种增加数据的吞吐量是一直指数形式增加的,但是不会这样一直增加,tcp有个叫慢启动阈值,当到这个数值时候,增长的就会以线性增长
- 当
TCP
开始启动的时候,慢启动阈值
等于窗口最大值 - 在每次
超时重发
的时候,慢启动阈值
会变成原来的一半同时拥塞窗口置回1
延迟应答
就是加入这次缓冲区收到500k数据,立即返回应答包,返回的窗口大小也是500,但是如果等他个十几毫秒,就会收到1M数据,就会返回窗口大小更大,服务端就会发送很多数据过来,吞吐量就很大。效率更高。
- 数量限制: 每隔
N个包
就应答一次 - 时间限制: 超过大
延迟时间
就应答一次
注:具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2
, 超时时间取200ms
捎带应答
就是在延迟应答基础上,看下你的网络状况是不是很好,接收端就会在ACK中捎带应答,自己的网络状况。
如何避免粘包问题呢?明确两个包之间的边界
对于定长的包,保证每次都按固定大小读取即可。例如一个Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。
对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议是程序员自己来定义的, 只要保证分隔符不和正文冲突即可)。
对于UDP协议,如果还没有上层交付数据, UDP的报文长度仍然在。 同时UDP是一个一个把数据交付给应用层,这样就有存在明确的数据边界,站在应用层的角度, 使用UDP的时候要么收到完整的UDP报文要么不收,不会出现"半个"的情况。
TCP连接异常情况:
进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别。机器重启和进程终止一样。
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。应用层的某些协议, 也有一些这样的检测机制.例如HTTP长连接中, 也会定期检测对方的状态.Q在QQ 断线之后, 也会定期尝试重新连接。
参考:
https://blog.csdn.net/hansionz/article/details/86435127
https://blog.csdn.net/qq_40927789/article/details/80607610