TCP学习

Posted on

TCP(传输控制协议,Transmission Control Protocol)是一种端到端的、面向连接、可靠的传输层协议。在实现上基于ip协议,一个通过网络数据报(internet datagram)接收/发送数据的协议。在此基础上,TCP主要提供了如下的几个能力:

本文主要介绍Reliability和FlowControl

可靠性(Reliability)

保证可靠性,需要应对网络环境中的各种不确定性,包括数据损坏、丢失、重发、乱序等等。在这种网络情况,tcp提出了如下机制来保证数据传输的可靠性。

通过以上三种机制,tcp保证了在复杂的网络环境中,提供了可靠的数据传输能力。

复用(Multiplexing)

为了支持多进程同时使用tcp,tcp使用ip+port组成了一个socket,由一对socket确定一个唯一的连接(包含主机ip、port以及目标机器ip、port)。接收端的TCP服务就是根据不同的端口号来将数据包传送给应用层的不同程序,这个过程叫做解复用(demultiplex)。相应的发送端会把应用层不同程序的数据映射到不同的端口号,这个过程叫做复用(multiplex)。

连接

TCP的整个数据传输的过程都是基于连接的,连接是TCP对上层做的一个抽象,一个连接维护了两端需要同步的一些信息,比如sockets、sequence number、window等等。而建立和销毁连接的过程则是对这些信息的初始化、同步的过程。基于此TCP提供了一组状态机来维护每个连接生命周期的状态。整个状态机分为两组,一组为未同步状态,是连接还未建立起来的一组状态集合;另一组为已同步状态,是包括创建到最后销毁过程的状态集合:

状态转移

下图包含了一个连接在整个生命周期的状态转移图示,注意这并不是状态变换的所有情况,并没有包含异常处理的状态转移。更详细的可以看tcp详细状态

tcp状态机

初始化连接(三次握手)

初始化连接的过程,一般在两端包含三次信息交换的过程,因此被称做三次握手。三次握手的过程中,主要是为了同步两端的一些信息,识别已失效的数据,例如对方能接收的报文段长度、对方发送包分的开始序列号(seq number)。三次握手的示例见下图:

tcp三次握手

假设连接的两端分别为小c和小s。他们俩住在两个小区,通过让其他人传话的方式沟通,因此会出现很多复杂不可预测的情况。因此两人做了一个约定,每次交流开始之前两人分别随便在心里想一个数字,然后告诉对方,后面的传话,每个字都把这个数字+1,让对方知道自己发送的顺序。当两人都知道了对方的心里的数字时,就可以交流真正的信息了。让对方知道自己的数字这个同步过程,他们起了个名字:三次握手,并且把双方同步后的状态叫做已建立连接。整个过程表示如下:

此时,当小s收到最后一次确认的时候,连接建立完成,双方到达已建立连接的状态(ESTABLISHED)。

注意,三次握手是最常用的连接情况(server被动监听、client主动连接),但并不是唯一的连接过程。双方同时主动发起连接,也可以完成一次连接,情况可以参照上面的状态机想象一下。

关闭连接(四次挥手)

关闭连接的过程,一般在两端包含四次信息交换的过程,因此也被称做四次挥手。四次回收的过程,是为了同步两边的状态,使双方都知道连接关闭,不再发送数据,同时释放占用的资源。四次挥手的过程示例如下图示:

tcp四次挥手

根据如上小s和小c二人,当小c数据发送完成想关闭连接时,发生了一次状态同步的过程,表示如下:

此时当小s收到最后的回复后,关闭连接,清理资源。同时小c不确定是否最后的回复s可以收到,于是多等了一会(2msl)后关闭连接,清理资源。

注意,四次回收也不是唯一的关闭连接的过程,双方同时结束连接会走另外的流程。这里就不多说了,可以参考上述状态机来做一下思考。

流量控制(FlowControl)

在数据发送过程中由于发送端不清楚接收端的数据处理能力,导致发送端持续发送数据,接收端在数据超过自己缓存区的时候,就会丢掉数据,这样不仅导致数据传输效率差、还会大量占用网络带宽。于是TCP作为基础的传输层能力,需要提供可以让接收端控制发送端流量的方式。于是就有了我们耳熟能详的滑动窗口。滑动窗口通过在建立连接时同步的窗口扩大因子(window scale factor),以及每次ACK时的win字段(接收方回应发送方可接受缓冲大小),确定发送方可以发送的数据长度。下面是滑动窗口可能会遇到的一些问题。

Zero Window(零窗口问题)

如果发送端一直发送数据,发送的速度超过了接收端的处理能力,那么窗口大小会一直减小,最终达到0。当窗口大小为0时,发送端就不能继续发送数据了。那么发送端要等到什么时候呢?

糊涂窗口综合征(Silly Window Syndrome)

这个是由于在滑动窗口过程中,每次只滑动一点,就会导致每次只发送少量数据,可能会导致持续大量发送小批量数据。这个问题需要同时考虑接收和发送两端:

如果是接收端引起的问题:如果接收端收到的数据导致Window size小于某个值,可以直接Ack(0)回复发送端,这样就把Window给关闭了,发送端就不会继续发送数据。当接收端处理了一些数据后Windows size 大于等于了MSS,或者接收端缓冲区有一半为空,通知发送端可以继续发送数据。

如果是发送端引起的问题,那么就会使用著名的 Nagle’s 算法。这个算法的思路也是延时处理,需要满足如下两个条件:

Nagle算法默认是打开的,所以,对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性比较强的程序,你需要关闭这个算法。

异常情况

整个过程中,还有一部分由于网络问题导致的异常情况,这个TCP提供了reset机制来处理各种正常设想外的情况。

产生reset的场景

reset的处理