从协议角度来看TCP通信的各种异常

前言

•本文所介绍内容,相信大家都或多或少已经掌握,在这里尽量用一泡屎的时间来做个简单的回顾巩固。

•另外,时间仓促写得很烂,大家将就看吧。

我们先回忆一下TCP协议的基本逻辑。在客户端和服务端能够收发数据之前首先必需建立连接。连接的建立在协议层面也是通过收发数据包完成,只不过在用户层面就是客户端调用了一个connect函数。连接的过程俗称“三次握手”。

连接建立

•另外一个比较重要的内容是TCP协议的状态转换,理解了这个内容,我们才能清楚出现各种异常情况下数据包的内容。

异常情况分析

1.试图与一个不存在的端口建立连接(主机正常)

这里的不存在的端口是指在服务器端没有程序监听在该端口。我们的客户端就调用connect,试图与其建立连接。这时会发生什么呢?

这种情况下我们在客户端通常会收到如下异常内容:Connection refused(连接拒绝)

而该错误报文就是一个包含RST的报文。

2.试图与一个某端口建立连接但该主机已经宕机(主机宕机)

这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,仍然尝试去与其建立连接。这种场景也是分为2种情况的,一种是刚刚宕机,另外一种是宕机了很长时间。

为什么要分这2种情况?

这主要根ARP协议有关系,如果宕机时间很长ARP会在本地缓存失效,TCP客户端就无法向目的服务端发送数据包。如果是刚刚宕机的情况,此时客户端是可以向服务端发送数据包的。但是由于服务器宕机,因此不会给客户端发送任何回复。由于客户端并不知道服务端宕机,因此会重复发送SYNC数据包,最终会产生超时。

3.建立连接时,服务器应用被阻塞(或者僵死)

在客户端建立连接的过程中服务进程应用处于僵死状态,这种情况在实际中也会经常出现(我们假设仅仅应用程序僵死,而内核没有僵死)。此时会出现什么状态?TCP的三次是否可以完成?客户端是否可以收发数据?

在用户层面我们知道,服务端通过accept接口返回一个新的套接字,这时就可以和客户端进行数据往来了。也就是在用户层面来说,accept返回结果说明3次握手完成了,否则accept会被阻塞。在我们假设的情况下,其实就相当于应用程序无法进行accept操作了。

accept会通过软中断陷入内核中,从队列中查找是否有处于ESTABLISHED状态的套接字。如果有则返回该套接字,否则阻塞当前进程。也就是说这里只是一个查询的过程,并不参与三次握手的任何逻辑。另外如果连接已建立,accept没有返回,客户端是否可以发送数据?答案是可以的。因为数据的发送和接受都是在内核态进行的。

总的来说,TCP的客户端是否可以发送数据与服务端程序是否工作没有任何关系。

4.服务进程crash

由于服务端程序crash了,此时在服务端操作系统中的套接字数据结构已经被释放,因此在协议层收到数据包的时候无法找到对应的套接字进行处理,于是发送了一个RST报文,客户端会收到RST报文。

5.手动杀死服务端进程

在kill进程时TCP协议是能够感知到的,并且发送的FIN报文。kill进程是通过shell想内核发送了SIGKILL或者SIGTERM,内核接收到该信号之后会进行相应的扫尾工作。

6.服务主机关机

在系统关闭时,init进程会给所有进程发送SIGTERM信号,等待一段时间,然后再给所有仍在运行的进程发送SIGKILL信号。当服务器进程死掉时,会关闭所有文件描述符。带来的影响和上面杀死server相同。

7.服务器进程所在的主机宕机后重启

在客户端发出请求前,服务器端主机经历了宕机—重启的过程。当客户端TCP把分节发送到服务器端所在的主机,服务器端所在主机的TCP丢失了崩溃前所有连接信息,即TCP收到了一个根本不存在连接上的报文,会响应一个RST分节。

8.产生RST的情况

•试图与一个不存在的端口建立连接。

•接收缓冲区还有数据未读取,此时调用close会发送RST。

•向一个已关闭的连接write,对端会发送RST,继续write则产生SIGPIPE。

•使用SO_LINGER规定close行为是发送RST,而不是发送FIN。

……