接上一篇:【网络面试必问】浏览器如何委托协议栈完成消息的收发
1. 协议栈
一直对操作系统系统的内核协议栈理解的模模糊糊,借着这一篇博客做一下简单梳理。 我觉得最直白的理解,内核协议栈就是操作系统中的一个网络控制软件,就是一段程序代码,它负责和网卡驱动程序交互,实现消息的发送和接收。在探究协议栈内部原理之前,先从整体上看下TCP/IP软件采用的分层结构。
在这张图中,可以看到从上往下的层级结构中,上面的部分会向下面的委派工作,下面的部分实际来执行。其中,操作系统这一层,协议栈包含了两部分,第一部分是负责TCP协议和UDP协议的数据收发部分,他们直接对接的是应用程序的委托,第二部分是IP协议控制的网络包收发操作部分,比如TCP协议就会将数据包交由IP协议来做包切分,然后发送给通讯对象。
当然IP协议也不能直接发送网络包,IP下面的网卡驱动程序负责控制网卡硬件,最下面的网卡实现最终的收发操作,也就是对网线中的信号进行发送和接收。
2. 套接字
套接字也是个让人迷惑不解的网络词汇,英文是socket,大概是迷人的英译汉困惑了很多人,看一下它的英文释义:
所以吧,完全可以把客户端和服务端的套接字想象成两个插座,然后中间用双插头的电线连接起来,各种数据就从这根电线里面流动。
其实,套接字socket就是位于协议栈内部的一块内存空间,记录了用于通讯操作的控制信息,比如通讯对象的IP地址、端口号以及通讯操作的进行状态等等,这个内存空间在编码层面被命名为socket的对象实体。这里面提到了通讯操作的控制信息,比如,在发送数据时,数据会被差分成很多网络包,发送方要知道接收方是否收到了某个网络包,接收方可能返回了收到网络包的确认信息,也可能包丢失,所以发送方的套接字中就会记录某个包对方已收到的消息或者某个包已发送了的多场时间,以便确定是否重新发送。
当然,套接字中记录的可远不止这些控制信息,上面的只是其中一个例子。套接字中记录了各种用于控制通讯操作的控制信息,协议栈通过这些信息决定下一步的动作,这就是套接字的作用。
在计算机中,我们可以通过 netstat
命令查看真正的套接字,如下:
3. 创建套接字
前面,我们提到浏览器通过调用Socket库的socket()
程序组件来创建套接字,现在我们深入到协议栈内部,看一下实现原理。
- 申请内存: 首先,协议栈会开辟一块存放套接字的内存空间,相当于存放控制通讯操作的控制信息的容器,但是这个容器是空的。
- 返回描述符: 其次,协议栈会将代表这个套接字的描述符返回给应用程序,用于后续区分多个套接字。
- 存放描述符: 应用程序如浏览器会将此描述符存放于自己的内存空间中,后续收发消息等操作时就可以把数据和描述符告知协议栈,以进行后续操作。
但是直到现在,这块内存还是空的,没有存放任何控制信息。
4. 连接服务器
连接实际上是通讯的双方交换控制信息,并且在自己的套接字中记录这些信息,比如对方的IP和端口号就是最典型的例子。前面我们提到,套接字刚创建之初是没有存放任何数据的,也不知道通讯对象是谁。
另外,连接阶段,还会分配一块临时存放数据的内存空间,即缓冲区,用于存放接下来数据收发阶段的信息。
前面,我们提到,浏览器会通过调用Socket库中的connect()
程序组件完成连接的过程:
connect(<套接字描述符>, <服务器IP地址>, <服务器端口号>, ...);
通过connect()
函数,浏览器告诉协议栈,找到描述符对应的套接字,并提供了连接对象的IP地址和端口号,下面连接的动作就开始了,在协议栈中,TCP模块会与此IP对应的服务器的TCP模块交换控制信息。
这里会经过三次握手的过程,在熟悉这个过程之前,我们需要了解网络包的结构,正常情况每次数据请求都会被拆分成很多个网络包,这些网络包会包含很多头部信息,如TCP头部控制信息、IP头部控制信息等,通过这些头部信息最终找到目标服务器。但是在连接阶段,因为还没有数据产生,所以数据块部分是空的,只有各种头部控制信息。
下面我们看一下简略版的三次握手阶段:
- 第一次握手: 首先,客户端创建一个不包含数据的网络包,只有表示连接控制信息的头部,头部中包含IP、端口号等信息。同时将头部中的控制位SYN比特标记为1,表示连接。接下里,TCP模块会将此网络包传递给IP模块并委托它发送给服务器。根据TCP头部中的端口号,可以顺利找到服务器中对应的套接字。
- 第二次握手: 服务器端的套接字,会写入相应信息,这里主要包含客户端的IP地址,端口号等,并将状态改为正在连接。同时服务端的TCP模块会将响应的网络包头部设置发送方和接收方的端口号,同时将SYN比特标记为1,ACK控制位标记为1,ACK=1表示收到了第一次握手的报文。网络中经常会发生错误导致网络包丢失,因此双方在通讯时必须相互确认对方已收到网络包,设置ACK标记位就是来进行这一步确认的。以上步骤完成后,服务器TCP模块也会委托IP模块进行网络包的发送。
- 第三次握手: 服务器返回的网络包到达客户端后,客户端会通过TCP头部的SYN=1确认操作是否成功,如成功,会在客户端套接字中写入服务器的IP地址、端口号,同时将状态改为连接完成。接下来,客户端也要将ACK比特位设置为1并发送给服务器,告诉服务器刚才的网络包已收到,服务器接收到这个包之后,连接操作才算全部完成。
- 通过上面三次握手的步骤,可以看到服务器和客户端的套接字写入信息分别是在第二步和第三步完成的。接下来,套接字就可以进入收发数据的状态了,可以想象成有根管子将这两个套接字连在了一起,这就是连接。至此,协议栈的操作就结束了。