TCP协议

TCP协议的特点是:面向连接、字节流和可靠传输。

使用TCP协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。

*
TCP协议采用返回确认机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功。

*
TCP协议采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段。

*
TCP报文段最终是以IP数据报发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还会对接收到的TCP报文段重排、整理,再交付给应用层

TCP会将数据放到缓冲区当中,接收端应用程序可以一次性将TCP接收缓冲区中的数据全部读出,也可以分多次读取。但是UDP每发送一个数据包,接收端必须及时针对每一个UDP数据报执行读操作(通过recvfrom系统调用),否则就会丢包。

TCP头部结构

*
16位端口号(port
number):告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。
所有知名服务使用的端口号都定义在/etc/services文件中。

*
32位序号(sequence
number):一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。假设主机A和主机B进行TCP通信,A发送给B的第一个TCP报文段中,序号值被系统初始化为某个随机值ISN(Initial
Sequence
Number,初始序号值)。那么在该传输方向上(从A到B),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。例如,某个TCP报文段传送的数据是字节流中的第1025~2048字节,那么该报文段的序号值就是ISN+1025。另外一个传输方向(从B到A)的TCP报文段的序号值也具有相同的含义。

*
32位确认号(acknowledgement
number):用作对另一方发送来的TCP报文段的响应。其值是收到的TCP报文段的序号值加1。假设主机A和主机B进行TCP通信,那么A发送出的TCP报文段不仅携带自己的序号,而且包含对B发送来的TCP报文段的确认号。反之,B发送出的TCP报文段也同时携带自己的序号和对A发送来的报文段的确认号。

*
4位头部长度(header length):标识该TCP头部有多少个32bit字(4字节)。因为4位最大能表示15,所以TCP头部最长是60字节。

*
6位标志位包含如下几项:

* URG标志,表示紧急指针(urgent pointer)是否有效。
* ACK标志,表示确认号是否有效。我们称携带ACK标志的TCP报文段为确认报文段。
*
PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在TCP接收缓冲区中)。
* RST标志,表示要求对方重新建立连接。我们称携带RST标志的TCP报文段为复位报文段。
* SYN标志,表示请求建立一个连接。我们称携带SYN标志的TCP报文段为同步报文段。
* FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段。
*
16位窗口大小(window size):是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver
Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。

*
16位校验和(TCP
checksum):由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。

*
16位紧急指针(urgent
pointer):是一个正的偏移量。它和序号字段的值相加表示最后一个紧急数据的下一字节的序号。因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移。TCP的紧急指针是发送端向接收端发送紧急数据的方法。

TCP连接

TCP三次握手

* 客户端向服务器发送一个连接请求,将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器,等待服务器确认。
*
服务器收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
* 客户端检查标志位,确认连接建立,返回数据包,ACK=1,此时已经可以携带数据
TCP四次挥手

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭

* Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
* Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1,Server进入CLOSE_WAIT状态。
* Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
*
Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
TCP超时重传

客户端在长时间未收到响应报文时会重传报文,例如,重传时间间隔由/proc/sys/net/ipv4/tcp_syn_retries内核变量所定义。每次重连的超时时间都增加一倍。在5次重连均失败的情况下,TCP模块放弃连接并通知应用程序。

TCP状态转移

TCP连接的任意一端在任一时刻都处于某种状态,当前状态可以通过netstat命令查看

客户端状态

客户端通过connect系统调用主动与服务器建立连接。connect系统调用首先给服务器发送一个同步报文段,使连接转移到SYN_SENT状态。此后,connect系统调用可能因为如下两个原因失败返回:

*
如果connect连接的目标端口不存在(未被任何进程监听),或者该端口仍被处于TIME_WAIT状态的连接所占用,则服务器将给客户端发送一个复位报文段,connect调用失败。
* 如果目标端口存在,但connect在超时时间内未收到服务器的确认报文段,则connect调用失败。

connect调用失败将使连接立即返回到初始的CLOSED状态。如果客户端成功收到服务器的同步报文段和确认,则connect调用成功返回,连接转移至ESTABLISHED状态。

当客户端执行主动关闭时,它将向服务器发送一个结束报文段,同时连接进入FIN_WAIT_1状态。若此时客户端收到服务器专门用于确认目的的确认报文段,则连接转移至FIN_WAIT_2状态。当客户端处于FIN_WAIT_2状态时,服务器处于CLOSE_WAIT状态,这一对状态是可能发生半关闭的状态。此时如果服务器也关闭连接(发送结束报文段),则客户端将给予确认并进入TIME_WAIT状态。

服务器状态

服务器通过listen系统调用进入LISTEN状态,被动等待客户端连接,因此执行的是所谓的被动打开。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核等待队列中,并向客户端发送带SYN标志的确认报文段。此时该连接处于SYN_RCVD状态。如果服务器成功地接收到客户端发送回的确认报文段,则该连接转移到ESTABLISHED状态。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。

当客户端主动关闭连接时(通过close或shutdown系统调用向服务器发送结束报文段),服务器通过返回确认报文段使连接进入CLOSE_WAIT状态。这个状态的含义很明确:等待服务器应用程序关闭连接。通常,服务器检测到客户端关闭连接后,也会立即给客户端发送一个结束报文段来关闭连接。这将使连接转移到LAST_ACK状态,以等待客户端对结束报文段的最后一次确认。一旦确认完成,连接就彻底关闭了。

TCP拥塞控制

滑动窗口

为了解决可靠传输以及包乱序的问题,TCP 引入滑动窗口的概念。在传输过程中,client 和 server 协商接收窗口 rwnd,再结合拥塞控制窗口
cwnd 计算滑动窗口 swnd。在 Linux 内核实现中,滑动窗口 cwnd 是以包为单位,所以在计算 swnd 时需要乘上 mss(最大分段大小)。

滑动窗口包含 4 部分:

* 已收到 ack 确认的数据;
* 已发还没收到 ack 的;
* 在窗口中还没有发出的(接收方还有空间);
* 窗口以外的数据(接收方没空间)。

滑动后的示意图如下(收到 36 的 ack,并发出了 46-51 的数据):

拥塞控制

RTT:RTT——Round Trip Time,也就是一个数据包从发出去到回来的时间。这样发送端就大约知道需要多少的时间。

拥塞的发生是因为网络上的包太多导致路由器总是丢包,拥塞控制是快速传输的基础。TCP不能忽略网络上发生的事情,而无脑地一个劲地重发数据,对网络造成更大的伤害。对此TCP的设计理念是:
TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲。就像交通阻塞一样,每个车都应该把路让出来,而不要再去抢路了。 一个拥塞控制算法一般包括慢启动算法、
拥塞避免算法、快速重传算法、快速恢复算法四部分。

慢启动算法

慢启动的意思是,刚刚加入网络的连接,一点一点地提速,不要一上来就像那些特权车一样霸道地把路占满。新同学上高速还是要慢一点,不要把已经在高速上的秩序给搞乱了。

慢启动的算法如下(cwnd全称Congestion Window):

1)连接建好的开始先初始化cwnd = 1,表明可以传一个MSS大小的数据。

2)每当收到一个ACK,cwnd++; 呈线性上升

3)每当过了一个RTT,cwnd = cwnd*2; 呈指数让升

4)还有一个ssthresh(slow start threshold),是一个上限,当cwnd >= ssthresh时,就会进入“拥塞避免算法”

所以,我们可以看到,如果网速很快的话,ACK也会返回得快,RTT也会短,那么,这个慢启动就一点也不慢。下图说明了这个过程。

 拥塞避免算法

前面说过,还有一个ssthresh(slow start threshold),是一个上限,当cwnd >=
ssthresh时,就会进入“拥塞避免算法”。一般来说ssthresh的值是65535,单位是字节,当cwnd达到这个值时后,算法如下:

1)收到一个ACK时,cwnd = cwnd + 1/cwnd

2)当每过一个RTT时,cwnd = cwnd + 1

这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。很明显,是一个线性上升的算法。

拥塞状态时的算法

前面我们说过,当丢包的时候,会有两种情况:

1)等到RTO超时,重传数据包。TCP认为这种情况太糟糕,反应也很强烈。

*
* sshthresh =  cwnd /2
* cwnd 重置为 1
* 进入慢启动过程
2)Fast Retransmit算法,也就是在收到3个duplicate ACK时就开启重传,而不用等到RTO超时。

*
* TCP Tahoe的实现和RTO超时一样。
*
*
TCP Reno的实现是:

* cwnd = cwnd /2
* sshthresh = cwnd
* 进入快速恢复算法——Fast Recovery

上面我们可以看到RTO超时后,sshthresh会变成cwnd的一半,这意味着,如果cwnd<=sshthresh时出现的丢包,那么TCP的sshthresh就会减了一半,然后等cwnd又很快地以指数级增涨爬到这个地方时,就会成慢慢的线性增涨。我们可以看到,TCP是怎么通过这种强烈地震荡快速而小心得找到网站流量的平衡点的。

快速恢复算法

TCP Reno

快速重传和快速恢复算法一般同时使用。快速恢复算法是认为,你还有3个Duplicated
Acks说明网络也不那么糟糕,所以没有必要像RTO超时那么强烈。 注意,进入快速恢复算法之前,cwnd 和 sshthresh已被更新:

* cwnd = cwnd /2
* sshthresh = cwnd
然后,真正的Fast Recovery算法如下:

* cwnd = sshthresh  + 3 * MSS (3的意思是确认有3个数据包被收到了)
* 重传Duplicated ACKs指定的数据包
* 如果再收到 duplicated Acks,那么cwnd = cwnd +1
* 如果收到了新的Ack,那么,cwnd = sshthresh ,然后就进入了拥塞避免的算法了。

技术
©2019-2020 Toolsou All rights reserved,
大一上c语言学生管理系统(下)年底了,不要跳槽。字节跳动测试工程师凉经分享教你用Python画一棵圣诞树用C实现圣诞树python 使用turtle 画樱花(python3验证ok)win10系统的计算机C盘在哪,c盘users在哪(win10c盘找不到users)计算机发展史上最著名的两位鼻祖HDFS主要组件(数据块、NameNode、DataNode、secondaryNameNode)python 指定时间运行代码