本文引用图片均来自 高军: 计算机网络
TCP作为面向连接的协议,在每次通信前必须建立
连接,通信结束后必须释放
连接
建立连接
TCP建立连接主要是为了确认对方的存在、双方的收发能力
以及协商参数
一般的,将主动发起TCP连接的进程称为客户进程
,另一方则称为服务进程
。初始时服务进程处于监听状态,等待客户进程的连接建立请求
当客户进程想要建立连接时就发送TCP请求报文段
,并在发送报文后进入同步已发送
状态。报文段首部中同步位SYN
值为1
,表明这是TCP连接请求报文段。序号字段seq
为一个随机值x
,作为客户进程为此次通信选择的初始序号
。值得注意的是SYN为1
的报文段不能
携带数据
服务进程收到请求后返回TCP连接请求确认报文段
,并在发送报文后进入同步已接收
状态。报文段首部中同步位SYN
和确认位ACK
值都为1
,表明这是TCP连接请求确认报文段。序号字段seq
为一个随机值y
,作为服务进程为此次通信选择的初始序号
。确认字段ack
值为x+1
,表明对客户进程
所选择的初始序号值x的确认
客户进程收到TCP连接请求确认报文段后需要向服务进程发送一个普通TCP确认报文段
,并在发送报文后进入连接已建立
状态。报文段首部中确认位ACK
值为1
,表明这是普通TCP确认报文段。序号字段seq
为x+1
,这是因为客户进程在请求建立连接时选择的初始序号为x。确认字段ack
值为y+1
,表明对服务进程
所选择的初始序号值y的确认
。值得注意的是普通
ACK确认报文段可以
携带数据,但如果不携带数据则不消耗序号,这样发送的下一个报文段序号仍为x+1
服务进程收到确认报文段
后也进入连接已建立
状态,至此,双方的TCP连接建立成功可以进行通信了
建立连接的流程主要分为三步,因此也被称为三次握手,下面是一些握手过程中的要点:
能否只握手两次?
答案是不行的
首先从确认双方收发能力
的角度来看:服务进程收到客户进程的连接请求报文,知道客户进程
有发送能力
。客户进程收到服务进程的请求确认报文,知道服务进程
有接收和发送能力
。如果此时客户进程不返回普通确认报文,那么服务进程就无法确认客户进程是否收到请求确认报文,不知道
客户进程有没有接收能力。本人认为以上说法适用于这样一种情形
——在两次握手建立连接后,客户进程并没有发送数据,服务进程想发送数据却因为不确定客户进程的接收能力而无法发送
然后从资源
的角度来看:客户进程发送的连接请求报文
可能在网络上长时间滞留
,那么客户进程会因为长时间没收到服务进程的请求确认报文而重传
请求报文。假设重传请求报文后成功建立了连接并且双方基于连接完成了通信,那么客户进程就处于连接关闭
状态。如果此时在网络上滞留的请求报文到达了服务进程,那么服务进程会认为
有客户进程发起了新的请求,此时服务进程返回请求确认报文并进入连接已建立状态,等待
客户进程发送数据。但是客户进程并没有发起新的请求所以它会忽略请求确认报文,而此时服务进程并不知情而一直等待。
三次握手出现丢包怎么办?
丢包主要分为以下三种情况:
- 客户进程
连接请求报文
丢失:报文丢失后客户进程会因为迟迟收不到服务进程的响应报文而触发超时重传
。注意,重传次数是有限的
,每次重传等待时间不同
- 服务进程
请求确认报文
丢失:报文丢失后服务进程会因为迟迟收不到客户进程的响应报文而触发超时重传
。同样的,服务进程重传次数也是有限
的且每次重传等待时间不同
。另外,此时客户进程也
会因为收不到确认报文而触发超时重传 - 客户进程
普通确认报文
丢失:此时,客户进程已经单方面进入连接已建立状态并且可以发送数据了。那么普通确认报文丢失就要分为以下几种情况:a)
客户进程和服务进程都没有数据要发送,此时服务进程会因为迟迟收不到客户进程的响应而触发超时重传,重传次数有限。b)
客户进程发送了数据,此时服务进程根据报文中的ack字段
可以知道客户进程已经收到了请求确认报文,服务进程进入连接已建立状态并接受数据包。c)
服务进程想发送数据却发送不了,触发超时重传,重传次数有限。以上a和c中如果服务进程重传次数用尽
仍未收到客户进程的响应且客户进程发送数据到服务进程,此时服务进程会返回RST(Reset the connection)响应
关闭连接
为什么初始序号是随机的?
初始序号ISN(Initial Sequence Number)
是双方协商的主要参数之一,用于让对方知道接下来接收数据的时候如何按序号组装数据,详情可参考这里
ISN随机主要有两个方面的考虑:
安全性
:如果ISN可预测,那么恶意者可以伪装IP
向服务器发送连接请求,随后恶意者使用预测
的服务器ISN向服务器再发送一个普通确认报文,那么服务器就会和伪装过的恶意者建立起连接。这样,无论谁都可以伪装任意IP和服务器建立连接数据可靠性
:客户进程和服务进程可能会频繁的建立和断开连接,假设每次建立连接都使用固定的ISN,那么可能出现下述情况:某次通信过程中的数据包在网络上长时间滞留
,此次连接结束前
仍未到达服务进程。客户进程后来和服务进程建立了新的连接,此时滞留的数据包
到达了服务进程且携带的序号
刚好和服务进程在这次新的连接中期待的序号相同,那么服务器就接受了旧的数据导致数据错误
。详情可参考这里
RFC793
提到ISN随机算法:ISN = M + F(localhost, localport, remotehost, remoteport)
。
M是计时器每隔4毫秒加1, F是hash算法
为什么第一二次握手不能携带数据?
首先这是协议的规定,在前两次握手中报文首部的同步位SYN置为表示本次数据报不能携带数据。然后,从连接建立的角度来看,此时连接尚未建立,服务进程和客户进行都不知道对方的接收能力,不能携带数据也是理所当然的。最后,从安全的角度
来看,如果前两次握手能携带数据,那么攻击者可以在第一次握手时携带大量数据
然后大量发送
连接建立请求从而消耗
服务器大量资源
。
第三次握手可以携带数据是因为客户进程收到服务进程的请求确认报文后就已经知道服务进程的接收和发送能力,此时客户进程就可以进入连接已建立状态,携带数据也是理所当然的
在这里额外提一下握手过程中的SYN攻击
SYN攻击
攻击者发出连接建立请求报文(SYN报文)
并在收到服务进程的请求确认报文后不回复
任何报文(不执行第三次握手),此时的TCP连接处于半连接状态
,服务进程会把处于半连接状态的连接放入半连接队列
中。由于迟迟收不到客户进程的普通请求确认报文,服务进程会一次次的触发超时重传
。虽然重传有次数限制,但这也会耗费一定的时间。如果服务进程短时间收到大量同样的恶意连接请求就会耗费
服务器大量资源
释放连接
释放连接的请求一般由客户进程主动发起
当客户进程想要释放连接时就发送TCP连接释放报文段
,并在发送报文后进入终止等待1
状态。报文段首部中终止位FIN
和确认位ACK
值为1
,表明这是TCP连接释放报文段。序号字段seq
值u
等于客户进程之前发送过的
数据最后一个字节的序号加1。确认号字段ack
的值v
等于客户进程之前收到过的
数据最后一个字节的序号加1。值得注意的是FIN为1
的报文即使不携带数据也要
消耗一个序号
服务进程收到连接释放报文段
后返回普通TCP确认报文段
,并在发送报文后进入关闭等待
状态。报文段首部中确认位ACK
值为1
,表明这是普通TCP确认报文段。序号字段seq
的值v
等于服务进程之前已发送过
的数据最后一个字节的序号加1。确认号字段ack
值为u+1
,表明对客户进程连接释放报文的确认
此时的TCP连接处于半关闭状态
,客户进程不能再发送数据但是可以接收数据,服务进程还可以发送数据
客户进程收到普通TCP确认报文段
后便进入终止等待2
状态,等待服务进程关闭连接。当服务进程数据传输完毕就向客户进程发送连接释放报文
,并在发送报文后进入最后确认状态
。报文段首部中终止位FIN
和确认位ACK
值为1
,表明这时TCP连接释放报文段。序号字段seq
值w
等于服务进程已发送过
的数据最后一个字节的序号加1。确认号字段ack
值u+1
,表明对客户进程连接释放报文的重复确认
客户进程收到连接释放报文段
后返回普通TCP确认报文段
,并在发送报文后进入时间等待
状态。报文段首部中确认位ACK
值为1
,表面这是普通TCP确认报文段。序号字段seq
值u+1
。确认字段ack
值w+1
,表明对服务进程连接释放报文的确认
服务进程收到确认报文
后便进入连接关闭
状态,客户进程则需要等待
两倍报文段最大生存时间MSL((Maximum Segment Lifetime)
才能进入关闭状态
释放连接的流程主要分为四步,因此也被称为四次挥手
,下面是一些挥手过程中的要点:
为什么客户端需要等待2倍MSL时间?
MSL是任何报文能在网络上存活的最长时间
,超过时间的报文将被网络丢弃
首先从丢包的角度看,如果最后一次挥手的报文段丢失
,服务进程会因为迟迟收不到确认报文而触发超时重传
,但是此时客户进程已经进入关闭状态
,重传的连接释放报文会被直接丢弃
,这样服务进程将一直收不到
客户进程对服务进程连接释放的确认,从而造成服务进程等待直到重传次数耗尽
等待2MSL时间可以确保客户进程以较大的概率
能收到服务进程重传的
连接释放报文,当客户端收到重传的连接释放报文后计时器会重新计时
然后是数据的可靠性角度,等待2MSL时间可以确保本次连接产生的报文都从网络中消失
,防止有些旧报文滞留在网络上干扰新连接的数据
RFC793建议MSL为两分钟,但是时间等待状态需要耗费CPU和占用端口
等资源,如果出现过多处于时间等待状态的连接可能耗费大量的CPU资源以及无法发起新的连接
,所以MSL一般都根据不同的网络情况会进行调整
四次挥手出现丢包怎么办?
挥手过程中出现丢包可以参考握手过程的丢包处理,主要都是触发超时重传
,重传都有次数限制超过
之后就进入关闭状态
,需要注意的是普通TCP确认报文(ACK报文)
是不重传的,只有连接释放报文(FIN报文)
会重传
为什么需要挥手四次?
通俗的讲就是一方没有数据需要发送想关闭连接了,另一方给予确认,这里产生两次挥手。但是,另一方可能还需要发送数据,等另一方也没数据需要发送了这里又产生一次关闭和确认的过程,产生两次挥手。以上过程加起来就一共四次挥手
如果服务进程在收到客户进程的连接释放请求后也没有数据需要发送了,那么能不能把确认报文和连接释放报文合并(二、三次挥手合并)呢?可以的话就只需要三次挥手
保活计时器是什么?
建立连接后一方如果故障即不发生数据又不关闭连接会导致另一方一直等待,为了避免这种情况就需要设置一个计时器,如果在一定时限内没有收到另一方的任何报文,此时会发送探测报文看看对方是否正常。若连续10个探测报文都无响应,那么就可以判断另一方故障直接关闭
连接。当然,每次
收到数据保活计时器都会重置
客户进程在收到服务进程的连接释放报文后又收到服务进程的数据报会怎样?
这个问题就是说服务进程的数据在网络上滞留了,这些滞留的数据在服务进程发送了连接释放报文后才到达客户进程
在终止等待2
状态的客户进程收到连接释放报文后通过检查首部的序号字段可以发现该报文是乱序报文
,此时客户进程会把报文放入乱序队列
中并且不会进入时间等待状态。等客户进程收到滞留的数据时会检查乱序队列
看能不能找到相应序号的数据,如果找到了还会查看其首部的终止位FIN
,发现其值为1
才会进入时间等待状态。详情可参考这里