TCP协议详解

TCP协议

什么是TCP协议

传输控制协议(TCP,Transmission ControlProtocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。TCP协议建立的是一种点到点的,一对一的可靠连接,与UDP相比以牺牲效率为代价换取高可靠性的服务。

TCP报文段

TCP报文段也叫TCP分组,是TCP协议传输和接收数据的一种封装格式,只有按照该格式发送的数据才可以被TCP协议通信的双方正确的接收与解析。

TCP报文的组成部分描述如下:

    源端口:标识源端应用进程,即发送TCP分组的进程端口号。 目的端口:标识目的端应用进程,即需要接收分组数据的进程端口号。 序号(seq):在SYN标志未置1时,该字段指示了用户数据区中的第一个字节的序列号;在SYN标志置1时,该字段指示的是初始发送的序列号。 确认号(ACK):用来确认本端TCP实体已经接收到的数据,其值表示期待对端发送的下一个字节的序号,实际上告诉对方,这个序号减1以前的字节已经正确接收。例如发送确认号为1001,则表示前1000个字节已经被确认接收了。 数据偏移:表示以 32bit为单位的TCP分组头的总长度,用于确定用户数据区的起始位置。 RST:连接复位,重新连接 SYN:同步序号,当SYN置1时,表示建立连接,分组将发送seq为初始序列号(其值一般随机) FIN:结束标志,表示关闭连接,当该字段置1时,表示分组将要关闭连接 ACK:确认号有效的标志,置1时表示确认号有效。 URG:紧急指针有效的标志,置1时表示紧急指针有效 PSH:Push操作。

TCP建立连接的过程(三次握手)

TCP连接的建立是通过三次握手的过程完成的,下面将具体讲解何为三次握手:

第一次握手

第一次握手由客户端向服务器 发出:客户端发送一个TCP数据报,其中,TCP分组的源端口为客户端发起通信建立的进程端口,而目的端口为服务器处理请求的进程端口号,比如80端口。在表示建立连接的TCP分组中,SYN标志位为被置1,则告知服务器发送该分组的目的是请求建立连接,并且当SYN置1时,该分组中的seq字段发送的是初始序列号cilent_isn,初始序列号的取值方案有多种,一般取随机值。

第二次握手

第二次握手由服务器向客户端发出:服务器在成功接收了客户端第一次握手发送的分组后,首先需要解析收到的TCP分组,发现SYN置1后,得知这个客户端发送分组的目的是为了建立连接,在确认完毕分组信息后,服务器也会向客户端发送一个TCP分组来代表第二次握手。

这个由服务器发送的TCP分组包含以下内容:

    首先将代表建立连接目的的SYN标志位同样置1 然后随机地产生服务器端的初始序列号 server_isn在seq字段中发送 同时将代表累计确认接收字节位数的ACK确认号发送,该确认号ACK的值为客户端发送的TCP分组中的初始序列号+1,即cilent_isn+1。也就是发送服务器期望接收的下一个字节序号。

第三次握手

第三次握手由客户端向服务器发出:客户端在接收到服务器第二次握手的回复之后,同样会接收来自于服务器的TCP分组并进行解读:由SYN为1解读该TCP分组的目的是继续建立连接,由分组中的ACK确认号了解到服务器已经成功接收了前cilent_isn个字节的内容,并期望接收第cilent_isn+1个字节的字节序号,同时累计确认来自服务器的初始序列号。并发送最后一个TCP分组给服务器来完成第三次握手。

这个由客户端发送给服务器的最后一个TCP分组的内容有:

    代表继续建立连接的SYN为1 发送服务器期望接收的值为cilent_isn+1的序列号字段seq 发送期望接收到的下一个字节的序列号,也就是确认号ACK,值为server_isn+1

为什么需要三次握手

为什么TCP的连接必须进行三次握手,而不是一次或两次?

为了防止建立重复的连接而损耗资源或造成其他问题

谢希仁版《计算机网络》中的例子:“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

也就是说,假如只采用两次握手,会让因网络错误延时到达的客户端连接请求被服务器接收时,服务器无从得知这是一个失效的请求而与客户端建立一个无效的连接。在三次握手机制的情形下,这样无效的请求分组会被返回给客户端来确认,而客户端不在等待该由该分组产生第三次握手的状态,服务器得不到客户端的回应,等待超时后会取消本次连接的建立。从而避免了错误连接的建立

TCP拆除连接的过程(四次挥手)

四次握手的连接关闭过程与三次握手相类似: 四次挥手的过程简述如下:

第一次挥手: 客户端因为不再有数据发送给服务器,所以向服务器发送FIN报文表示想要关闭连接,不会再发送数据了。同时包含客户端的报文序号M

第二次挥手: 服务器在接收了来自客户端的FIN报文后,得知客户端不再发送数据,将要关闭连接。但是,此时服务器端获取还有部分数据没有回传给客户端,服务器可能还要向客户端发送一部分数据才能关闭连接。所以服务器不会立马同意关闭连接,而是先发送表示确认信息的ACK报文,该ACK报文中包含了值为M+1的确认号。

第三次挥手: 在服务器端完成向客户端传送最后的数据后,此时不再有数据需要传输了。那么服务器也准备关闭连接,所以向客户端发送表示关闭连接的FIN报文,报文中包含了服务器端的序号N

第四次挥手: 在客户端也收到了服务器端发送的FIN报文之后,得知服务器端也可以关闭连接了,此时再向服务器发送最后一次确认报文ACK,使连接成功关闭,ACK报文中的确认号为N+1

为什么需要四次挥手?

为什么需要中间两次挥手? 为什么需要其中的第二次和第三次的原因其实已经提到,假如在服务器收到客户端的第一次挥手就准备关闭连接发送FIN报文,不再发送数据。那么只是客户端单方面提出了中断通信的请求而导致连接关闭,使服务器中要发往客户端的最后数据不能正常传送,会导致通信不完整。 为什么需要第四次挥手? 第四次挥手同样是用来解决一个因网络时延滞后到达的失效请求的问题。 请分析这样一个情形:即假如客户端一次发送了两个关闭请求,其中一个滞后了很久才到达,此时它们当时建立的连接已经被第一个请求关闭。而服务器又再次和客户端建立了新的连接,并且此时不需要关闭,但是这个失效的关闭请求滞后到达了。

    假如没有最后一次挥手,那么服务器以为客户端要关闭连接,一次发送ACK和FIN报文后连接就被错误关闭了,这样显然是存在问题的。 假如有最后一次握手,服务器在收到失效报文后仍然会发送ACK和FIN报文给客户端来确认客户端是否真的要关闭连接。而客户端此时的状态并不是等待关闭连接的状态,所以不会给服务器发送ACK报文进行关闭连接的确认回复,服务器在等待一段时间超时后会取消连接的关闭,从而不会因为失效的报文让连接错误地关闭。

TCP发送数据的过程(超时和重传)

TCP数据的发送过程也与建立连接的过程相类似: 发送数据的过程采用了累计确认的机制,其中ACK表示的是累积确认的字节序号。 如上图,发送的TCP分组中可以包含数据,也可以不包含数据,但是一定需要包含seq序号和确认号。seq序号表示数据区中第一个字节的序号,而回传的ack确认号的值为seq的值加上发送的数据字节数。表示这些字节的数据已经被确认接受了,并表达了期望接收的下一个字节序号位置。

发送TCP分组的一方会在发送分组时设置超时时间并开启计时器,当在超时时间内未收到该分组的ACK回复时,会认为该分组丢失,并重发该分组。假如之前的分组被连续发送,而该分组及之后的分组已经被接收端确认,那么本次重复分组收到的ACK是累计确认的字节序号值,而非该分组的seq+数据字节数,如上图右侧所示。

经验分享 程序员 微信小程序 职场和发展