《网络是怎样连接的》书摘与笔记(一)

网络是怎样连接的

Posted by Haiming on April 3, 2019

今天开始看《网络是怎样连接的》,有所感想的地方,记录在此,以备复习。

1. 浏览器生成消息——探索浏览器内部

热身问答

  1. 浏览器等网络应用程序并不具备网络控制功能,而是委托操作系统来控制。

本章内容

  1. 生成HTTP请求信息
  2. 向DNS服务器查询Web服务器的IP地址
  3. DNS服务器的接力查询
  4. 委托协议栈(操作系统之中)发送消息

1.1 生成HTTP请求消息

1.1.1 探索之旅从输入网址开始

1.1.1.1 什么是URL?

URL:Uniform Resource Locator,统一资源定位符。

URL的开头文字表示浏览器应该使用的访问方法,不同的访问方法有不同种类的格式规定。开头文字也标志着不同的协议类型,如向Web服务器发起请求时候要使用HTTP协议,向FTP服务器发送请求时候要使用FTP协议。

1.1.2 浏览器要先解析URL

浏览器先解析URL,将一串URL按照不同协议的不同格式分解之后取得对应的资源,比如HTTP协议最后要取得html资源。

1.1.3 省略文件名的情况

在省略文件名的情况之下,服务器会返回默认的文件,通常是index.html或者default.htm之类的。但是如果是一串字符表示的,例如说http://www.lab.glasscom.com/whatisthis 这个网址,如果Web 服务器上存在名为whatisthis 的文件,则将whatisthis 作为文件名来处理;如果存在名为whatisthis 的目录,则将whatisthis 作为目录名来处理。由于文件名的唯一性,磁盘上面不可能又有名字为whatisthis的文件又有名为whatisthis的文件夹,因此不会产生歧义。

1.1.4 HTTP的基本思路

HTTP的基本思路是:

客户端向服务器发送请求消息,内容为方法+URI(Uniform Resource Identifier,统一资源标识符) ,其所包含的东西是“对什么”和”进行什么样的操作“两部分。其中”对什么“是使用URI来标记,我们也可以使用URL作为一种URI。

在收到消息之后,Web服务器会对内容进行解析,通过方法+URI来判断”对什么“”进行什么操作“。根据要求操作之后返回一个状态码,用来表示此次执行成功或者失误,具体因素是什么等等。状态码之后就是头字段(可有可无)与网页数据。将数据发送到浏览器及逆行解析之后,HTTP的所有工作就完成了。

HTTP/1.1相比于HTTP/1.0增加了对于代理传输的考虑。

下面列举出HTTP/1.1的主要方法:

  • GET:获取URI指定的信息
  • POST: 从客户端向服务器发送数据
  • HEAD: 类似于GET,但是只返回HTTP的信息头。用于获得文件最后更新时间等属性信息
  • OPTIONS: 用于通知或查询通信选项
  • PUT: 替换URI指定的服务器上的文件,如果没有的话,就新建一个指定文件
  • DELETE: 删除URI指定的服务器上的文件
  • TRACE: 将服务器收到的请求行和头部直接返回给客户端,用于在使用代理的情况下检查改写请求的情况
  • CONNECT: 使用代理传输加密消息时使用的方法

1.1.5 生成HTTP请求信息

请求信息和服务器的返回信息都可以带有头字段,其不是必须的,但是很多情况下需要对其做一些其他信息的说明,因此大部分都有头字段。

1.1.6 发送请求后会收到响应

在响应消息之中,第一行的内容是状态码和响应短语,状态码只是一个数字,例如404,200等等,相应的,响应短语是一段字符,用来告知执行的结果。

HTTP 状态码概要:状态码的第一位数字表示状态类型,第二、三位数字表示具体的情况。下表列举了第一位数字的含义。

1xx: 告知请求的处理进度和情况,代表请求已经接受,需要继续处理。其响应全部为信息性的。且在HTTP/1.0之中不存在1xx的响应。 2xx: 成功 3xx: 表示需要进一步操作,重定向 4xx: 客户端错误 5xx: 服务器错误

在网页之中仅有文字时,上述处理结束整个过程就结束了。但是如果是含有图片的情况,那么还要在屏幕上留出显示图片的空间,之后再次访问Web服务器去获取相应图片,并且嵌入到预留的空间之中。

但是由于每条消息之中仅仅可以写一个URI,因此几张图片(几个资源)就要相对应的对于服务器发送几个请求来获取相应资源。服务器本身并不知道这些资源是一个页面需要的,其仅仅是对于每一条URI作响应,之后返回对应的数据。资源的嵌入与拼接等等都是在浏览器之中实现的。

1.2 向DNS服务器查询Web服务器的IP地址

1.2.1 IP地址的基本知识

发送HTTP报文是委托操作系统来进行的,但是在委托操作系统发送消息的时候,必须提供IP地址而非其域名。

IP地址之中同时包含了网络号和主机号,在IP地址的规则之中,二者相连起来一共是32bit,但是仅凭借一个IP地址是无法知道哪部分是网络号,哪部分是主机号的,因此需要额外的信息来做决策,这个额外信息就是子网掩码

另外,主机号部分的bit全部为0指的是整个子网,而主机号的部分bit全部为1是其子网的广播地址。

1.2.2 域名和IP地址并用的理由

若只是用IP地址来通信的话对人类的记忆不友好,但是如果只使用域名来标记各个服务器的话,首先域名的长度要长很多,对于路由器来说增加了负担,另外一方面,域名的长度不固定,处理不固定长度的信息比处理固定长度的信息要复杂很多,这也是造成效率低下的原因。因此综合二者,使用DNS作为域名解析是一个权衡的方案。

1.2.3 Socket库提供查询IP地址的功能

DNS:Domain Name System, 域名服务系统。将服务器名称和IP 地址进行关联是DNS 最常见的用法,但DNS 的功能并不仅限于此,它还可以将邮件地址和邮件服务器进行关联,以及为各种信息关联相应的名称。

对于DNS服务器,我们的计算机上一定有相应的DNS客户端,而相当于DNS客户端的部分就叫做DNS解析器。通过DNS查询IP地址的操作称为域名解析。

解析器存在于操作系统的Socket库之中,Socket库是一堆通用组件的组合,其他的应用程序都要使用其中的组件。

库的好处在于:

  1. 使用现成的组件节省工作量
  2. 对于多个使用相同组件的可以实现程序的标准化

Socket库之中包含的程序组件可以让其他的应用程序调用操作系统的网络功能, 而解析器就是其中的一种程序组件。

1.2.4 通过解析器向DNS服务器发出查询

在程序之中直接使用解析器的程序名称和Web服务器的域名即可。根据域名查找IP地址时,浏览器就会使用Socket之中的解析器。

1.2.5 解析器的内部原理

应用程序——>Socket——>操作系统内部的协议栈(用于发送UDP消息)——>网卡——>DNS服务器

DNS 是同时占用 UDP 和 TCP 的 53 端口传输数据的。但是大部分情况下是使用UDP来进行传输:

为什么DNS使用UDP来传输?

  1. 从效率角度来讲: TCP需要三次握手四次挥手,UDP不需要,相对而言传输速度会快很多
  2. 从数据一致性角度来讲:一般DNS的包都不大,一个UDP的package足够传输,因此不需要做分片,丢包的话直接重传即可,没有使用TCP的必要。

当然,也有使用TCP情况传输DNS,但是基本上看不到: 当解析器发出一个request后,返回的response中的tc删节标志比特位被置1时,说明反馈报文因为超长而有删节。这是因为UDP的报文最大长度为512字节。解析器发现后,将使用TCP重发request,TCP允许报文长度超过512字节。既然TCP能将data stream分成多个segment,它就能用更多的segment来传送任意长度的数据。

1.3 全世界DNS服务器的大接力

1.3.1 DNS服务器的基本工作

来自客户端的查询信息有:

  1. 域名:服务器,邮件服务器的名称
  2. Class:设计DNS方案时候,DNS在互联网之外的其他网络的应用也被考虑到,因此Class用来保存网络的信息,但是后来之后互联网,因此这个字段永远是IN
  3. 记录类型:表示域名对应何种类型的记录。

DNS服务器在查找三个方面的查询信息之后,返回IP地址给客户端。

1.3.2 域名的层次结构

域名从右到左其优先级逐渐降低。

由于地址过多,不可能只有一台DNS服务器,在这种情况之下, 就有了根据域的划分来放置IP地址的解决方案。每个域都作为一个整体信息存放在DNS服务器之中。

1.3.3 寻找相应的DNS服务器并获取IP地址

由域名的组织关系可得,比较有效率的方式是将负责管理下级域的DNS服务器注册到其负责的上级DNS服务器之中,然后上级的DNS服务器再注册到其上级的DNS服务器中,以此类推

其实在例如.com或者.jp等顶级域之上还有一级域,称为根域,根域之中保存着所有顶级域的DNS 服务器信息。同时还需要将根域的DNS服务器信息保存在所有的DNS服务器之中,这样就可以保证可以通过任一DNS服务器找到根域服务器。

DNS有缓存机制,DNS服务器可以将最近查找过的域名和IP地址的对应关系记录下来,可以用来实现快速响应,这样一来就有信息可能不正确的问题。解决方案:

  1. 所有的注册信息都有一个有效期
  2. 对查询进行响应的时候,DNS服务器也会告知客户端这个结果来自缓存还是来自负责该域名的DNS服务器。

1.4 委托协议栈发送消息

1.4.1 数据收发操作概览

向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用Socket库之中的程序组件。简而言之,程序通过Socket库来使用协议栈。Socket库只是充当了一个桥梁的角色。

收发数据的操作主要分为四个阶段:

  1. 创建套接字
  2. 客户端将管道连接到服务器端的套接字上
  3. 收发数据
  4. 断开管道并删除套接字

1.4.2 创建套接字阶段

不同的应用程序要使用不同的套接字,应用程序是通过”描述符“这一类似号码牌的东西来识别套接字的。

1.4.3 连接阶段:把管道接上去

在调用connect时,要指定描述符服务器IP端口号 三个参数

1.4.4 通信阶段:传递消息

1.4.5 断开阶段:收发数据结束

Web的HTTP协议规定,当Web服务器发送完响应消息之后,应该主动执行断开操作。断开操作传达到客户端之后,客户端的套接字也会进入断开阶段。在HTTp/1.0之中,没有流水线的概念,因此每个资源,例如图片,都要经历一次完整的上述过程,造成了资源的极大浪费。在HTTP/1.1之中,引入了流水线的概念,避免了重复连接和断开的操作。

小测验

  1. 向DNS服务器发送请求消息的程序叫做解析器

2.用电信号传输TCP/IP数据

本章几乎全部面向TCP,几乎不涉及到UDP。除非特殊强调,否则默认对于TCP过程进行解析。

2.1 创建套接字

2.1.1 协议栈的内部结构

2.1.2 套接字的实体就是通信控制信息

套接字就是一份操作指南,协议栈需要根据这些操作指南来进行操作。 其中信息主要包括了:协议类型:TCP/UDP, 本地地址和端口号,通信地址和端口号,状态,PID(进程标识符)

2.1.3 调用socket时候的操作

2.2 连接服务器

2.2.1 连接是什么意思

在连接服务器的过程中要分配 缓冲区 ,用于数据的临时存放

2.2.2 负责保存控制信息的头部

控制信息分为两类:

  1. 客户端和服务器相互联络时交换的控制信息:除了连接时候需要,在整个通信过程之中都需要。这些信息在每次发送报文的时候都会附在最前面。一般记作TCP头部,以太网头部,IP头部等等。
  2. 保存在套接字中,用来操作协议栈的信息。应用程序传递来的信息和通信对象接收到的信息都会保存在这里,除此之外, 收发数据操作的执行状态等信息也会保存在这里。
    • 不同系统的协议栈实现方式也不同,因此不能要求socket之中的控制信息完全相同。

2.2.3 连接操作的实际过程

连接操作的第一步是在TCP模块创建表示连接的头部。

2.3 收发数据

2.3.1将HTTP请求消息交给协议栈

协议栈并不是一收到数据就进行发送,因为从上层应用程序发送过来的消息不是可控的,因此通常积累到一定数量再发送出去,避免给网络造成太大负担。下面是考虑因素:

  1. MTU: 一个网络包的最大长度,以太网是1500字节
  2. MSS:除去头部以后,一个网络包所能容纳的TCP数据的最大长度
  3. 时间:当快要达到最大等待时间的时候不论数据包的大小都要发出

2.3.2 对较大的数据进行拆分

对于较大的数据,要将其拆分成几个TCP的package。对应的要在TCP的头部做标记变化

2.3.3 使用ACK号确认网络包已收到

对于TCP而言,其被称为面向连接的传输,那么就要有保障。保障的方式是使用ACK位和SYN。在每次传输的过程中比对SYN是否为上次接收到的ACK+1,如果是的话就说明中间没有遗漏,不是的话就说明中间已经遗漏了部分包,需要重传。

序号字段是随机生成的,目的就是为了防止恶性攻击。

在得到对方的ACK确认之前,所有的包都会保存在缓冲区之中,以便万一中间过程丢失,可以迅速重传。

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

TCP协议会根据ACK的返回时间来调整其等待时间。

2.3.5 使用窗口有效管理ACK号

TCP使用滑动窗口来获得更高的效率。所谓窗口,就是在还没有接收到对方的ACK时候就已经开始继续传输数据包了。

这种处理方式有一定的问题,当滑动窗口的值很大时,接收方的数据处理能力不够,那么就会出现其缓冲区溢出的情况,所以在第一次通信的时候,接收方要告诉发送方自己最多可以接受多少数据,这样发送方可以根据这个值进行调整,这也就是”滑动窗口“

具体过程是:接收方每次都会在TCP头部的窗口字段将自己能接受的数据量告诉对方,这样发送方不会发送过多的数据,也就不会出现超出接收方的处理能力的情况了。

能够接收到的最大数量称为窗口大小,一般和接收方的缓冲区大小相一致。

2.3.6 ACK和窗口的合并

按理可知,如果需要传输确认,那么针对每一次确认都要发送一次ACK和窗口更新,会导致网络的效率下降,因此要在有可能的情况下合并包。下面是合并包的几种情况:

  1. 在等待ACK的时候需要更新窗口,那么就将两个报文放在一个包里面发送
  2. 在连续发送ACK的情况下,只发送最后一个报文的ACK,因为ACK指的是最后收到的报文,只要用最后一个作为定位即可。
  3. 在连续发送窗口更新的时候也可以减少包的数量,这种情况也可以省略中间过程,只要发送最后的结果就好了

2.3.7 接收HTTP响应信息

所有的HTTP请求都有响应信息。

由于接受响应信息需要一定的时间,所以在发送完请求之后相应的协议栈会暂时挂起,等待信息进入之后再重新处理返回信息。

过程如下:

协议栈检查数据块和TCP头部,确认报文没有问题—>协议栈将数据块暂存到接收缓冲区之中,并且将数据块按顺序拼接起来—> 将数据交给应用程序,之后择机发送窗口更新

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

2.4.1 数据发送完毕后断开连接

首先对于 HTTP/1.0 和 HTTP/1.1 而言,断开的方式并不相同:

  1. HTTP/1.0 而言, 在服务器返回response之后直接开启断开进程。但是也有情况是客户端发完就结束了,不再管之后的事情,那么也可以由客户端发起断开进程。总之还是要看具体情况。
  2. HTTP/1.1 而言,在服务器response之后,客户端还可以继续下一个请求,即pipeline模式,如果没有请求要发送,那么由客户端开启断开进程。

但是无论哪种情况,都是先结束的一方开启断开进程。

断开进程的具体操作都是将报文的某些位置的值改变而言的,具体如下:

如果是服务器端开始close程序,那么首先会将报文的FIN位置置1,客户端收到之后将自己的套接字标记为断开操作状态,然后为了告知服务器收到FIN为1的包,客户端会返回一个ACK。

2.4.2 删除套接字

删除套接字的操作是在通信结束之后发起的,但是并不是在通信结束之后就立刻将对应的套接字删除掉,因为在传输过程之中很有可能出现纰漏,例如:

在最后一次客户端返回对于服务器的ACK号之后,如果客户端认为通信已经结束(没我事了哈哈哈),直接将对应的套接字一并删除,但是最后的ACK报文丢失了,那么服务器会在超时之后让客户端重传报文。这种情况下:

  1. 如果已结束的被删除的套接字的端口号还未被重新分配,那么会直接出现错误。
  2. 如果已结束的被删除的套接字的端口号已经被重新分配,那么当服务器重新发送FIN(超时重传)的时候,这个FIN会直接将刚刚开启的端口号断开。

2.4.3 数据收发操作小结

  1. Server 创建套接字等待 Client 连接
  2. Client 向Server 发起连接操作: 客户端生成 SYN=1 的TCP包发送给服务器,其头部还有初始序号与 Server 向 Client 发送数据包时候的窗口大小。
  3. Server 收到 package 之后,服务器返回一个 SYN=1 的 TCP 包, 其包含序号和 Server 的窗口大小,之外还有确认收到步骤2产生的包的 ACK 号
  4. 在步骤3产生的包到达 Client 之后,Client 会向 Server 返回一个包含 ACK 的TCP包, 到这里就进入了数据收发阶段。

2.5 IP和以太网的包收发操作

2.5.2 包的基本知识

包是由头部和数据两部分组成的,头部包含目标地址等控制信息,数据则是要传递给对方的数据。

首先,发送方的网络设备会负责创建包, 在生成具有正确控制信息的头部,并且加入要传输的数据之后,包会被发往最近的网络设备,而网络设备之中有一张记录着哪个包该发往哪的路由表。在查找路由表之后,其会发送至对应的设备。

在真实的网络结构之中有两种不同的转发设备:路由器和集线器:

  1. 路由器根据目标地址判断下一个路由器的位置,是按照IP规则传输包的设备
  2. 集线器在子网之中将网络包传输到下一个路由,是按照以太网规则传输包的设备

IP协议根据目标地址判断下一个IP转发设备的位置,子网之中的以太网协议将包传输到下一个转发设备。

TCP/IP 包包含两个头部,一个是MAC头部,一个是IP头部。其中IP地址始终是目的的IP地址,不会改变,但是MAC头部在每次包的转换的时候都会改成下一跳的MAC地址。更准确的说,每一次转发之后的MAC地址都会被舍弃,换上新的MAC头部。

2.5.2 包收发操作概览

IP模块负责添加两个头部:

  1. MAC头部: 以太网用的头部,包含MAC地址
  2. IP头部,IP用的头部,包含IP地址

在添加完头部之后,封装好的包会被交给网卡发送,如果有的话,接受过程刚好和发送过程相反。

2.5.3 生成包含接收方IP地址的IP头部

对于发送方而言,IP地址的信息流向是:

应用程序—>TCP模块—>IP模块

如果IP地址有错,那么错误只会发生在发送第一个SYN包的情况,在TCP握手之后就可以肯定两边的连接是有保证的,即IP一定没错。

2.5.4 生成以太网用的MAC头部

MAC头部的开头是接收方和发送方的MAC地址,还有以太类型三部分。对于IP协议而言,以太类型为0800(十六进制)。发送方的MAC地址可以直接查找得到,但是接收方的不可以直接拿到,那么就需要执行从IP地址查询MAC地址的操作。

2.5.5 通过ARP查询目标路由器的MAC地址

ARP:Address Resolution Protocol,地址解析协议。

利用ARP查询MAC地址的操作如下: 对所有设备广播:已知这个IP地址,谁是这个设备? 然后对应的设备返回:我是这个设备,我的MAC地址是xxx。其他设备忽略此次请求。

一般情况下会将查询结果放到ARP缓存区,这样可以减小整个网络的压力。但是只用ARP缓存的话,当IP地址变化的时候,ARP缓存之中的内容就会不同,所以在一段时间之后ARP会被清空,大家都重新获取IP地址对应的MAC地址。

2.5.6 以太网的基本知识

早期的以太网就是将所有计算机连接在一起的一个网络,所有计算机都能收到所有报文,不是自己的就丢掉,所以要在报文头部增加接收方和发送方的地址。另外不同报文还可能会发生碰撞,这种情况下有单独的碰撞处理算法(2n回退)。

2.5.7 将IP包转换成电或者光信号发送出去

以太网部分的碰撞检测,重发等等操作是由网卡内部的程序决定的。网卡之中也有缓冲区,其用来临时保存要收发的包。

2.5.8 给网络包再加三个控制数据

2.5.9 向集线器发送网络包

2.5.10 接受返回包

2.5.11 将服务器的响应包从IP传递给TCP

如果从网卡已经将对应的自己的MAC地址的包接收上来,那么会首先检查这个包的IP地址和自己的IP地址是否相同,如果不同,直接调用ICMP协议告知对方此处有错误。

主要的ICMP消息有:

  • 没有到达目的地就被丢弃,例如目标IP在路由表中不存在,目标端口不存在对应的套接字,需要分片但是分片被禁用
  • 发送的包超出路由器的转发能力。但是并不是一定会发送此消息,当路由器性能不足到一定程度时,甚至连这条消息都不发送,而选择直接丢弃所有超出能力的包。若发送方收到这条消息,就必须降低发送速率。
  • IP头部有错误
  • 超过了IP头部的TTL保活时间

2.6 UDP协议的收发操作

2.6.1 不需要重发的数据用UDP发送更高效

2.6.2 控制用的短数据

一般而言,像控制用的短数据这种,可以使用UDP发送。因为这种数据,哪怕丢包,也都只是需要重传一次而已, 不需要做其他操作。

对于控制的数据,这样并不会发生任何的问题,因为一般而言对于控制的数据,都会有对应的状态回复,没有的话说明丢包,直接重发即可。

UDP头部:

  • 发送方端口号:16 bit
  • 接收方端口号: 16 bit
  • 数据长度: 16 bit
  • 校验和: 16 bit

2.6.3 音频和视频数据

音频和视频由于具有较强的容错率(画面的几个像素错了并不会导致太大问题)和具有较强的实时性(重传也没必要,已经过去了),所以一般也使用UDP来进行传输。