从浏览器输入网址开始深入剖析网络的连接过程(二)

本系列文章致力于从浏览器中输入网址(比如 http://www.lab.glasscom.com/,下文皆以此为例),一路追踪到显示出网页内容为止的整个过程中分析网络全貌。

本文为此系列文章的第二篇,主要从协议栈和网卡的角度出发,探索搬运数据的机制。

目录

1、概述

2、协议栈内部结构

3、创建套接字

4、连接服务器

5、收发数据

6、从服务器断开并删除套接字

7、TCP的整体流程

8、IP 与以太网的包收发操作

9、UDP


1、概述

从应用程序(浏览器)收到委托后,协议栈通过 TCP 协议收发数据的操作可以分为 4 个阶段,前一篇文章也提到了,但只是简单介绍了一下,本文将详细说明。此外本文还将探索操作系统中的网络控制软件(协议栈)和网络硬件(网卡)是如何将浏览器的消息发送给服务器的。

接下来通过以下几个部分展开介绍。

2、协议栈内部结构

协议栈的内部如下图所示:

IP 协议:控制网络包收发操作的部分。

网卡驱动程序:负责控制网卡硬件,和普通的嵌入式驱动程序驱动硬件一样,不同厂家操作方式不同。

网卡:负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作。

3、创建套接字

创建套接字阶段,应用程序会调用 socket 申请创建套接字,协议栈根据应用程序的申请执行创建套接字的操作。在这个过程中,协议栈首先会分配用于存放一个套接字所需的内存空间,之后会往里面存入初始状态的控制信息

接下来,需要将表示这个套接字的描述符告知应用程序

4、连接服务器

连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作。但套接字刚刚创建完成的时候,里面并没有存放任何数据,也不知道通信的对象是谁。所以应用程序需要把服务器的 IP 地址和端口号等信息告知协议栈,并在套接字中进行记录。

此外当数据收发操作时,还需要一块用来临时存放要收发的数据的内存空间,这块内存空间称为缓冲区,它也是在连接操作的过程中分配的。

套接字中存放控制信息(这里指控制信息的第二类),缓冲区中存放收发的数据。

其中控制信息分为两类:

(1)客户端和服务器相互联络时交换的控制信息(TCP 头部中记录的信息)

这些信息不仅连接时需要,包括数据收发和断开连接操作在内,整个通信过程中都需要。这些信息在 TCP 头部控制字段中进行了定义,这些字段是固定的。TCP 头部信息(只列出了必需字段,一些可选字段没有列出)如下图所示:

此外以太网和 IP 协议也有自己的控制信息分别存在于 MAC 头部和 IP 头部。

(2)协议栈控制操作的信息,保存在套接字中(即本地套接字控制信息,浏览器和协议栈交互时使用)

应用程序传递来的信息以及从通信对象接收到的信息,还有收发数据操作的执行状态等信息都会保存在这里,协议栈会根据这些信息来执行每一步的操作。

这些信息会根据协议栈本身的实现方式不同而不同,通信双方不可见。

套接字的内容有:

协议类型(TCP或UDP)、计算机本身(本地端)的 IP 地址和端口号、通信对象远程端的 IP 地址和端口号、通信状态(监听等待连接或完成连接正在通信),进程标识符 PID 等。

注:

套接字的协议类型可能是在创建套接字或者建立连接时由应用程序指定的。(猜测)

计算机本身(本地端)的 IP 地址可能是在创建套接字或者建立连接时查询路由表得到的。(擦测)

计算机本身(本地端)的端口号是在创建套接字过程中创建的。

通信对象远程端的 IP 地址和端口号是收到服务器端返回连接的ACK包中得到的。

实际的连接过程如下:

这个过程是从应用程序调用 Socket 库的 connect 开始的,

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

上面的调用提供了服务器的 IP 地址和端口号,这些信息会传递给协议栈中的 TCP 模块。然后,TCP 模块会与该 IP 地址对应的对象,也就是与服务器的 TCP 模块交换控制信息,这一交互过程包括下面几个步骤。

(1)客户端先创建一个包含表示开始数据收发操作的控制信息的头部。

(2)然后将头部的控制位的 SYN 比特设置为 1,设置合适的序号和窗口大小。

(3)接下来 TCP 模块会将信息传递给 IP 模块并委托它进行发送。

(4)网络包通过网络到达服务器。

(5)服务器上的 IP 模块会将接收到的数据传递给 TCP 模块。

(6)服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字(处于等待连接状态)

(7)当找到对应的套接字之后,套接字中会写入客户端的 IP 地址,端口号等信息,并将状态改为正在连接。

(8)然后服务器返回响应,这个过程和客户端一样,需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特。此外,在返回响应时还需要将 ACK 控制位设为 1,这表示已经接收到相应的网络包。

(9)接下来,服务器 TCP 模块会将 TCP 头部传递给 IP 模块,并委托 IP 模块向客户端返回响应。

(10)然后,网络包就会返回到客户端,通过 IP 模块到达 TCP 模块,并通过 TCP 头部的信息确认连接服务器的操作是否成功。如果 SYN 为 1 则表示连接成功,这时会向套接字中写入服务器的 IP 地址、端口号等信息,同时还会将状态改为连接完毕。

(11)相应地, 客户端也需要将 ACK 比特设置为 1 并发回服务器,告诉服务器刚才的响应包已经收到。当这个服务器收到这个返回包之后,连接操作全部完成。

5、收发数据

(1)数据发送时机

应用程序调用 write 将要发送的数据交给协议栈,协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。需要在数据积累到一定量时再发送出去,至于要积累多少数据才能发送,需要根据下面几个要素来判断。

<1> 每个网络包能容纳的数据长度

MTU 表示一个网络包的最大长度(包含头部),除去头部之后,一个网络包所能容纳的 TCP 数据的最大长度为 MSS,当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题了。

<2> 时间

当应用程序发送数据的频率不高的时候,如 果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长而造成发送延迟。

判断要素就是这两个,但它们其实是互相矛盾的。如果长度优先,那 么网络的效率会提高,但可能会因为等待填满缓冲区而产生延迟;相反地, 如果时间优先,那么延迟时间会变少,但又会降低网络的效率。在实际进行发送操作时中需要综合这两个因素以达到平衡。

像浏览器这种会话型的应用程序在向服务器发送数据时,等待填满缓冲区导致延迟会产生很大影响,因此一般会使用直接发送的选项

(2)对较大的数据包进行拆分

(3)使用 ACK 号确认网络包已收到

在实际的通信中, 序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,这是因为如果序号都从 1 开始,通信过程就会非常容易预测,有人会利用这一点来发动攻击。

(4)根据网络包平均往返时间调整 ACK 号等待时间

TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间。

(5)使用滑动窗口有效管理 ACK 号

所谓滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包。这样一来,等待 ACK 号的这段时间就被有效利用起来了。

但有可能会出现发送包的频率超过接收方处理能力的情况。如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出。

所以接收方需要告诉发送方自己最多能接收多少数据, 然后发送方根据这个值对数据发送操作进行控制。

(6)ACK 与窗口的合并

在等待发送 ACK 号的时候正好需要更新窗口,这时就可以把 ACK 号和窗口更新放在一个包里发送,从而减少包的数量。

(7)接收 HTTP 响应消息

浏览器在委托协议栈发送请求消息之后,会调用 read 程序来获取响应消息。

协议栈会检查收到的数据块和 TCP 头部的内容,判断是否有数据丢失,如果没有问题则返回 ACK 号。然后,协议栈将数据块暂存到接收缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序。具体来说,协议栈会将接收到的数据复制到应用程序指定的内存地址中,然后将控制流程交回应用程序。

6、从服务器断开并删除套接字

协议栈在设计上允许任何一方先发起断开过程。

这里我们以服务器一方发起断开过程为例来进行讲解。首先,服务器一方的应用程序会调用 Socket 库的 close 程序。然后,服务器的协议栈会生成包含断开信息 的 TCP 头部,具体来说就是将控制位中的 FIN 比特设为 1。接下来,协议栈会委托 IP 模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息

断开连接交互过程如下图所示:

和服务器的通信结束之后,用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。等待这段时间是为了防止误操作。

7、TCP的整体流程

8、IP 与以太网的包收发操作

TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块(TCP 模块的数据块前面加上 TCP 头部,然后传递给 IP 模块)将数据封装成包发送给通信对象。收到委托后,IP 模块会将包的内容当作一整块数据,在前面加上包含控制信息的头部。IP 模块会添加 IP 头部和 MAC 头部这两种头部。其中,

  • IP 头部中包含 IP 协议规定的、根据 IP 地址将包发往目的地所需的控制信息。
  • MAC 头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息。

这两个头部分别具有不同的作用。首先,发送方将包的目的地址,也就是要访问的服务器的 IP 地址写入 IP 头部中。这样一来,我们就知道这个包应该发往哪里,IP 协议就可以根据这一地址查找包的传输方向,从而找到下一个路由器的位置。接下来,IP 协议会委托以太网协议将包传输过去。这时,IP 协议会查找下一个路由器的以太网地址(MAC 地址),并将这个地址写入 MAC 头部中(舍弃包原来的 MAC 头部)。这样一来,以太网协议就知道要将这个包发到哪一个路由器上了。

(1)包的结构

TCP / IP 包结构如下图所示:

(2)网络转发设备

发送方的网络设备创建包后,接下来,包会被发往最近的网络转发设备。转发设备会根据头部中的信息判断接下来应该发往哪里。这个过程需要用到一张表,这张表里面记录了每一个地址对应的发送方向,也就是按照头部里记录的目的地址在表里进行查询,并根据查到的信息判断接下来应该发往哪个方向。

接下来,包在向目的地移动的过程中,又会到达下一个转发设备,然后又会按照同样的方式被发往下一个转发设备。就这样,经过多个转发设备的接力之后,包最终就会到达接收方的网络设备。

转发设备有两种,路由器和集线器。

路由器:路由器根据目标地址(IP 地址)判断下一个路由器的位置。

集线器:集线器在子网中(MAC 地址)将网络包传输到下一个路由。

路由器中有一张 IP 协议的表(路由表),包到达路由器后可根据这张表以及 IP 头部中记录的目的地址信息查出接下来应该发往哪个路由器。

集线器里有一张表(用于以太网协议的表),可根据以太网头部中记录的目的地信息查出相应的传输方向。

(3)IP 头部格式

IP 地址实际上并不是分配给计算机的,而是分配给网卡的,因此当计算机上存在多块网卡时,每一块网卡都会有自己的 IP 地址。IP 头部 20 个字节。

如果计算机有多块网卡,该如何判断应该把包交给哪块网卡呢?根据路由表来判断。

(3)MAC 头部

MAC 地址是在网卡生产时写入 ROM 里的,只要将这个值读取出来写入 MAC 头部就可以了。

(4)通过 ARP 查询目标路由器的 MAC 地址

(5)集线器

集线器分为两种,中继式集线器和交换式集线器(交换机)。前面提到的集线器都是中继式集线器。

中继式集线器:信号到达整个子网,多台设备同时发送信号会造成碰撞。

交换式集线器:信号到达指定的设备,多台设备同时发送信号不会造成碰撞。

(6)网卡

网卡的 ROM 中保存着全世界唯一的 MAC 地址,这是在生产网卡时写入的。网卡中保存的 MAC 地址会由网卡驱动程序读取并分配给 MAC 模块。

网卡如下图所示:

网卡驱动从 IP 模块获取包之后,会将其复制到网卡内的缓冲区中,然后向 MAC 模块发送包的命令。

MAC 模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。然后从报头开始将数字信息按每个比特转换成电信号(通用信号),网卡送出去的包如下图所示:

PHY(MAU)模块会将信号转换为可在网线上传输的格式, 并通过网线发送出去。

网卡接收返回包通过中断机制。和普通硬件模块收包原理一样(例如串口接收数据)。中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作。

网卡在安装的时候就在硬件中设置了中断号,在中断处理程序中则将硬件的中断号和相应的驱动程序绑定。现在的硬件设备都遵循即插即用规范自动设置中断号。

我的感想:

计算机网络的通信流程是一个很通用的网络模型,研究过蓝牙Mesh网络的小伙伴会发现,计算机网络和蓝牙Mesh网络的层次结构、通信流程都非常相似。

做过嵌入式开发的小伙伴可能也会发现,虽然计算机的硬件很复杂,但像BIOS启动、网卡驱动、数据的接收方式等等和简单的嵌入式设备的启动方式,硬件驱动,中断接收数据等等也是大同小异。

9、UDP

UDP 遇到错误或者丢包也一概不管,UDP 只负责单纯地发送包而已,并不像 TCP 一样会对包的送达状态进行监控。

UDP 头部如下图所示,共 8 个字节:

适用场景:

  • 操作基本上都可以在一个包的大小范围内解决,例如 DNS 查询。
  • 发送音频和视频数据时。

参考文献

[1] 网络是怎样连接的 日·户根勤/著,周自恒/译

注:转载请注明出处

微信公众号

喜欢我的还可以关注我的微信公众号:傻猫爱学习,里面有你意想不到的惊喜哦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值