深入理解TCP/IP协议

很多人总觉得学习TCP/IP协议没什么用,觉得日常编程开发只需要知道socket接口怎么用就可以了 。如果大家定位过线上问题就会知道,实际上并非如此 。如果应用在局域网内,且设备一切正常的情况下可能确实如此,但如果一旦出现诸如中间交换机不稳定、物理服务器宕机或者其它异常情况时,此时引起的问题如果只停留在套接字接口的理解层面将无法解决 。因此,深入理解TCP/IP协议,对我们分析异常问题有很大的帮助 。
下图是网络通信中常见的架构,也就是CS架构 。其中程序包括两部分,分别为客户端(Client)和服务端(Server) 。当然,实际的环境还要复杂的多,在客户端和服务端之间可能有多种不同种类和数量的设备,这些设备都会增加网络通信的复杂性 。自然,也会增加程序开发容错的复杂性 。

深入理解TCP/IP协议

文章插图
图1 基本架构
TCP的基本流程在分析异常情况之前,我们先回忆一下TCP协议的基本逻辑 。在客户端和服务端能够收发数据之前首先必需建立连接 。连接的建立在协议层面也是通过收发数据包完成,只不过在用户层面就是客户端调用了一个connect函数 。连接的过程俗称“三次握手”,具体流程如图2所示 。
深入理解TCP/IP协议

文章插图
图2 TCP的三次握手流程
TCP连接的断开也是比较复杂的,需要经过所谓的“四次挥手”的流程 。其原因是因为TCP是双工通信,分别需要从客户端和服务端2侧断开连接 。
深入理解TCP/IP协议

文章插图
图3 TCP的四次挥手
另外一个比较重要的内容是TCP协议的状态转换,理解了这个内容,我们才能清楚出现各种异常情况下数据包的内容 。
深入理解TCP/IP协议

文章插图
图4 TCP状态转换图
本文只是简单回忆一下TCP的基本流程,详细的内容可以参考本号之前的文章《从TCP到Socket,彻底理解网络编程是怎么回事
异常情况分析了解了TCP的基本流程之后,我们再看一下各种异常情况 。这些异常情况才是我们在后续解决问题的时候的关键 。了解了这些异常情况及原理,后面解决问题才能游刃有余 。
1. 试图与一个不存在的端口建立连接(主机正常)
这里的不存在的端口是指在服务器端没有程序监听在该端口 。我们的客户端就调用connect,试图与其建立连接 。这时会发生什么呢?
这种情况下我们在客户端通常会收到如下异常内容:
[Errno 111] Connection refused(连接拒绝)
具体含义可以查一下linux的相关手册,或者用搜索引擎搜索一下 。试想一下,服务端本来就没有程序监听在这个接口,因此在服务端是无法完成连接的建立过程的 。我们参考‘三次握手’的流程可以知道当客户端的SYNC包到达服务端时,TCP协议没有找到监听的套接字,就会向客户端发送一个错误的报文,告诉客户端产生了错误 。而该错误报文就是一个包含RST的报文 。这种异常情况也很容易模拟,我们只需要写一个小程序,连接服务器上没有监听的端口即可 。如下是通过wireshark捕获的数据包,可以看到红色部分的RST报文 。
深入理解TCP/IP协议

文章插图
图5 数据包截图
继续深入理解一下,在操作系统层面,TCP的服务端实际上就是从网卡的寄存器中读取数据,然后进行解析 。对于TCP自然会解析出目的端口这个关键信息,然后根据这个信息查看有没有这样的套接字 。这个套接字是什么呢?在用户层面是一个文件句柄,但在内核中实际是一个数据结构,里面记录了很多信息 。这个数据结构存储在一个哈希表中,通过函数__inet_lookup_skb(net/inet_hashtables.h)可以实现对该数据结构的查找 。对于上述情况,自然无法找到该套接字,因此TCP服务端会进行错误处理,处理的方式就是给客户端发送一个RST(通过函数tcp_v4_send_reset进行发送) 。
2. 试图与一个某端口建立连接但该主机已经宕机(主机宕机)
这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,仍然尝试去与其建立连接 。这种场景也是分为2种情况的,一种是刚刚宕机,另外一种是宕机了很长时间 。为什么要分这2种情况?
这主要根ARP协议有关系,ARP会在本地缓存失效,TCP客户端就无法想目的服务端发送数据包了 。


推荐阅读