计算机网络书籍--《网络是怎样连接的》阅读笔记

第一章 浏览器生成信息

1.1 生成HTTP请求信息

1.1.1 URL

Uniform Resource Locator, 统一资源定位符。就是网址。

不同的URL能够用来判断使用哪种功能来访问相应的数据,比如访问Web服务器就要用”http:”,而访问FTP服务器用”ftp:”。

  • FTP:FileTransferProtocol,文件传送协议。这是一种在上传、下载文件时使用的协议。使用FTP协议来传送文件的程序也被叫作FTP。

1.1.2 解析URL

URL的组成
主要内容包括:协议,要访问的服务器和访问什么文件。

1.1.3 HTTP的基本思路

主要由两部分组成:

  1. 请求信息
  2. 响应信息

每条请求信息中只能写一个URI,所以每次只能获取一个文件,如果需要获取多个文件,必须对每个文件单独发送一条请求。

1.1.3.1 请求信息

由两部分组成:

  1. 对什么进行请求
  2. 进行怎样的操作

“对什么请求”部分称为URI,一般来说,URI的内容是一个存放网页数据的文件名或者是一个CGI程序的文件名,是访问目标的统称。格式为 /<目录名>/…/<文件名> 。因为路径名一般都包含在URL中,因此只要从URL提取出来写上去就行。

  • URI: Uniform Resource Identifier,统一资源标识符。

    CGI程序:对Web服务器程序调用其他程序的规则所做的定义就是CGI。

“进行怎样的操作”的部分称为方法,方法表示让Web服务器完成怎样的操作,比如读取URI表示的数据、将客户端输入的数据发送给URI表示的程序等。下面是一些HTTP的主要方法。
请求方法

常用的方法是GET和POST,GET多用于访问Web服务器获取网页数据时;POST多用于在表单中填写数据并将其发送给Web服务器时。因为GET方法能够发送的数据只有几百个字节,如果表单的数据超过这一长度,则必须使用POST方法来请求。

除该两种部分,还有一些还有一些用来表示附加信息的头字段。客户端向 Web 服务器发送数据时,会先发送头字段,然后再发送数据。

请求信息的格式

  1. 请求行
  2. 信息头
  3. 信息体

请求行可以大致了解请求的内容,包含方法、URI和HTTP版本,大致可以清楚向谁(URI)进行怎样的操作(方法)。

信息头是对请求内容进行更为详细的补充。

信息体就是想向服务端发送的信息,比如账号密码,一般和POST请求匹配使用,将表单中的信息发送给服务端。

1.1.3.2 响应信息

和请求信息大致相同,只不过不是请求行,而是状态行,内容为HTTP版本、状态码和响应短语。状态码为一个数字,用来告知程序执行的结果;响应短语则是一段文字,用来向人们告知执行的结果。
状态码

返回响应信息后,就可以得到请求信息对应的结果,如果无错误,则正常显示在浏览器上。为文字时,则该对信息全部处理完毕,如果还包含图片等资源,则还有下文。

当有图片时,网页中相应位置嵌入表示图片文件的标签(<img src = “image1.jpg>)的控制信息。浏览器在读到该标签时,会在屏幕上留出来显示图片的空间,然后再次访问Web服务器,按照按照标签中指定的文件名向 Web 服务器请求获取相应的图片并显示在预留的空间中。这个步骤和获取网页文件时一样,只要在 URI 部分写上图片的文件名并生成和发送请求消息就可以了。

且因为每次请求只能获取一个文件,所以遇到图片时还要对存在的图片进行的请求。
对网址中有图片时的请求

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

1.2.1 IP地址的基本信息

在生成HTTP信息之后,下一个步骤就是根据域名查询IP地址。

先了解TCP/IP的基本思路:由一些小的子网,通过路由器连接起来组成的一个大的网络。子网可以理解为通过集线器连接起来的几台计算机,看做一个单位,称为子网。将子网通过路由器连接起来,就形成了一个网络。

在网络中,所有设备都会被分配一个地址。这个地址就是IP地址,由“号”和“室”组成,“号”是分配给整个子网的,称为网络号,“室”是分配给子网中的计算机的,称为主机号。通过IP地址就可以判断出访问对象服务器的位置,从而将信息发送到服务器。
信息发送的简单步骤:发送者发出的信息首先经过子网中的集线器,发送到距离发送者最近的路由器上。接下来,路由器会根据信息的目的地判断下一个路由器的位置,然后将信息发送到下一个路由器,即信息再经子网内的集线器被转发到下一个路由器。不断重复该过程,最终信息就被传送到了目的地。

IP地址的表示方法:

IP地址的表示方式

( a ) 为最基础的IP主体表示,但该种表示不知道哪部分是网络号,哪部分是主机号,所以需要加上一些修饰信息来完善。

( b ) 采用和IP地址主体相同的格式来表示子网掩码的方法,子网掩码中1的部分对应的是IP地址中的网络号,0的部分是主机号,如下图。是一种二进制上对应的关系

格式

( c ) 标出网络号的比特位数,剩下的就是主机号。

( d ) 和 ( e ) 就是当主机号全为0时和主机号全为1时的情况。当全为0时,表示整个子网;当全为1时,表示向子网上的全部设备发送包。

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

省流:只用域名则数据量太大,只用IP没域名好记,所以域名和IP对应使用最好。

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

查询IP地址,就是询问最近的DNS服务器,该域名的IP地址是什么,DNS服务器接收该信息后返回对应的IP地址(如果域名对应的IP存在)。

通过DNS查询IP地址的操作称为域名解析,负责域名解析的就叫解析器。解析器是一段程序,在Socket库中,Socket库是用于调用网络功能的程序组件集合。

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

如图

1.2.5 解析器的内部原理

解析器原理

  • 控制流程转移:由于调用了其他程序,原本运行的程序进入暂停状态,而被调用的程序开始运行。

图中的丝滑小连招说白了就是应用程序调Socket库中的解析器,解析器调操作系统中的协议栈,协议栈调网卡,最终访问DNS服务器的就是网卡,收到DNS返回的IP后就反方向再走到最上层的应用程序即可。在这之中是一系列的控制流程转移。

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

1.3.1 DNS服务器的基本工作

首先,DNS服务器要接收来自客户端的查询信息,该信息包含三部分

  1. 域名:服务器、邮件服务器(邮件地址中@后面的部分)的名称
  2. Class:不变,为IN,原本是为互联网外的网络设计的
  3. 记录类型:表示域名对应何种类型的记录。比如类型为A时,表示域名对应的是IP地址;当类型为MX时,表示域名对应的是邮件服务器。对于不同的记录类型,服务器向客户端返回的信息也会不同。

查询信息

然后,DNS服务器会从已有记录中查找域名、Class和记录类型全部匹配的记录。

综上所述,DNS服务器的基本工作就是根据需要查询的域名和记录类型查找相关的记录,并向客户端返回响应信息。

DNS服务器会从域名与IP地址的对照表中查找相应的记录,并返回IP地址。

1.3.2 域名的结构层次

将信息分布保存在多台DNS服务器中,这些DNS服务器相互接力配合,从而查找出要查询的信息。

DNS中的域名是用句点来分隔的,比如www.lab.glasscom.com,这里的句点就代表了不同层次之间的界限,相当于上层与下层,越靠右层次越高, com → glasscom → lab → www

一个域的信息是作为一个整体存放到DNS服务器中的,不能将一个域拆开来存放在多台DNS服务器中。

1.3.3 寻找响应的DNS服务器并获取IP

用域名的层次结构,下级域的DNS服务器的IP地址注册到上级域的DNS服务器中。这样,我们就可以通过上级DNS服务器查找到下级服务器的IP地址,也就可以向下级DNS服务器发送请求了。

在com、jp这样的顶层域之上,还有一个共同的域,叫根域,一般在顶层域结尾有个句点,就代表根域,比如:www.lab.glasscom.com. 。所以通过该根域,我们就可以顺藤摸瓜,找到任意一个域的DNS服务器。

不过我们一般无法直接访问到根域,而是先访问到最近的DNS服务器,所以要想能够顺藤摸瓜,则每个DNS服务器中都要有根域的DNS服务器信息。

查询

查询

1.3.4 通过缓存加快DNS服务器的响应

现实中,上级域和下级域可能共享同一台DNS服务器,所以访问上级DNS服务器时就可以向下跳过一级服务器,直接返回再下一级的DNS服务器的相关信息。

DNS服务器的缓冲功能,能够存储之前查询过的域名的IP地址,这样在查找到之前查找过的域名时,就可以直接给出响应信息,不需要再次查找。且在要查询的域名不存在时,也会存储“不存在”这一响应结果。

1.4 委托协议栈发送信息

1.4.1 数据收发操作概览

知道了IP地址后,就可以委托操作系统内部的协议栈向这个目标IP地址,也就是我们要访问的Web服务器发送信息。要发送给 Web 服务器的 HTTP 消息是一种数字信息(digital data),因此也可以说是委托协议栈来发送数字信息。收发数字信息这一操作不仅限于浏览器,对于各种使用网络的应用程序来说都是共通的。因此,这一操作的过程也不仅适用于 Web,而是适用于任何网络应用程序。

发送数据不像向DNS服务器查询IP地址那样,只用Socket库中的一个程序组件,而是一系列程序组件相结合来实现的。向操作系统内部的协议栈发出委托时,需要按照指定的顺序来调用Socket库中的程序组件。

下面是收发数据操作的整体思路。
粗略来讲,就是在收发数据的两台计算机之间连接了一条数据通道,数据沿着这条通道流动,最终到达目的地。

收发概览

该管道并非一开始就存在,而是在收发数据之前搭建的。建立管道的关键是管道两端的数据出入口,这些出入口称为套接字。我们需要创造套接字,再搭建管道。过程为:服务器一方先创建套接字,然后等待客户端向该套接字连接管道。当客户端创建套接字后,连接到服务器的套接字上,就形成了管道。此时就可以通过管道来收发数据了。管道在连接时是由客户端发起的,而断开时双方都可以发起。

总体收发数据可以分为下面四个步骤:

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

这四个操作都是委托操作系统中的协议栈来执行的,浏览器只是发出委托,不做事。
委托内容 → Socket库中的程序组件 → 操作系统中的协议栈。
如果有了解一点操作系统,就知道操作系统是直接和硬件接触的最底层的部分,向应用程序暴露接口,应用程序通过接口来间接的对硬件进行操作,实际还是操作系统操作。

1.4.2 创建套接字

总体收发数据图:

总体收发数据图

在创建套接字节阶段,需要调用Socket库中的socket程序组件,和构造函数的意思差不多,就是初始化。

当创建完套接字后,协议栈会返回一个描述符,应用程序会将收到的描述符存放在内存中。描述符是用来识别不同的套接字的,给应用程序找到客户端内对应的套接字。因为一台计算机内能同时存在多个套接字,所以需要描述符来分辨不同的套接字。

1.4.3 连接阶段

需要调用Socket库中的connect程序组件来完成,需要描述符、服务器IP和端口号这三个参数。

  1. 描述符:判断使用客户端中的哪个套接字来和服务器中的套接字来连接。
  2. 服务器IP:联系人
  3. 端口号:找服务器上的哪个套接字,在哪,被连接放所暴露的具体接口地址。通常是规定好,比如浏览器访问Web服务器用80号接口。

当连接成功后,协议栈会将对方的IP地址和端口号等信息保存在套接字中。

1.4.4 通信阶段

通过Socket库的write程序组件。需要描述符和要发送的数据两个参数。

要发送的数据对于浏览器来说就是用户输入网址生成的HTTP请求信息,描述符就是定位这些数据发出的出口。因为套接字中已经保存了已连接的通信对象的信息,所以指定出口后就可以直接发送了。

接下来就是服务器接收信息,对信息进行解析并执行相关操作,向客户端返回响应信息。

接收信息一般是Socket库中的read程序组件委托协议栈来完成,需要指定接收响应信息的内存地址,该内存地址称为接收缓冲区,相当于应用程序内部的内存空间,所以当信息被存放到接收缓冲区中时,就相当于已经转交给了应用程序。

1.4.5 断开阶段

通过Socket库的close程序组件。将套接字之间的管道断开,套接字本身也被删除。

所以总的来说,每获取一次数据,都要执行一次连接、发送请求信息、接收响应信息、断开的过程。所以当网页中信息较多时,会重复多次,效率较低,所以设计了一种能一次连接中收发多个请求和响应的方法。

第二章 用电信号传输TCP/IP数据

2.1 创建套接字

2.1.1 协议栈的内部结构

协议栈内部结构

TCP/IP软件采用分层结构

协议栈由上下两部分组成

  1. 负责用TCP协议收发数据的部分和负责用UDP协议收发数据的部分
    它们只会接收应用程序的委托执行收发数据的操作
    一般应用程序都是使用TCP收发数据,而DNS查询等收发较短的控制数据时就使用UDP。
  2. 用IP协议控制网络包收发操作的部分
    在互联网上传数据时,数据会被切分成一个个的网络包,而将网络包发送给通信对象的操作就是由IP来负责的
    IP包括ICMP协议和ARP协议。ICMP用于告知网络包传送过程中产生的错误以及各种控制信息,ARP用于根据IP地址查询相应的以太网MAC地址。

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

在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,可以说该内存空间就是套接字,即存放控制信息的内存空间就是套接字。

  • 控制信息:IP地址、端口号、通信操作的进行状态等。

协议栈在执行操作时需要阅读这些控制信息。比如,在发送数据时,要看一看套接字中的通信对象IP地址和端口号,以便指向的IP地址和端口发送数据。在

在发送数据之后,协议栈需要等待对方返回收到数据的响应信息,但数据也可能在中途丢失,永远也等不到对方的响应。在这样的情况下,我们不能一直等下去,需要在等待一定时间之后重新发送丢失的数据,这就需要协议栈能够知道执行发送数据操作后过了多长时间。为此,套接字中必须要记录是否已经收到响应,以及发送数据后经过多长时间,才能根据这些信息按照需要执行重发操作。

套接字的作用:套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动 ,协议栈是根据套接字中记录的控制信息来工作的。

套接字内容可以在windows中的命令提示符中输入netstat指令查询,如下:

套接字内容

分为四个信息

  1. 协议类型:使用TCP/IP协议通信的情况下,会显示TCP或UDP。
  2. 本机地址:本机的IP地址和端口号。
  3. 通信对象:通信对象的IP地址和端口号。0.0.0.0表示还没有开始通信,没有绑定IP地址和端口号。此外UDP协议中的套接字不绑定对方的地址和端口,显示”:”。
  4. 状态:LISTENING等待对方连接的状态;ESTABLISHED完成连接并正在进行数据通信的状态。
  5. PID:使用该套接字的程序PID(进程标识符)。默认不显示。

2.1.3 调用socket时的操作

  • Socket表示Socket库,socket表示库中的socket程序组件。

操作概览

信息收发操作

应用程序调用socket申请创建套接字,协议栈首先会分配一个用于存放套接字所需的内存空间,且在该内存空间中写入表收发操作未开始的初始状态的信息。

协议栈分配内存是通过操作系统中的“内存管理“的模块,对整体内存进行合理调度,避免出错。

然后要将表示这个套接字的描述符告知应用程序。描述符是用于区分协议栈中多个套接字的号码牌。应用程序在向协议栈进行收发数据委托时就需要提供该描述符。

2.2 连接服务器

2.2.1 连接的意思

创建套接字后,应用程序会调用Socket库中的connect,随后协议栈会将本地的套接字与服务器的套接字进行连接。

连接有三大目的:

  1. 将服务器的IP地址和端口号等信息告知协议栈
  2. 客户端向服务器传达开始通信的请求
  3. 双方交换控制信息

交换的控制信息存储到一块开辟的内存空间中,称为缓冲区。交换的控制信息是根据通信规则来确定的,只要根据规则来执行连接操作,双方就可以得到必要的信息进而完成数据收发的准备。

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

控制信息大体上可以分为两类:

  1. 客户端和服务器相互联络时交换的控制信息。这些信息在整个通信过程中都需要,被添加在客户端与服务器之间传递的网络包的开头。在数据收发未开始阶段,网络包中没有具体的数据,只有控制信息。因为控制信息在网络包的开头,所以被称为头部。以太网和IP协议也有自己的控制信息,这些信息也叫头部。

TCP头部

交换控制信息

  1. 保存在套接字中,用于控制协议栈操作的信息。 应用程序和通信对象发给协议栈的信息都保存在套接字中,还有收发数据操作的执行状态等信息。

2.2.3 连接操作的实际过程

从调用Socket库的connect开始。

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

传入给connect的参数会传递给协议栈中的TCP模块。然后,TCP模块会与该IP地址对应的对象,也就是与服务器的TCP模块交换控制信息,分为以下几个步骤:

  1. 客户端创建一个包含表示开始数据收发操作的控制信息的头部。通过该头部客户端可以知道服务器的套接字的位置。将头部中的控制位SYN比特设置为1,表示连接。此外还要设置适当的序号和窗口大小(后文有提及)。
  2. TCP模块会将创建的信息传递给IP模块并委托它进行发送。当服务器的TCP模块收到该信息时,会根据TCP头部中记录的端口号找到对应的套接字,之后会将相应的信息写入套接字中,并将状态改为正在连接。完成上述操作后,服务器的TCP会返回响应,这个过程还需要将ACK控制位设置为1,这表示已经收到对方的网络包。ACK就是用来确定网络包是否送达。接下来,服务器的TCP模块会将TCP头部传递给IP模块,并委托IP模块向客户端返回响应。
  3. 网络包返回客户端。通过该网络包内的内容,判断连接服务器的操作是否成功,如果SYN为1则表示连接成功,此时会向套接字中写入服务器的IP地址、端口号等信息,同时还会将状态改为连接完毕。客户端也会发送ACK比特设置为1的成功标志给服务器。

2.3 收发数据

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

应用程序调用Socket库中的write程序组件,将要发送的信息给到协议栈,协议栈收到数据后执行发送操作,这一操作有如下要点:

  1. 协议栈不关心应用程序传来的数据是什么内容

  2. 协议栈收到数据并不是马上发送,而是先保存在内部的发送缓冲区中,并等待应用程序的下一段数据。因为应用程序发送数据并不由协议栈控制,所以发送标准并不统一,可能导致数据大小参差不齐,影响传输效率。积累多少数据再发送是一依照下面几个要素来判断的。

    • 每个网络包能容量的数据长度。协议栈会根据一个叫MTU(Maximum Transmission Unit 最大传输单元)的参数来进行判断。MTU表示一个网络包的最大长度,以太网中一般为1500字节,包含头部的总长度,所以MTU减去头部的长度,就是一个网络包中所能容纳的最大数据长度,称为MSS(Maximum Segment Size 最大分段大小)。当从应用程序收到的数据长度超过或接近MSS时再发送出去,就可以避免发送大量小包的问题了。

      MTU和MSS

    • 时间。协议栈内部存在一个计时器,当经过一定时间后,就会打网络包发出去。

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

    应用程序可以在发送数据时指定一些选项,来选择哪个要素优先。

2.3.2 对较大的数据进行拆分

当发送缓冲区中的数据超过MSS的长度,会将缓冲区中的数据按照MSS长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。

当需要发送缓冲区中的数据时,会将每块数据前面加上TCP头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给IP模块进行数据的发送。

数据拆分

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

确认的原理:首先,TCP 模块在拆分数据时,会先算好每一块数据相当于从头开始的第几个字节,接下来在发送这一块数据时,将算好的字节数写在 TCP 头部中,“序号”字段就是派在这个用场上的。
然后,发送数据的长度也需要告知接收方,不过这个并不是放在 TCP 头部里面的,因为用整个网络包的长度减去头部的长度就可以得到数据的长度,所以接收方可以用这种方法来进行计算。
有了上面两个数值,我们就可以知道发送的数据是从第几个字节开始,长度是多少了。

通过这些信息,接收方还能够检查收到的网络包有没有遗漏。例如假设上次接收到第 1460 字节,那么接下来如果收到序号为 1461 的包,说明中间没有遗漏;但如果收到的包序号为2921,那就说明中间有包遗漏了。
如果确认没有遗漏,接收方会将到目前为止接收到的数据长度加起来,计算出一共已经收到了多少个字节,然后将这个数值写入 TCP 头部的 ACK 号中发送给发送方。

ACK号

在实际的通信中,序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,这是因为如果序号都从 1 开始,通信过程就会非常容易预测,有人会利用这一点来
发动攻击。但是如果初始值是随机的,那么对方就搞不清楚序号到底是从多少开始计算的,因此需要在开始收发数据之前将初始值告知通信对象。这个操作在将 SYN 控制位设为 1 并发送给服务器的操作时进行。实际上,在将 SYN 设为 1 的同时,还需要同时设置序号字段的值,而这里的值就代表序号的初始值。

服务器向客户端也是同理:

服务器向客户端

实际工作过程:

实际工作过程

TCP 采用这样的方式确认对方是否收到了数据,在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的 ACK 号,那么就重新发送这些包。

如果发生网络中断、服务器宕机等问题,那么无论 TCP 怎样重传都不管用。这种情况下,无论如何尝试都是徒劳,因此 TCP 会在尝试几次重传无效之后强制结束通信,并向应用程序报错。

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

TCP 采用了动态调整等待时间的方法,这个等待时间是根据 ACK 号返回所需的时间来判断的。具体来说,TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间。

2.3.5 使用窗口有效管理 ACK

一个包一个ACK号的效率较低,所以使用滑动窗口的方式来管理数据发送和ACK号,利用等待ACK号返回的空挡期。

滑动窗口式

当数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出。缓冲区溢出之后,后面的数据就进不来了,因此接收方就收不到后面的包了。使用下面的方法来避免这种情况的发生,即:接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制。
思路:接收方将数据暂存到接收缓冲区中并执行接收操作。当接收操作完成后,接收缓冲区中的空间会被释放出来,也就可以接收更多的数据了,这时接收方会通过 TCP 头部中的窗口字段将自己能接收的数据量告知发送方。

发送缓冲区剩余大小

2.3.6 ACK与窗口的合并

更新窗口大小的时机:接收方从缓冲区中取出数据传递给应用程序的时候。这个操作是接收方应用程序发出请求时才会进行的,而发送方不知道什么时候会进行这样的操作,因此当接收方将数据传递给应用程序,导致接收缓冲区剩余容量增加时,就需要告知发送方,这就是更新窗口大小的时机。

ACK号发送的时机:收到数据之和马上发送。

所以接收到一个网络包后,在接收端数据处理的前后有两个单独的网络包发送,会导致网络效率降低,所以要平衡两者的包发送时机,减少发送包的数量,提高网络效率。

比如,接收方在发送 ACK 号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里面发送了。当需要连续发送多个窗口更新时也可以减少包的数量,因为连续发生窗口更新说明应用程序连续请求了数据,接收缓冲区的剩余空间连续增加。这种情况和ACK 号一样,可以省略中间过程,只要发送最终的结果就可以了。

2.3.7 接收HTTP响应信息

首先,浏览器在委托协议栈发送请求消息之后,会调用 read 程序来获取响应消息。然后,控制流程会通过 read 转移到协议栈 ****,然后协议栈会执行接下来的操作。和发送数据一样,接收数据也需要将数据暂存到接收缓冲区中,这里的操作过程如下。
首先,协议栈尝试从接收缓冲区中取出数据并传递给应用程序,但这个时候请求消息刚刚发送出去,响应消息可能还没返回。响应消息的返回还需要等待一段时间,因此这时接收缓冲区中并没有数据,那么接收数据的操作也就无法继续。这时,协议栈会将应用程序的委托,也就是从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起 ****,等服务器返回的响应消息到达之后再继续执行接收操作。

协议栈接收信息:协议栈会检查收到的数据块和 TCP 头部的内容,判断是否有数据丢失,如果没有问题则返回 ACK 号。然后,协议栈将数据块暂存到接收缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序。具体来说,协议栈会将接收到的数据复制到应用程序指定的内存地址中,然后将控制流程交回应用程序。将数据交给应用程序之后,协议栈还需要找到合适的时机向发送方发送窗口更新。

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

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

数据发送完毕后服务端和客户端都可主动断开连接,至于是谁操作,那就得看应用程序的设置了。下面以服务器一方断开为例。

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

断开连接

接下来到客户端。当客户端收到来自服务器发来的 FIN 为 1 的 TCP 头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后,为了告知服务器已收到 FIN 为 1 的包,客户端会向服务器返回一个 ACK 号。这些操作完成后,协议栈就可以等待应用程序来取数据了。

过了一会儿,应用程序就会调用 read 来读取数据 ****。这时,协议栈不会向应用程序传递数据,而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。此时,客户端的工作完成,也会生成一个FIN为1的TCP头部,委托IP模块发送给服务器,当服务器收到后也会返回一个ACK号给客户端。至此,双方通信结束。

2.4.2 删除套接字

套接字删除并不是在通信结束后立即被删除,而是会等待一会才会删除。因为立即删除会存在一些隐患,比如下面的例子。

当客户端收到来自服务器发送的FIN为1的TCP头部时,确认无误后,会发送ACK号给服务器,但如果该信息丢失,服务器没有收到该ACK号,则会再次发送FIN为1的TCP头部,但客户端的套接字已经删除,无法接收到再次发来的FIN为1的TCP头部。如果此时恰好有一个应用程序创建了一个新的套接字,且与删除的套接字的端口相同,则发送来的FIN为1的TCP头部会被新的套接字接收,则新创建的套接字未接收到数据就被删除了,会导致应用程序无法接收到数据,导致错误。

具体等待多长时间,和包的重传操作有关。一般来说会等几分钟在删除。

2.4.3 数据收发操作小结

步骤:

  1. 创建套接字,通信双方
  2. 客户端发起通信请求
  3. 服务器收到请求发送响应
  4. 客户端收到发送响应,连接完成
  5. 数据收发
    • 应用程序委托的数据划分
    • 滑动窗口式接收
    • ACK号返回
  6. 结束后断开连接
  7. 套接字删除

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

2.5.1 包的基本知识

包的结构: 头部 + 数据

头部记录了各种控制信息,数据就是包的内容。

TCP/IP包:MAC头部 + IP头部 + TCP头部 + 数据块

MAC头部:以太网的控制信息

IP头部:IP控制信息

IP包:IP头部 + TCP头部 + 数据块

以太网包:MAC头部 + IP头部 + TCP头部 + 数据块

包的结构

首先,发送方的网络设备会负责创建包,创建包的过程就是生成含有正确控制信息的头部,然后再附加上要发送的数据。接下来,包会被发往最近的网络转发设备。当到达最近的转发设备之后,转发设备会根据头部中的信息判断接下来应该发往哪里。这个过程需要用到一张表,这张表里面记录了每一个地址对应的发送方向,也就是按照头部里记录的目的地址在表里进行查询,并根据查到的信息判断接下来应该发往哪个方向。这些表有MAC地址:端口、IP:端口、IP:MAC地址等。把发送方和接收方统称为终端节点。

网络转发设备有交换机、路由器和集线器等,有着各自的分工。

  1. 交换机:MAC地址:端口,找到目标MAC地址所在端口。
  2. 路由器:IP:端口,找到目标IP所在子网。
  3. 集线器:将包以广播形式发送给在集线器上的所有设备。

TCP/IP协议有两个头部。

  1. MAC头部
  2. IP头部

步骤

IP网络包的传输方式

2.5.2 包收发操作概述

IP模块的操作:

  1. 将TCP传输来的TCP头部 + 数据当作一整块,再在前面加上IP头部和MAC头部。MAC头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息。IP头部包含发往目的地所需的控制信息。
  2. 将封装好的信息交给网络硬件,由网络硬件发送。

IP模块不关心发来的是什么,只负责加上相应头部后发送出去。

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

接收方IP地址来源于应用程序,如果出现错误,IP模块不负责。

IP地址是分配给计算机中的网卡的,可以改变,有多少个网卡就有多少个IP地址,使用哪个IP主要由要发送的目标路由器决定(默认网关 → 路由器IP)。

路由表:IP和端口的映射,在路由器中。

如果要发送的目标IP在同一子网中,则发给交换机后,由交换机按照MAC地址直接发送给目标IP地址的机器即可。
如果要发送的目标IP不在同一子网中,则会通过ARP协议,将MAC地址通过默认网关写为路由器的MAC地址,交给交换机时,交换机就将该包发送给路由器,路由器通过MAC头部,分辨出是发送给自己的,且目标IP是在其他网络中,则会发送出去,当到达相应网络的路由器时,路由器会通过IP地址和MAC地址的映射,将该包加上相应的MAC地址,再通过路由表发送给对应端口的交换机,最后交换机通过MAC地址找到对应的机器,机器会判断MAC地址和自己是否相同,如果相同,则接收,反之则舍去。
但假如路由器不知道该IP地址对应的MAC地址,则会通过APR协议向所有端口的交换机发送目标IP,如果该IP是某个机器的,则会相应,将MAC地址发送过去。

IP头部

IP头部总览

2.5.4 生成以太网用的MAC地址

以太网:以太网是局域网的一种技术。

MAC地址:每个网卡的物理地址。

以太网通过MAC地址来发送包。

MAC头部

MAC头部是以太网使用的头部,包含了接收方和发送方的MAC地址等信息。

IP地址为32Bit,MAC地址为48Bit。

目标MAC地址在包要通过路由器发送出去时,会不断改变。当要发送给路由器时,MAC地址为路由器的MAC地址,而不是目标MAC地址,在传输到目标机器所在路由器时,MAC地址会变为目标MAC地址。

发送方MAC地址就由发送这个包的网卡决定。

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

当发送方不知道路由器的MAC地址是,会以默认网关的IP地址(目标路由器的IP)为包,发送给子网中的所有机器,当目标路由器收到该包时,会将该包加上MAC地址,包装成帧,发送回发送方。

在子网的每个机器中,都会有一个ARP缓存,存放IP和MAC地址的映射。

2.5.6 以太网的基本知识

MAC地址和交换机,从集线器的群发变成依据MAC地址的私发。

双绞线(英语:Twisted pair)是由两条外面被覆塑胶类绝缘材料、内含铜线缆,互相绝缘的双线互相缠绕(一般以顺时针缠绕),绞合成螺旋状的一种电线缆。双绞线可减少发送中信号的衰减、减少**串扰(英语:crosstalk)及噪声**(英语:Noise)、并改善了对外部电磁干扰的抑制能力。(来自维基百科)

以太网的三个性质:

  1. 包发送到MAC头部的接收方地址代表目的地
  2. 用发送方MAC地址识别发送方
  3. 用以太类型识别包的内容

与IP相同,以太网也不关心网络包的实际内容,因此以太网的收发操作也和TCP的工作阶段无关,都是共通的。

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

数据 → 段(TCP头部 + 数据)→ 包(IP头部 + 段)→ 帧(MAC头部 + 包)→ 二进制bit(电或光信号传输)

网卡的初始化过程:

上电启动操作系统后,网卡驱动程序会对硬件进行初始化操作,硬件才可以使用。

上面说了网卡MAC地址,其中MAC地址存放在网卡ROM中,通过网卡驱动读取到MAC模块中。MAC地址是唯一的,在生产网卡时写入。

2.3.8 给网络包的三个控制数据

就是由包变为帧的过程。

MAC模块会将包从缓冲区取出,并在开头加上报头和起始帧分界符,并在末尾加上用于检测错误的帧检验序列。

帧

报头是一串像1010101010101…… 这样的1和0交替的比特序列,长度为56比特,它的作用是确定包的读取时机。当这些比特序列转为电信号后,会形成如下图的波形,当遇到这样的波形就可以判断读取数据的时机。

报头

如何通过电信号来读取数据:

用电信号来表达数字信息时,我们需要让0和1两种比特分别对应特定的电压和电流。但读取该信号的时机如何确定?答案是通过时钟信号,当时钟信号从上往下变化时读取电压和电流的值,读到几就是几。但该种方式容易产生偏差,即时钟信号也需要一定时间被读取到,这段时间可能产生误差,导致错误。

所以可以使用时钟信号和数据信号叠加在一起,且告知时钟信号的频率,让接收方自己按照该频率拆分出数据信号。报头的作用就是告知测量时钟信号的特性信号。

起始帧分界符:以某一变化作为标记,从这开始提取网络包内容。

FCS(帧校验序列):用来检查包传输过程中因噪声导致的波形紊乱、数据错误,是一串32比特的序列,是通过公式对包从头到尾的所有内容进行计算而得出来的。通过发送方计算的FCS和接收方计算的FCS不同而判断错误。

2.5.9 向集线器发送网络包

通过网线发送包有两种操作:

  1. 集线器的半双工
  2. 交换机的全双工

半双工:某一时刻只能进行发送或者接收其中一种操作。全双工:发送和接收并行的方式。

在半双工模式中,为了避免信号碰撞,首先要判断网线中是否存在其他设备发送的信号。如果有,则需要等待该信号传输完毕,为防止碰撞。当没有信号传输时,则开始发送信号。

网卡的 MAC 模块生成通用信号,然后由PHYMAU)模块转换成可在网线中传输的格式,并通过网线发送出去。

PHY(MAU)的职责并不是仅仅是将 MAC 模块传递过来的信号通过网线发送出去,它还需要监控接收线路中有没有信号进来。在开始发送信号之前,需要先确认没有其他信号进来,这时才能开始发送。如果在信号开始发送到结束发送的这段时间内一直没有其他信号进来,发送操作就成功完成了。以太网不会确认发送的信号对方有没有收到。根据以太网的规格,两台设备之间的网线不能超过 100 米,在这个距离内极少会发生错误,万一发生错误,协议栈的 TCP 也会负责搞定,因此在发送信号时没有必要检查错误。

天不遂人愿,总会出现信号碰撞的情况出现,此时继续发送信号是没有意义的,因此发送操作会终止。为了通知其他设备当前线路已发生碰撞,还会发送一段时间的阻塞信号,然后所有的发送操作会全部停止。

等待一段时间之后,网络中的设备会尝试重新发送信号。但如果所有设备的等待时间都相同,那肯定还会发生碰撞,因此必须让等待的时间相互错开。具体来说,等待时间是根据 MAC 地址生成一个随机数计算出来的。当网络拥塞时,发生碰撞的可能性就会提高,重试发送的时候可能又会和另外一台设备的发送操作冲突,这时会将等待时间延长一倍,然后再次重试。以此类推,每次发生碰撞就将等待时间延长一倍,最多重试 10次,如果还是不行就报告通信错误。

2.5.10 接收返回包

集线器半双工模式的以太网中,只要某一终端发信息,则其他所有终端都会接收该信息。

信息的开头是报头,通过报头的波形同步时钟,然后遇到起始帧分界符时开始将后面的信号转换成数字信息。这个操作和发送时是相反的,即 PHY(MAU)模块先开始工作,然后再轮到 MAC 模块。首先,PHY(MAU)模块会将信号转换成通用格式并发送给 MAC 模块,MAC 模块再从头开始将信号转换为数字信息,并存放到缓冲区中。当到达信号的末尾时,还需要检查 FCS。具体来说,就是将从包开头到结尾的所有比特套用到公式中计算出 FCS,然后和包末尾的 FCS 进行对比,正常情况下两者应该是一致的,如果中途受到噪声干扰而导致波形发生紊乱,则两者的值会产生差异,这时这个包就会被当作错误包而被丢弃。

如果 FCS 校验没有问题,接下来就要看一下 MAC 头部中接收方 MAC 地址与网卡在初始化时分配给自己的MAC 地址是否一致,以判断这个包是不是发给自己的。如果接收方 MAC 地址和自己 MAC 地址一致,则将包放入缓冲区中 ****。到这里,MAC 模块的工作就完成了,接下来网卡会通知计算机收到了一个包。

通知计算机的操作会使用一个叫作中断的机制。在网卡执行接收包的操作的过程中,计算机并不是一直监控着网卡的活动,而是去继续执行其他的任务。因此,如果网卡不通知计算机,计算机是不知道包已经收到了这件事的。

中断机制:网卡向扩展总线中的中断信号线发送信号,该信号线通过计算机中的中断控制器连接到 CPU。当产生中断信号时,CPU 会暂时挂起正在处理的任务,切换到操作系统中的中断处理程序。然后,中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作。

中断是有编号的,网卡在安装的时候就在硬件中设置了中断号,在中断处理程序中则将硬件的中断号和相应的驱动程序绑定。

网卡驱动被中断处理程序调用后,会从网卡的缓冲区中取出收到的包,并通过 MAC 头部中的以太类型字段判断协议的类型。

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

IP模块工作:检查IP头部格式 → 查看接收方IP地址

如果接收方 IP 地址不是自己的地址,那一定是发生了什么错误。客户端计算机不负责对包进行转发,因此不应该收到不是发给自己的包(服务器可以收到不是发送给自己的包,而是只对包进行转发)。当发生这样的错误时,IP 模块会通过 ICMP 消息将错误告知发送方。ICMP内容如下。

ICMP

ICMP

如果接收方 IP 地址正确,则这个包会被接收下来,这时还需要完成另一项工作。IP 协议有一个叫作分片的功能。对发出去的包进行分片,对收到的包进行重组。因为网线和局域网中只能传输小包。分片的包会在 IP 头部的标志字段中进行标记,当收到分片的包时,IP 模块会将其暂存在内部的内存空间中,然后等待 IP 头部中具有相同 ID 的包全部到达。此外,IP头部还有一个分片偏移量(fragment offset)字段,它表示当前分片在整个包中所处的位置。在所有分片全部收到之后,就可以将它们还原成原始的包,这个操作叫作分片重组。

到这里,IP 模块的工作就结束了,接下来包会被交给 TCP 模块。TCP 模块会根据 IP 头部中的接收方和发送方 IP 地址,以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字 。找到对应的套接字之后,就可以根据套接字中记录的通信状态,执行相应的操作了。

2.6 UDP协议的收发操作

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

UDP协议不需要返回类似TCP协议的控制信息,因为就一个包,如果出错了就重发一次,影响不大。TCP协议为了实现高效的传输,尽量减少重复发送已送达的包,而是只重发那些出错的或者未送达的包,这样才比UDP协议更加复杂。

2.6.2 控制用的短数据

UDP 没有 TCP 的接收确认、窗口等机制,因此在收发数据之前也不需要交换控制信息,也就是说不需要建立和断开连接的步骤,只要在从应用程序获取的数据前面加上
UDP 头部,然后交给 IP 进行发送就可以了。

接收也很简单,只要根据 IP 头部中的接收方和发送方 IP 地址,以及 UDP 头部中的接收方和发送方端口号,找到相应的套接字并将数据交给相应的应用程序就可以了。

如果出错,则应用程序收不到信息,会再次委托,就重发了。

UDP头部

2.6.3 音频和视频数据

音频和视频数据必须在规定的时间内送达,一旦送达晚了,就会错过播放时机,导致声音和图像卡顿。如果像 TCP 一样通过接收确认响应来检查错误并重发,重发的过程需要消耗一定的时间,因此重发的数据很可能已经错过了播放的时机。一旦错过播放时机,重发数据也是没有用的,因为声音和图像已经卡顿了,这是无法挽回的。

此外,音频和视频数据中缺少了某些包并不会产生严重的问题,只是会产生一些失真或者卡顿而已,一般都是可以接受的。

在这些无需重发数据,或者是重发了也没什么意义的情况下,使用 UDP 发送数据的效率会更高。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值