TCP 是一个面向连接的协议,所以在连接双方发送数据之前,都需要首先建立一条连接。这和前面讲到的协议完全不同。前面讲的所有协议都只是发送数据 而已,大多数都不关心发送的数据是不是送到,UDP 尤其明显,从编程的角度来说,UDP 编程也要简单 的多----UDP 都不用考虑数据分片。
书中用 telnet 登陆退出来解释 TCP 协议连接的建立和中止的过程,可以看到,TCP 连接的建立可以简单的称为三次握手,而连接 的中止则可以叫做四次握手。
在建立连接的时候,客户端首先向服务器申请打开某一个端口(用 SYN 段等于1的 TCP 报文),然后服务器端发回一个 ACK 报文 通知客户端请求报文收 到,客户端收到确认报文以后再次发出确认报文确认刚才服务器端发出的确认报文(绕口么),至此,连接的 建立完成。这就叫做三次握手。如果打算让双方都做好 准备的话,一定要发送三次报文,而且只需要三次报文就可以了。
可以想见,如果再加上 TCP 的超时重传机制,那么 TCP 就完全可以保证一个数据包被送到目的地。
TCP 有一个特别的概念叫做 half-close,这个概念是说,TCP 的连接是全双工(可以同时发送和接收)连接,因此在关闭 连接的时候,必须关闭传和送两个方向上的连接。客户机给服务器一个 FIN 为1的 TCP 报文,然后服务器返回给客户端一个确认 ACK 报文, 并且发送一个 FIN 报文,当客户机回复 ACK 报文后(四次握手),连接就结束了。
在建立连接的时候,通信的双方要互相确认对方的最大报文长度(MSS),以便通信。一般这个 SYN 长度是 MTU 减去固定 IP 首部 和 TCP 首部长度。 对于一个以太网,一般可以达到1460字节。当然如果对于非本地的 IP,这个 MSS 可能就只有536字节,而且,如 果中间的传输网络的 MSS 更佳的小的 话,这个值还会变得更小。
客户端的状态可以用如下的流程来表示: CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED 以上流程是在程序正常的情况下应该有的流程,从书中的图中可以看到,在建立连接时,当客户端收到 SYN 报文的 ACK 以后,客户端就打开了数据交互地 连接。而结束连接则通常是客户端主动结束的,客户端结束应用程序以后,需要经历 FIN_WAIT_1, FIN_WAIT_2等状态,这些状态的迁移就是前 面提到的结束连接的四次握手。
服务器的状态可以用如下的流程来表示:
CLOSED->LISTEN->SYN 收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED 在建立连接的时候,服务器端是在第三次握手之后才进入数据交互状态,而关闭连接则是在关闭连接的第二次握手以后(注意不是第四次)。而关闭以后还要等待客户端给出最后的 ACK 包才能进入初始的状态。即时通讯聊天软件app开发可以加蔚可云咨询
书中的图还有一些其他的状态迁移,这些状态迁移针对服务器和客户端两方面的总结如下:
LISTEN->SYN_SENT:对于这个解释就很简单了,服务器有时候也要打开连接的嘛。
SYN_SENT->SYN:收到服务器和客户端在 SYN_SENT 状态下如果收到 SYN 数据报,则都需要发送 SYN 的 ACK 数据报并把自己的状态调整到 SYN 收到状态,准备进入 ESTABLISHED
SYN_SENT->CLOSED:在发送超时的情况下,会返回到 CLOSED 状态。
SYN_收到->LISTEN:如果受到 RST 包,会返回到 LISTEN 状态。
SYN_收到->FIN_WAIT_1:这个迁移是说,可以不用到 ESTABLISHED 状态,而可以直接跳转到 FIN_WAIT_1状态并等待关闭。
书中给的图里面,有一个 TIME_WAIT 等待状态,这个状态又叫做2MSL 状态,说的是在 TIME_WAIT2发送了最后一个 ACK 数 据报以后, 要进入 TIME_WAIT 状态,这个状态是防止最后一次握手的数据报没有传送到对方那里而准备的(注意这不是四次握手, 这是第四次握手的保险状态)。这个 状态在很大程度上保证了双方都可以正常结束,但是,问题也来了。
由于插口的2MSL 状态(插口是 IP 和端口对的意思,socket),使得应用程序在2MSL 时间内是无法再次使用同一个插口的,对于 客户程序还好 一些,但是对于服务程序,例如 httpd,它总是要使用同一个端口来进行服务,而在2MSL 时间内,启动 httpd 就会出现 错误(插口被使用)。为了避免 这个错误,服务器给出了一个平静时间的概念,这是说在2MSL 时间内,虽然可以重新启动服务器,但是这个服务器还是要平静的等待2MSL 时间的过去才能进行下一次连接。
这就是著名的半关闭的状态了,这是在关闭连接时,客户端和服务器两次握手之后的状态。在这个状态下,应用程序还有接受数 据的能力,但是已经无法发送 数据,但是也有一种可能是,客户端一直处于 FIN_WAIT_2状态,而服务器则一直处于 WAIT_CLOSE 状态,而直到应用层来决定关闭这个状态。
RST 是另一种关闭连接的方式,应用程序应该可以判断 RST 包的真实性,即是否为异常中止。而同时打开和同时关闭则是两种特 殊的 TCP 状态,发生的概率很小。
前面曾经讲述过 UDP 的服务器设计,可以发现 UDP 的服务器完全不需要所谓的并发机制,它只要建立一个数据输入队列就可以。 但是 TCP 不同,TCP 服务器对于每一个连接都需要建立一个独立的进程(或者是轻量级的,线程),来保证对话的独立性。所以 TCP 服务器是并发的。而且 TCP 还需要配备一个呼入 连接请求队列(UDP 服务器也同样不需要),来为每一个连接请求建立对话进程,这 也就是为什么各种 TCP 服务器都有一个最大连接数的原因。而根据源主机的 IP 和端口号码,服务器可以很轻松的区别出不同的会话, 来进行数据的分发。