TCP(Transmission Control Protocol,传输控制协议)是一种面向链接的、可靠的、基于字节流的通讯协议,数据在传输前要创建链接,传输完毕后还要断开链接。它是个超级麻烦的协议,是互联网的基础,也是每一个程序员必备的基本功。首先来看看OSI的七层模型:程序员
咱们须要知道TCP工做在网络OSI的七层模型中的第四层——Transport层,IP在第三层——Network层,ARP在第二层——Data Link层;在第二层上的数据,咱们把它叫Frame,在第三层上的数据叫Packet,第四层的数据叫Segment。 同时,咱们须要简单的知道,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,而后再发送到数据接收端。这个基本的流程你须要知道,就是每一个数据都会通过数据的封装和解封装的过程。 在OSI七层模型中,每一层的做用和对应的协议以下: 算法
TCP是一个协议,那这个协议是如何定义的,它的数据格式是什么样子的呢?要进行更深层次的剖析,就须要了解,甚至是熟记TCP协议中每一个字段的含义。 服务器
上面就是TCP协议头部的格式,因为它过重要了,是理解其它内容的基础,下面就将每一个字段的信息都详细的说明一下。网络
URG
,ACK
,PSH
,RST
,SYN
,FIN
。每一个标志位的意思以下:
SYN
标志位和ACK
标志位搭配使用,当链接请求的时候,SYN
=1,ACK
=0;链接被响应的时候,SYN
=1,ACK
=1;FIN
标志位的TCP数据包后,链接将被断开。好了,基本知识都已经准备好了,下面来看看TCP创建链接、发送数据及断开链接。并发
1.创建链接
使用 connect() 创建链接时,客户端和服务器端会相互发送三个数据包,俗称三次握手。能够形象的比喻为下面的对话:socket
客户端调用 socket() 函数建立套接字后,由于没有创建链接,因此套接字处于CLOSED
状态;服务器端调用 listen() 函数后,套接字进入LISTEN
状态,开始监听客户端请求。
这个时候,客户端开始发起请求:
1).当客户端调用 connect() 函数后,TCP协议会组建一个数据包,并设置 SYN 标志位,表示该数据包是用来创建同步链接的。同时生成一个随机数字 1000,填充“序号(Seq)”字段,表示该数据包的序号。完成这些工做,开始向服务器端发送数据包,客户端就进入了SYN-SEND
状态。
2).服务器端收到数据包,检测到已经设置了 SYN 标志位,就知道这是客户端发来的创建链接的“请求包”。服务器端也会组建一个数据包,并设置 SYN 和 ACK 标志位,SYN 表示该数据包用来创建链接,ACK 用来确认收到了刚才客户端发送的数据包。
服务器生成一个随机数 2000(2000 和客户端数据包没有关系),填充“序号(Seq)”字段。服务器将客户端数据包序号(1000)加1,获得1001,并用这个数字填充“确认号(Ack)”字段。服务器将数据包发出,进入SYN-RECV
状态。
3).客户端收到数据包,检测到已经设置了 SYN 和 ACK 标志位,就知道这是服务器发来的“确认包”。客户端会检测“确认号(Ack)”字段,看它的值是否为 1000+1,若是是就说明链接创建成功。
接下来,客户端会继续组建数据包,并设置 ACK 标志位,表示客户端正确接收了服务器发来的“确认包”。同时,将刚才服务器发来的数据包序号(2000)加1,获得 2001,并用这个数字来填充“确认号(Ack)”字段。客户端将数据包发出,进入ESTABLISED
状态,表示链接已经成功创建。
4).服务器端收到数据包,检测到已经设置了 ACK 标志位,就知道这是客户端发来的“确认包”。服务器会检测“确认号(Ack)”字段,看它的值是否为 2000+1,若是是就说明链接创建成功,服务器进入ESTABLISED
状态。
至此,客户端和服务器都进入了ESTABLISED
状态,链接创建成功,接下来就能够收发数据了。函数
那为何非要三次呢?怎么以为两次就能够完成了。spa
在谢希仁的《计算机网络》中是这样说的:为了防止已失效的链接请求报文段忽然又传送到了服务端,于是产生错误。计算机网络
在书中同时举了一个例子,以下:指针
已失效的链接请求报文段”的产生在这样一种状况下:client发出的第一个链接请求报文段并无丢失,而是在某个网络结点长时间的滞留了,以至延误到链接释放之后的某个时间才到达server。原本这是一个早已失效的报文段。但server收到此失效的链接请求报文段后,就误认为是client再次发出的一个新的链接请求。因而就向client发出确认报文段,赞成创建链接。假设不采用“三次握手”,那么只要server发出确认,新的链接就创建了。因为如今client并无发出创建链接的请求,所以不会理睬server的确认,也不会向server发送数据。但server却觉得新的运输链接已经创建,并一直等待client发来数据。这样,server的不少资源就白白浪费掉了。采用“三次握手”的办法能够防止上述现象发生。例如刚才那种状况,client不会向server的确认发出确认。server因为收不到确认,就知道client并无要求创建链接。
2.数据传输
创建链接后,两台主机就能够相互传输数据了。以下图所示:
上图给出了主机A分2次(分2个数据包)向主机B传递200字节的过程。首先,主机A经过1个数据包发送100个字节的数据,数据包的 Seq 号设置为 1200。主机B为了确认这一点,向主机A发送 ACK 包,并将 Ack 号设置为 1301。
为了保证数据准确到达,目标机器在收到数据包(包括SYN包、FIN包、普通数据包等)包后必须当即回传ACK包,这样发送方才能确认数据传输成功。
此时 Ack 号为 1301 而不是 1201,缘由在于 Ack 号的增量为传输的数据字节数。假设每次 Ack 号不加传输的字节数,这样虽然能够确认数据包的传输,但没法明确100字节所有正确传递仍是丢失了一部分,好比只传递了80字节。所以按以下的公式确认 Ack 号:Ack号 = Seq号 + 传递的字节数 + 1
与三次握手协议相同,最后加 1 是为了告诉对方要传递的 Seq 号。
下面分析传输过程当中数据包丢失的状况,以下图所示:
上图表示经过 Seq 1301 数据包向主机B传递100字节的数据,但中间发生了错误,主机B未收到。通过一段时间后,主机A仍未收到对于 Seq 1301 的ACK确认,所以尝试重传数据。为了完成数据包的重传,TCP套接字每次发送数据包时都会启动定时器,若是在必定时间内没有收到目标机器传回的 ACK 包,那么定时器超时,数据包会重传。
上图演示的是数据包丢失的状况,也会有 ACK 包丢失的状况,同样会重传。
这个值太大了会致使没必要要的等待,过小会致使没必要要的重传,理论上最好是网络 RTT 时间,但又受制于网络距离与瞬态时延变化,因此实际上使用自适应的动态算法(例如 Jacobson 算法和 Karn 算法等)来肯定超时时间。
往返时间(RTT,Round-Trip Time)表示从发送端发送数据开始,到发送端收到来自接收端的 ACK 确认包(接收端收到数据后便当即确认),总共经历的时延。
TCP数据包重传次数根据系统设置的不一样而有所区别。有些系统,一个数据包只会被重传3次,若是重传3次后还未收到该数据包的 ACK 确认,就再也不尝试重传。但有些要求很高的业务系统,会不断地重传丢失的数据包,以尽最大可能保证业务数据的正常交互。
最后须要说明的是,发送端只有在收到对方的 ACK 确认包后,才会清空输出缓冲区中的数据。
3.断开链接
创建链接很是重要,它是数据正确传输的前提;断开链接一样重要,它让计算机释放再也不使用的资源。若是链接不能正常断开,不只会形成数据传输错误,还会致使套接字不能关闭,持续占用资源,若是并发量高,服务器压力堪忧。
创建链接须要三次握手,断开链接须要四次握手,下图演示了客户端主动断开链接的场景,能够形象的比喻为下面的对话:
创建链接后,客户端和服务器都处于ESTABLISED
状态。这时,客户端发起断开链接的请求:
1).客户端调用 close() 函数后,向服务器发送 FIN 数据包,进入FIN_WAIT_1
状态。FIN 是 Finish 的缩写,表示完成任务须要断开链接。
2).服务器收到数据包后,检测到设置了 FIN 标志位,知道要断开链接,因而向客户端发送“确认包”,进入CLOSE_WAIT
状态。
注意:服务器收到请求后并非当即断开链接,而是先向客户端发送“确认包”,告诉它我知道了,我须要准备一下才能断开链接。
3) 客户端收到“确认包”后进入FIN_WAIT_2
状态,等待服务器准备完毕后再次发送数据包。
4) 等待片刻后,服务器准备完毕,能够断开链接,因而再主动向客户端发送 FIN 包,告诉它我准备好了,断开链接吧。而后进入LAST_ACK
状态。
5) 客户端收到服务器的 FIN 包后,再向服务器发送 ACK 包,告诉它你断开链接吧。而后进入TIME_WAIT
状态。
6) 服务器收到客户端的 ACK 包后,就断开链接,关闭套接字,进入CLOSED
状态。
为何客户端最后一次发送 ACK包后进入 TIME_WAIT 状态,而不是直接进入 CLOSED 状态关闭链接?
TCP 是面向链接的传输方式,必须保证数据可以正确到达目标机器,不能丢失或出错,而网络是不稳定的,随时可能会毁坏数据,因此机器A每次向机器B发送数据包后,都要求机器B”确认“,回传ACK包,告诉机器A我收到了,这样机器A才能知道数据传送成功了。若是机器B没有回传ACK包,机器A会从新发送,直到机器B回传ACK包。
客户端最后一次向服务器回传ACK包时,有可能会由于网络问题致使服务器收不到,服务器会再次发送 FIN 包,若是这时客户端彻底关闭了链接,那么服务器不管如何也收不到ACK包了,因此客户端须要等待片刻、确认对方收到ACK包后才能进入CLOSED状态。那么,要等待多久呢?
TIME_WAIT 状态的说明
数据包在网络中是有生存时间的,超过这个时间还未到达目标主机就会被丢弃,并通知源主机。这称为报文最大生存时间(MSL,Maximum Segment Lifetime)。TIME_WAIT 要等待 2MSL 才会进入 CLOSED 状态。ACK 包到达服务器须要 MSL 时间,服务器重传 FIN 包也须要 MSL 时间,2MSL 是数据包往返的最大时间,若是 2MSL 后还未收到服务器重传的 FIN 包,就说明服务器已经收到了 ACK 包。