TCP之旅
3185字约11分钟
计算机网络
2017-12-08
前言
前面两篇介绍 Socket 的文章中,简单描述了 Socket 网络编程的过程。这一篇,主要介绍一下 TCP 的工作原理。包含以下六点:
- TCP连接简介
- TCP报文段
- 连接的建立和断开(三次握手,四次挥手)
- TCP可靠在哪里(序号、ACK回复、超时重传)
- 滑窗机制
- 拥塞控制机制
一、TCP连接简介
TCP 全称 Transmission Control Protocol,传输控制协议,是传输层的协议。它的任务是将应用层的数据准确地交付给网络层。
两个应用程序,如果用TCP来发送数据,那么他们必须先互相“握手”。也就是说,在正式传数据之前,要先互相发送一些预备报文段,以建立确保数据传输的参数。所以我们说,TCP是面向连接的(connection-oriented),也是可靠的传输。
虽然TCP是面向连接的,但是这种“连接”,不是端到端线路意义上的连接,也不是虚电路。TCP的连接,是保留在两个端系统的状态当中的。因此,中间路由器对TCP连接是视而不见的。
TCP提供的是全双工服务
,数据可以从A到B,也能从B到A。同时,TCP是点对点
的,TCP连接是单个发送方与单个接收方之间的连接,不能多播。(但这并不代表一个服务器不能与多个客户端建立连接,只是说,一个连接只能在两台主机之间,如果一台主机要接收另外两台主机的数据,需要为那两台主机分别建立TCP连接)
TCP是怎样建立连接的(三次握手)?
- 首先,客户端先发送一个特殊的TCP报文段
- 服务器返回另一个特殊的报文段响应
- 客户端用第三个特殊报文段作为响应
前两个报文段,不承载“有效载荷”(不包含应用层的数据),仅仅是打招呼。后一个报文段,可以承载有效载荷。由于在这两台主机之间发送了 3 个报文段,所以这种连接建立的过程,我们称之为“三次握手”,稍后我们详细讨论。
二、TCP报文段
TCP将应用程序的报文(message)分组,再为每个分组加上TCP首部
,形成多个TCP报文段(TCP segment)
,再下传给网络层。报文段的首部如下图:
首部包含了以下信息:
- 源端口号(source port) 和 目的端口号(destination port) :各16位,因为16位最大能表示的数字是65535,所以端口号最大值也只能是65535。
- 序号字段(sequence number field) :32位,用来实现可靠数据传输,例如一个500000字节的文件被分为500个TCP报文段,初始序号可以随意指定,后续的序号是前面序号+携带的字节数,例如初始序号79,每个报文段携带1000字节,第二个序号就是1079
- 确认号字段(Acknowledgement number field) :32位,也是用来实现可靠数据传输,表示期望对方发送的下一字节的序号
- 接收窗口字段(receive windows field) :16位,用于流量控制,表示接收方愿意接受的字节数量
- 首部长度字段(header length field) :4位,TCP的首部长度是可变的,这个字段表示首部长度
- 选项字段(options field) :不定位数,用作窗口调节因子
- 标志字段(flag field) :6位,包括ACK、RST、SYN、FIN、PSH、URG,各占1位
我们主要关注三个点:
- 一个TCP头部需要包含出发端口和目的地端口。这些与IP头中的两个IP地址共同确定了连接。
- 每个TCP片段都有序号。这些序号最终将数据部分的文本片段整理成为文本流。
- 只有标志字段的ACK位设定的时候,确认号字段才有效。ACK确认号说明了接收方期待接收的下一个片段,所以ACK确认号为最后接收到的片段序号加1。
三、TCP连接的建立和断开
三次握手
前面提到,TCP建立连接的过程称为三次握手。其详细过程如下:
- 第一步:客户端先发送一个特殊的TCP报文段,不包含应用层数据。但首部标志字段的SYN位被置为1(称为SYN报文段),并随机指定一个初始序号(seq)。(为什么初始序号要随机,主要是安全方面考虑)。
- 第二步:服务器收到客户端的SYN报文段后,就会为该TCP连接分配TCP缓存和变量,并回复一个报文段(称为SYNACK报文段)。在这个回复报文段里,SYN位也是被置为1,确认号ack置为客户端刚刚发来的初始序号seq+1,并随机指定一个自己的初始序号seq。
- 第三步:客户端收到服务器的SYNACK报文段后,在客户端为该TCP连接分配缓存和变量,同时也回复了一个报文段,此时连接已经建立,所以SYN置为0,ACK确认号为服务器的初始序号seq+1,seq为自己第一步的seq+1
四次挥手
当TCP连接的任意一方想断开连接时,将会发起断开信号,连接结束后,主机里的TCP连接资源(缓存和变量)随即被释放。断开的过程需要通信四次,所以称为四次挥手。其详细过程如下:
- 第一步:客户端发起关闭连接命令。此时会向服务器发送一个特殊的报文段,标志字段的
FIN
位被置为1
,随后客户端进入FIN_WAIT_1
状态,该状态表示客户端正在等待服务器的ACK确认。 - 第二步:服务器收到这个特殊的报文段后,回复一个确认报文段,收到这个确认报文段后,客户端进入
FIN_WAIT_2
状态,该状态表示客户端期望收到服务器的可以终止命令。 - 第三步:稍后,服务器也向客户端发起一个
FIN
位为1
的特殊报文段,表示可以终止。 - 第四步:客户端回复一个确认报文段进行确认,之后进入
TIME_WAIT
状态等待30秒(目的是确认报文若丢失可以重传),之后连接断开。
四、TCP 为何可靠
“流”和次序
我们之前提到,Linux的哲学是“一切皆文件”,一切都可以用“打开open –> 读写write/read –> 关闭close”的模式来操作。计算机程序之间的通信,是一个进程写入文本流(byte stream),而另一个进程读取这个流。TCP协议虚拟了文本流的通信。
要知道,在两台相隔很远的主机中,隔着许许多多的路由器和交换机,因此数据发送的时候,先发的数据可能会后到。好在TCP协议确保了数据到达的顺序与文本流顺序相符。这就是为什么TCP报文段里面需要有序号的原因。TCP接收方将接收到的许许多多报文段按照序号排列起来,组成原始数据。
“收到请回复”
TCP为什么需要确认号呢?我们知道,TCP是全双工的协议,在A主机向B主机发送数据的同时,B主机也能向A主机发送数据(在同一个TCP连接中)。例如,A向B发送8字节数据时携带了序号,seq=79,B向A发数据时也需要携带一个序号seq=45,同时,“回复”确认收到了A主机seq=79的序号,并期望接收A主机seq=87(为什么不是80,因为前一个序号有8字节)的序号,所以就有了确认号ack=87。
也就是说,TCP发送方每发送一个报文段,接收方都会回复一个确认号。以确保这个片段收到了。比如已经接收到了片段1,片段2,片段3,那么接收方就开始期待片段4。值得注意的是,如果此时收到了片段5 片段6,也不会丢弃,会暂时存起来,等到片段4到达再拼接,但是如果此时收到了片段9,那么接收方就可能拒绝接收了。
报文段丢了,重发
IP协议是不可靠的,也就是说,交付给网络层的报文段,可能会传着传着就丢了。那咋办? TCP有一个定时器超时,规定如果一段时间之后,还没有收到接收方的确认(ACK),就默认这个报文段丢了。就会重新发一个过去,直到接收方回复确认收到。
那么,如果说,这个报文段发过去了,接收方也接收到了。接收方返回给发送方的“确认”信息丢了,怎么办呢? 这种情况下,发送方依然会重发。 接收方一看,咦你居然发了两个一样的报文段?就会自动丢弃其中的一个了,当然,还要给发送方再发一次“收到”回复,以免发送方孜孜不倦、不辞劳苦地一直重发、一直重发。
五、滑窗
我们已经知道,TCP发送方片段1发出去,接收方回复ACK已收到1,发送方再发片段2,接收方再回复ACK已收到2......
在这种模式下,发送方保持发送->等待ACK->发送->等待ACK...的状态,虽然很“可靠”,但是效率太慢了。为了提高效率,同时发多个片段,又怕后发的先到了,怎么办呢?
我们可以这么做:利用缓存保留一些“不那么乱”的片段,期望能在短时间内补充上之前的片段(暂不处理,但发送相应的ACK);对于“乱”的比较厉害的片段,则将它们拒绝(不处理,也不发送对应的ACK)。
滑窗(sliding window)被同时应用于接收方和发送方,以解决以上问题。发送方和接收方各有一个滑窗。当片段位于滑窗中时,表示TCP正在处理该片段。滑窗中可以有多个片段,也就是可以同时处理多个片段。滑窗越大,越大的滑窗同时处理的片段数目越多(当然,计算机也必须分配出更多的缓存供滑窗使用)。
TCP有专门的算法动态调整滑窗的大小。
六、TCP拥塞控制
在TCP协议中,我们使用连接
记录TCP两端的状态,使用序号和分段
保证了TCP传输的有序,使用滑窗
(中的某些机制)来实现发送方和接收方处理能力的匹配,并使用重复发送
来实现TCP传输的可靠性。
一切看似都很美好,但是,随着互联网的发展,越来越多的主机之间要相互发送数据,有时候中间路由器会处理不过来从而发生丢包,一丢包,这些基于TCP的主机就又重新发送,路由器不堪重负,就会发生更严重的丢包,构成了一个恶性循环。这称为堵塞崩溃。
因此,为了避免发送堵塞崩溃,TCP加入了拥塞控制机制
。当TCP发送方探测到网络拥堵时,会控制自己发送片段的速率,以缓解网络的交通状况。
如何探测网络拥堵
当发生ACK超时和重复ACK。发送方就认为TCP片段丢失,则认为网络中出现堵塞。
如何控制速率
TCP协议通过控制滑窗(sliding window)大小来控制发送速率。在TCP滑窗管理中,有一个窗口限制,就是advertised window size,以实现TCP流量控制。TCP还会维护一个congestion window size,以根据网络状况来调整滑窗大小。真实滑窗大小取这两个滑窗限制的最小值,从而同时满足两个限制 (流量控制和堵塞控制)。
(TCP拥塞控制和滑窗的内容来自vamei的博客,vamei写得太好了,值得好好学习)