游戏服务器的数据传输通常基于TCP协议。了解TCP连接的建立过程和释放过程,才能更好的控制网络连接、优化网络性能。
比如,清楚TCP连接释放过程,才能理解SO_REUSEADDR选项控制端口能否立刻复用的意义:这是因为主动释放TCP连接的一方会进入TIME_WAIT状态,在这个状态下,这个连接的端口是不能被复用的。但游戏服务器希望重启之后能立刻绑定这个端口,因此一般会选择启用这个选项。
TCP三次握手
TCP建立连接的图示如下:
假定主机A运行的是TCP客户程序,B运行TCP服务器程序。
最初双方都处于CLOSED(关闭)状态。一开始,B的TCP服务器进程会先创建传输控制块TCB,准备接收客户进程的连接请求。然后B就进入LISTEN(收听)状态。
A的TCP客户进程也是先创建传输控制块TCB。然后打算建立TCP连接时,向B发出TCP连接请求报文。报文首部的同步位SYN置1,同时选择一个初始序号seq=x。这时,TCP客户进程进入SYN-SEND(同步已发送)状态。
B在接收到连接请求报文后,若同意建立连接,会向A发送确认。首部的SYN和ACK置1,确认号为x+1,选择初始序号为y。这时,TCP服务器进程进入SYN-RCVD(同步收到)状态。
A收到B的确认后,还要向B发送确认。首部的ACK置1,确认号为y+1,自己的序号为x+1。这时,TCP连接已经建立,A进入ESTABLISHED(已建立连接)状态。
B收到A的确认后,也进入ESTABLISHED状态。
为什么A还要发送一次确认呢?
这是防止"已失效的TCP连接请求报文段"到了B,因而产生错误。
假设A发送了一个TCP连接请求报文段,但迟迟没有收到B的确认。那么A会再发一次TCP连接请求报文段,然后成功建立连接。数据传输完毕后,关闭了连接。
若为丢失的TCP连接请求报文报只是在某个网络结点长时间滞留了,导致在TCP连接释放后的某个时间才到达B。那么B就会以为A发起了新的TCP连接请求,并向A发送确认。
如果A不需要发送最后一次确认。那么B在发送了确认报文段后,就认为连接已经建立,并等待A发送数据。但A其实没有发送TCP连接请求报文段,就会忽略B发送的确认,也不会向B发送数据。那么,B的很多资源就这样白白浪费了。
什么是传输控制块TCB?
传输控制块(Transmission Control Block)存储了每一个连接中的一些重要信息,如:TCP连接表,指向发送和接收缓存的指针,指向重传队列的指针,当前的发送和接收序号,等等。
TCP四次挥手
TCP释放连接的图示如下:
数据传输后,连接的双方都可以释放连接。现在A和B都处于ESTABLISHED状态。
A先发送TCP连接释放报文段。首部的终止控制位置1,序号为u。这时,A进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
B收到连接释放报文段后发送确认,确认号为u+1,自己的序号seq为v。然后B进入CLOSE-WAIT(关闭等待)状态。这时,A到B方向的连接就释放了,TCP连接处于半关闭(half-close)状态,即A已经没有数据要发送了,但B要发送数据,A还要接收。
A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发送连接释放报文段。
若B没有要向A发送的数据了,那么就发送TCP连接释放报文段。此时B的序号为w。B还必须重复上次已经发送过的确认号ack=u+1。这时,B进入LAST-ACK(最后确认)状态。
A在收到B的连接释放报文段后,必须对此发出确认。首部ACK置1,确认号为w+1,自己的序号为u+1。然后进入TIME-WAIT(时间等待)状态。经过时间等待计时器(TIME-WAIT timer)设置的2MSL(Maximun Segment Lifetime,最长报文段寿命)后,即经过4分钟后,A进入CLOSED。在A撤销相应的传输控制块TCB后,就结束了这次的连接。
B在收到A的确认后,就进入CLOSED。同样,B在撤销相应的传输控制块TCB后,就结束了这次的TCP连接。
为什么A在TIME-WAIT状态必须等待2MSL的时间呢?
- 为了保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段可能会丢失,导致B接收不到对已发送的FIN+ACK报文段的确认。那么,B会超时重传这个FIN+ACK报文段,A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着,A重传一次确认,重新启动2MSL计时器。最后,A和B都能够正常释放连接。若A不在TIME-WAIT状态下等待一段时间,而是在发送ACK报文段后立刻释放连接,就无法收到B重传的FIN+ACK报文段,也不会再发送一次确认。那么,B就无法按正常步骤释放连接。
- 防止"已失效的TCP连接报文"出现在本连接中。A在发送完最后一个ACK报文段之后,再经过2MSL,就可以使本连接持续时间内所产生的所有报文都从网络中消失。
一些报文段的规定
- TCP规定,SYN报文不能携带数据,但要消耗一个序号。
- TCP规定,ACK报文段可以携带数据。但如果不携带数据则不消耗序号。
- TCP规定,FIN报文即使不携带数据,也消耗掉一个序号。