前言

这个系列的博客是针对阅读《网络是怎样连接的》([日] 户根勤, 译 [中] 周自恒, ISBN: 9787115441249)一书的记录与思路整理。
有些概念是做纯路由交换的网络工程师很少接触但又很重要的部分,因此整理到这里以做分享。

连接的本质是什么?

以网页访问为例,我们知道,客户端和服务器刚创建的套接字中只包含了本端通信对象的信息,IP、端口、协议等等。客户端和服务器双方都无法在不知道对端信息的情况下通信,这就需要连接这个步骤来帮助客户端和服务器交换双方的信息。

客户端:应用程序将服务器的IP地址、端口号等信息告诉协议栈,协议栈以此为目的去连接远程的服务器。

服务器:等待客户端主动向自己告知客户端的IP地址、端口号等信息。

所以连接实际上是通信双方交换控制信息,在套接字中记录通信所需的必要信息并准备数据收发的一连串操作。像上面提到的客户端将IP地址和端口号告知服务器这样的过程就属于交换控制信息的一个具体的示例。所谓的控制信息,就是用来控制数据收发操作所需要的一些信息,IP地址和端口号是典型的例子。除此之外,还有其它一些控制信息,我们暂且按下不表。

同时连接要为数据收发作准备,我们还需要一块用来临时存放要收发的数据的内存空间,这块内存空间被称之为缓冲区,也会在连接阶段分配。

“使用“连接”这个词是有原因的。通信技术的历史已经有 100 多年,从通信技术诞生之初到几年之前的很长一段时间内,电话技术一直都是主流。而电话的操作过程分为三个阶段:(1)拨号与对方连接;(2)通话;(3)挂断。人们将电话的思路套用在现在的计算机网络中了,所以也就自然而然地将通信开始之前的准备操作称为“连接”了。如果没有这段历史的话,说不定现在我们就不叫“连接”而是叫“准备”了。因此,如果觉得“连接”这个词听起来有些怪,那么用“准备”这个词来替换也问题不大。”

负责保存控制信息的头部

关于控制信息,其实大体上可以分为两类。

C/S 互相联络时的控制信息

客户端和服务器互相联络时交换的控制信息。这类信息在通信的整个过程(连接、收发数据和断开连接)中都需要,这些内容表现在TCP 包头中。

TCP头部格式

头部是用来记录和交换控制信息的。

套接字中控制协议栈操作的信息

应用程序传递来的信息和从通信对象接收到的信息都会保存到缓冲区。

收发数据操作的执行状态等信息也会保存到缓冲区。

协议栈会根据这些信息来执行每一步操作。可以说,套接字的控制信息和协议栈的程序本身就是一体的,因此“协议栈具体需要哪些信息”会根据协议栈本身的实现方式不同而不同。

这一点对通信的双方来说是没有什么问题的。因为,协议栈中的控制信息通信对方是不可见的,只要在通信时按照规则将必要的信息写入头部,客户端和服务器之间的通信就能够成立(通信的过程遵循TCP 协议即可)。例如Windows 系统和Linux 系统的内部结构不同,协议栈的实现方式不同,必要的控制信息也不相同。但是,两个操作系统之间依然能够通信。

“无论协议栈的实现如何不同,IP 地址和端口号这些重要的信息都是共通的。”

连接操作的实际过程(TCP的三次握手)

应用程序调用Socket 库的connect 程序组件就开始了连接的过程。

connect (<描述符>, <服务器IP地址, 端口号>,...)

应用程序进行上面的调用就为协议栈的TCP模块提供了服务器的IP地址和端口号。TCP模块就会与该IP地址对应的TCP模块交换控制信息。

  1. 客户端先创建一个包含表示开始数据收发操作的控制信息的头部。这里关注的重点是发送方和接收方的端口号。(在这一步客户端已经具备了准确找到服务器套接字的能力,搞清楚了我要连接哪个套接字。)
  2. 将头部中的控制位SYN 比特设置为1,此外还需要设置适当的序号和窗口大小。
  3. 当TCP头部创建成功,TCP模块会委托IP模块将信息发送出去。IP模块执行将数据包发送给服务器。
  4. 服务器接收到数据包,服务器的IP模块会将收到的数据传递给TCP模块,TCP模块根据TCP头部的目的端口找到端口号对应的套接字。(从处于连接状态的套接字中找到与TCP头部中记录的端口号相同的套接字)
  5. 服务器的TCP模块开始返回响应。需要在响应的TCP 头部中将接收到的源目端口对调,将SYN比特设置为1,同时还需要将ACK控制位设置为1。服务器TCP模块委托IP模块将信息发送出去。
  6. 客户端IP模块接收到数据,将其传送给TCP模块。客户端的TCP模块通过头部信息确认连接服务器的操作是否成功。如果SYN设置为1表示连接成功,这时TCP模块会向套接字中写入服务器的IP地址、端口号等信息,同时还会将状态改为连接完毕。客户端此时还需要将头部信息的ACK比特设置为1发送回服务器,告诉服务器刚才的响应已经收到。
  7. 当服务器收到这个返回包之后,连接操作算是全部完成。

建立连接之后,协议栈的连接操作就结束了,也就是说connect已经执行完毕,控制流程被交回到应用程序。