计算机网络:运输层 - TCP首部格式 & 连接的创建与释放
- TCP首部格式
- 源端口 目的端口
- 序号
- 确认号
- 数据偏移
- 保留
- 控制位
- 窗口
- 检验和
- 紧急指针
- TCP连接创建 - 三次握手
- TCP传输过程
- TCP连接释放 - 四次挥手
TCP首部格式
TCP的首部如下:

首部的前20 byte是固定的,后面的选项字段可变。
源端口 目的端口
源端口和目的端口各占2 byte,即填入通信的两个进程使用的端口号。
序号
占4 byte,范围是 [ 0 , 2 32 − 1 ] {\red{[0, 2^{32} - 1]}} [0,232−1] ,TCP是面向字节流的,在TCP中传输的每一个字节都要按顺序进行编号。整个传输过程中,第一个字节的值是任意的,由发送方随机设定,后续所有字节都由第一个字节的编号以及偏移量得出。比如整个TCP连接中,第一个字节编号为x,那么第201个字节的编号就是x + 201。
如果某个字节在编号时,超出了 2 32 − 1 2^{32} - 1 232−1 ,此时从0开始重新计数。
确认号
占4 byte,含义是:期望收到对方下一个报文段的第一个字节的序号。
例如,主机B收到了来自A的编号为501的数据报,数据报长度为200。这说明主机B收到了编号为501 - 700字节的数据。那么主机B接下来就期望收到701开始的数据,此时确认号就设为701。
若确认号为 n,说明 n - 1 及之前的所有数据都已经收到了
数据偏移
占4 bit,其含义为:报文起始处到数据起始处的距离,简单理解就是整个首部的长度。
由于TCP带有填充字段,所以长度是不确定的,需要该字段来指明长度。另外的,由于只占该字段只占4 bit,能表示的范围是 [ 0 , 2 4 − 1 ] {\red{[0, 2^{4} - 1]}} [0,24−1],也就是[0, 15],而数据偏移字段以4 byte为单位。所以整个首部的长度最长为60 byte,进而说明选项字段的长度不超过40 byte。
保留
占6 bit,目前没有用,使用时设为全0。
控制位
接着就是连续的留个控制位。
紧急 URG:当URG = 1,表明紧急指针字段有效,告诉系统此报文有紧急数据,需要尽快传送,此时该报文就无需排队,直接插入到队列的首部立马发送出去。
确认 ACK:当ACK = 1时,确认号字段才有效。TCP规定:在连接建立后所有的传送报文段,ACK必须置为1。
推送 PSH:发送该报文后,如果希望尽快收到对方的回应,就可以PSH = 1。接受方收到该报文后,会立刻把该报文提交给应用进程,而不是等到接收缓存满了才提交。
复位 RST:当RST = 1,表示TCP连接出现重大差错,必须释放连接。也可以用来拒绝一个连接或报文段。
同步 SYN:用于建立连接
- 当
SYN = 1并且ACK = 0,表明这是一个连接请求报文,(ACK = 0的情况,一般来说整个TCP连接只有此处ACK = 0) - 当
SYN = 1并且ACK = 1,表明这是一个连接同意报文
终止 FIN:用于释放一个连接,当FIN = 1,表明这是一个释放连接的报文
窗口
占2 byte,用于指明自己的接收窗口,对方收到该报文后,读取窗口字段,就知道自己接下来可以发送多少数据了。
检验和
占2 byte,检验范围包括首部和数据两部分,与UDP一样,在计算检验和时,要加上伪首部。
如图:

只有计算检验和时才会存在伪首部,实际上其不存在,TCP 数据报中只有首部和数据。
紧急指针
占2 byte,仅在URG = 1时才有效。当URG = 1,说明这是一个紧急的报文,此时要立刻发出去,整个数据报会插队到其他数据报的前面。那么计算机怎么知道这个紧急报文的长度是多少?
此时就需要紧急指针字段,其指明了本报文中紧急数据的字节数,当把所有紧急数据处理完后,就要恢复正常状态,把之前的数据发送出去。而什么时候紧急数据发送完,就是依靠紧急指针字段指明的紧急数据的字节数。
讲解完报文的格式后,我们来讲解TCP连接的创建与释放。后续会用到一些标识符,接下来我解释一下每个标识符对应数据报首部的哪一个字段:
seq:对应首部中的序号字段,指明希望收到的下一个数据的序号是什么ack:对应首部中的确认号字段,表明xxx之前的所有数据都已经收到了ACK:对应首部中的确认位字段,表明确认号有效SYN:对应首部中的同步位字段,用于创建TCP连接FIN:对应首部中的终止位字段,用于终止TCP连接
TCP连接创建 - 三次握手
假设现有一台客户主机A,一台服务器主机B,现在A申请向B发起TCP连接。
处于创建连接的过程中,SYN就起作用了,如图:

首先令SYN = 1,表明当前正在创建连接,创建连接的报文分两种情况:
- 当
SYN = 1并且ACK = 0,表明这是一个连接请求报文,(ACK = 0的第一种情况) - 当
SYN = 1并且ACK = 1,表明这是一个连接同意报文
当前A正在发起连接的请求,所以此时ACK = 0,注意:后续只要不标明的位,都是0。
而发送数据是要对每个字节进行编号的,第一个字节的编号由主机随机生成,此时seq = x表明:第一个字节的编号为x。
当A发起请求后,此时B就要同意这个连接:

同意连接是SYN = 1,ACK = 1,表明当前报文用于同意一个连接。此时主机B也要生成第一个字节的编码,也就是seq = y。
B在回应时,还有一个字段ack = x + 1,ack表示:我希望收到的下一个数据的编号。
比如说某一次报文发送时,第一个字节的编号为666,总数据长度为200 byte,那么接收方就收到了[666, 865]的所有数据,此时回应报文为ack = 866表明下一个数据的编号为866。
那么目前来说刚刚TCP请求报文的编号为seq = x,现在我回应ack = x + 1,是不是可以理解为:整个TCP请求报文只携带一个字节的数据呢?
TPC规定:SYN = 1的报文不允许携带数据,但是消耗掉一个seq
其实SYN = 1的报文不携带数据部分,但是TCP强制规定了其要消耗掉一个seq,因此刚刚序号x视为被消耗了,下一个字节的序号为x + 1。
当A收到B的确认后,此时A也要再给B做一次确认:

这个确认,是对第二个报文的确认,此时SYN = 0,因为其既不是连接请求,也不是连接同意。ACK = 1表明ack字段有效。seq = x + 1,因为第一个报文seq = x,并且SYN = 1要消耗掉一个需要,此时就用下一个序号x + 1。
ack = y + 1是因为,刚刚B发送的报文seq = y,而SYN = 1要消耗掉一个序号,此时希望收到的下一个序号为y + 1。
接下来考虑一个问题:为什么需要三次报文交换才能建立连接?明明A发送一个请求,B发送一个同意,就表明双方都准备建立连接了,为什么不直接开始传输数据,而是还要第三次确认?
这是因为在B发送第二个同意报文后,A可能还没准备好接收数据。比如说B发送的同意报文丢失了,此时A还在等待B的同意,而B以为A已经可以发送数据了。结果B发送了一段数据后,A根本不接收,因为A在等B的同意。此时就是A没有准备好。
因此A要发送第三个报文,来表明自己已经准备好了,对面可以开始发送数据了。
另外的,第三个报文是可以携带数据的,此时A发送第三个报文时,表明连接建立完毕了,于是A就顺带可以把一部分数据先通过该报文传输过去!
对于
FIN = 0,SYN = 0且ACK = 1的报文,可以携带数据,携带多少数据就消耗多少序号,如果不携带数据,就不消耗序号
现在连接已经创建完毕,就可以正常数据传输了!
TCP传输过程
很多地方都只讲了连接创建与释放的过程,反而没有说明传输的过程。其实这个过程也很重要,本博客再简单讲解一下传输的过程。
如图所示:

现在TCP连接建立时,第三个报文携带了100个数据(data用于说明这个报文携带了多少数据)。
随后A又紧接着发送了300个数据:

此时seq = x + 101,这是因为刚刚的第三个报文携带了100 byte的数据,其中第一个字节的编号为x + 1,说明我已经把[x + 1, x + 100]的数据发出去了,接下来的300字节,第一个编号就是x + 101了。
而ack = y + 1,这是因为上一次收到B的报文是SYN = 1,ACK = 1的连接同意报文,序号为y,消耗掉一个序号后变为y + 1,即下一个希望收到的编号为y + 1。
随后B发送了一个长度为200 byte的报文:

此时seq = y + 1,这是因为上一次B发送的报文是seq = y,而SYN = 1消耗掉一个序号,这次第一个字节使用的序号为y + 1。ack = x + 401表明[x, x + 400]的所有数据都收到了,下一个希望收到的序号是x + 401,
随后A再发送一个100 byte的报文:

这时seq = x + 401,因为之前发送了[x, x + 400]的数据,下一个字节编号为x + 401。ack = y + 201表明[y, y + 200]的数据都受到了。
以此类推,直到连接释放。
TCP连接释放 - 四次挥手
当TCP连接传输数据完毕,此时就可以释放连接了。
TCP连接释放可以由任意一方发起,假设现在A发起释放连接:

首先要把FIN = 1,表明A发起了一个释放连接的请求。而seq = u,表明当前的报文编号为u,也说明之前A传输的最后一个字节编号为u - 1。ack = v,表明A收到的来自B的最后一个字节是u。
A发起释放连接的请求,只说明A要传送的数据已经完毕了,可以释放连接了。但是B可能还有没有传送完的数据:

首先B发送一个ACK报文,表明自己已经收到了刚刚FIN = 1的报文。
TCP规定:FIN = 1的报文,就算不携带数据也要消耗一个序号
A发送的连接释放报文中,FIN = 1并且不携带数据,那么也要消耗掉一个序号。因此B希望收到的下一个序号是u + 1。
如果B收到该报文,那么ack = u + 1,否则ack = u,这样发送方就可以根据下一个报文得知B有没有收到连接释放的请求包围了。
随后B可以继续发送自己之前没发完的数据,这期间B发送的报文FIN = 0,表明B还有数据要发,没这么快终止连接。
剩下B发送的所有报文中ack = u + 1,因为刚刚A发送了一个FIN = 1的报文。
而seq = v,表明自己现在发送的数据中,第一个字节序号为v。
当B传输完自己的所有数据后,在发送释放连接的同意报文:

FIN = 1表明这是一个连接释放的报文,在两个FIN = 1的报文中间,B还发了一些报文,导致序号一直增加,假设现在增加到了w,那么seq = w。
当B发送完最后一个FIN = 1的连接释放报文后,A最后发送一个确认报文:

这是因为B无法保证自己发出去的报文A一定可以接收到,如果B发送的FIN = 1的报文丢失了,此时B以为自己以为结束TCP连接,而A还在一直等待B发出FIN = 1的报文。所以要对这个FIN = 1的报文最后做一次确认。
当这四个报文传输完毕,A不能直接结束,而要等待2 MSL:

MSL(Maximum Segment Lifetime,最大报文段生存时间):指的是一个TCP报文在网络中存活的最长时间。
这是因为A传送的最后一个确认报文也有可能丢失,B如过发现A没有回应,超时计时器结束就重传FIN = 1的报文。而这个报文一定可以在2 * MSL期间到达,所以如果A在2MSL期间没有收到B的报文,说明最后一个报文B收到了,可以释放连接了。