提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
浏览器并不会亲自负责数据的传送。传送消息是搬运数字信息的机制负责的工作,浏览器会委托它将数据发送出去。委托操作系统中的网络控制软件将消息发送给服务器。
www 只是 Web 服务器上的一种命名
个人可以申请“.cn”域名,但“.com.cn”“.net.cn”等域名则是不开放给个人注册的
浏览器是一个具备多种客户端功能的综合性客户端软件,
因此它需要一些东西来判断应该使用其中哪种功能来访问相应的数据
而各种不同的 URL 就是用来干这个的
访问 Web 服务器时用“http:”
访问 FTP 服务器时用“ftp:”,这是一种在上传、下载文件时使用的协议
会在服务器上事先设置好文件名省略时要访问的默认文件名。这个设置根据服务器不同而不同,大多数情况下是 index.html 或者 default.htm
HTTP 协议定义了客户端和服务器之间交互的消息内容和步骤,其基本思路非常简单。
首先,客户端会向服务器发送请求消息。
请求消息中包含的内容是“对什么”和“进行怎样的操作”两个部分。
“对什么”的部分称为 URI,各种访问目标
“进行怎样的操作”的部分称为方法,需要让 Web 服务器完成怎样的工作
HTTP 消息中还有一些用来表示附加信息的头字段。
客户端向 Web 服务器发送数据时,会先发送头字段,然后再发送数据。不过,头字段属于可有可无的附加信息
收到请求消息之后,Web 服务器会对其中的内容进行解析,
通过 URI 和方法来判断“对什么”“进行怎样的操作”,并根据这些要求来完成自己的工作,然后将结果存放在响应消息中。
在响应消息的开头有一个状态码,它用来表示操作的执行结果是成功还是发生了错误。
当我们访问 Web 服务器时,遇到找不到的文件就会显示出 404 Not Found 的错误信息,其实这就是状态码。
状态码后面就是头字段和网页数据。
响应消息会被发送回客户端,客户端收到之后,浏览器会从消息中读出所需的数据并显示在屏幕上。
到这里,HTTP 的整个工作就完成了。
GET 方法:访问 Web 服务器获取网页数据
POST方法:在表单中填写数据并将其发送给 Web 服务器
PUT 和DELETE 方法,就能够从客户端修改或者删除 Web 服务器上的文件
1 条请求消息中只能写 1 个 URI。如果需要获取多个文件,必须对每个文件单独发送 1 条请求。
在网络中,所有的设备都会被分配一个地址。这个地址就相当于现实中某条路上的“×× 号 ×× 室”。
其中“号”对应的号码是分配给整个子网的,而“室”对应的号码是分配给子网中的计算机的,
这就是网络中的地址。“号”对应的号码称为网络号,“室”对应的号码称为主机号,这个地址的整体称为 IP 地址
子网掩码表示网络号与主机号之间的边界。
主机号部分全部为0代表整个子网而不是子网中的某台设备。此外,主机号部分全部为 1 代表向子网上所有设备发送包,即广播
TCP/IP 网络是通过 IP 地址来确定通信对象的,因此不知道 IP 地址就无法将消息发送给对方,
这和我们打电话的时候必须要知道对方的电话号码是一个道理。
因此,在委托操作系统发送消息时,必须要先查询好对方的 IP 地址
让人来使用名称,让路由器来使用 IP 地址。
为了填补两者之间的障碍,需要有一个机制能够通过名称来查询 IP 地址,或者通过 IP 地址来查询名称,
这样就能够在人和机器双方都不做出牺牲的前提下完美地解决问题。这个机制就是 DNS域名服务系统
Socket 库是用于调用网络功能的程序组件集合
根据域名查询IP地址时,浏览器会使用 Socket 库中的解析器
调用解析器后,解析器会向 DNS 服务器发送查询消息,然后 DNS 服务器会返回响应消息
响应消息中包含查询到的IP地址,解析器会取出 IP 地址,并将其写入浏览器指定的内存地址中
浏览器在向 Web 服务器发送消息时,只要从该内存地址取出 IP 地址,并将它与 HTTP 请求消息一起交给操作系统就可以了
由于调用了其他程序,原本运行的程序进入暂停状态,而被调用的程序开始运行,这就是“控制流程转移”
计算机的内部结构就是这样一层一层的。也就是说,很多程序组成不同的层次,彼此之间分工协作。
当接到上层委派的操作时,本层的程序并不会完成所有的工作,而是会完成一部分工作,再将剩下的部分委派到下层来完成。
Class 的值永远是代表互联网的 IN
当记录类型为 A 时,表示域名对应的是 IP 地址
当类型为 MX时,表示域名对应的是邮件服务器
DNS 服务器的基本工作就是根据需要查询的域名和记录类型查找相关的记录,并向客户端返回响应消息
DNS 服务器会从域名与 IP 地址的对照表中查找相应的记录,并返回 IP 地址。
在域名中,越靠右的位置表示其层级越高
将负责管理下级域的 DNS 服务器的 IP 地址注册到它们的上级 DNS 服务器中,
然后上级 DNS 服务器的 IP 地址再注册到更上一级的 DNS 服务器中
负责管理lab.glasscom.com这个域的 DNS 服务器的IP 地址需要注册到glasscom.com域的 DNS 服务器中,
而 glasscom.com 域的 DNS 服务器的 IP 地址又需要注册到com域的 DNS 服务器中
根域 DNS 服务器在运营上使用多台服务器来对应一个 IP 地址,因此尽管 IP 地址只有 13 个,但其实服务器的数量是很多的
DNS 服务器有一个缓存功能,可以记住之前查询过的域名。
如果要查询的域名和相关信息已经在缓存中,那么就可以直接返回响应,
接下来的查询可以从缓存的位置开始向下进行。
相比每次都从根域找起来说,缓存可以减少查询所需的时间
缓存:指的是将使用过的数据存放在离使用该数据的地方较近的高速存储装置中,以便提高后续访问速度的技术。
如CPU和内存之间的缓存、磁盘和内存之间的缓存等,在网络中缓存也是一种用来提高访问速度的普遍性技术
当要查询的域名不存在时,“不存在”这一响应结果也会被缓存。这样,当下次查询这个不存在的域名时,也可以快速响应
这个缓存机制中有一点需要注意,那就是信息被缓存后,原本的注册信息可能会发生改变,
这时缓存中的信息就有可能是不正确的。因此,DNS 服务器中保存的信息都设置有一个有效期,
当缓存中的信息超过有效期后,数据就会从缓存中删除。
而且,在对查询进行响应时,DNS服务器也会告知客户端这一响应的结果是来自缓存中还是来自负责管理该域名的DNS服务器。
使用 Socket 库来收发数据,简单来说,收发数据的两台计算机之间连接了一条数据通道,
数据沿着这条通道流动,最终到达目的地。我们可以把数据通道想象成一条管道,
将数据从一端送入管道,数据就会到达管道的另一端然后被取出。
数据可以从任何一端被送入管道,数据的流动是双向的
在进行收发数据操作之前,双方需要先建立起这条管道才行
建立管道的关键在于管道两端的数据出入口,这些出入口称为套接字
我们需要先创建套接字,然后再将套接字连接起来形成管道
首先,服务器一方先创建套接字,然后等待客户端向该套接字连接管道
当服务器进入等待状态时,客户端就可以连接管道了
客户端也会先创建一个套接字,然后从该套接字延伸出管道
最后管道连接到服务器端的套接字上
当双方的套接字连接起来之后,通信准备就完成了
接下来,只要将数据送入套接字就可以收发数据了
当数据全部发送完毕之后,连接的管道将会被断开。
管道在连接时是由客户端发起的,但在断开时可以由客户端或服务器任意一方发起
其中一方断开后,另一方也会随之断开,当管道断开后,套接字也会被删除。
(1)创建套接字(创建套接字阶段)
(2)将管道连接到服务器端的套接字上(连接阶段)
(3)收发数据(通信阶段)
(4)断开管道并删除套接字(断开阶段)
其中小写的 socket 表示程序组件的名称,大写字母开头的 Socket 表示库
套接字创建完成后,协议栈会返回一个描述符,应用程序会将收到的描述符存放在内存中
描述符是用来识别不同的套接字的
实际上计算机中会同时进行多个数据的通信操作,比如可以打开两个浏览器窗口,同时访问两台 Web 服务器
有两个数据收发操作在同时进行,也就需要创建两个不同的套接字,
同一台计算机上可能同时存在多个套接字,在这样的情况下,我们就需要一种方法来识别出某个特定的套接字,
这种方法就是描述符。我们可以将描述符理解成给某个套接字分配的编号。
想象一下在酒店寄存行李时的场景,酒店服务人员会给你一个号码牌,向服务人员出示号码牌,
就可以取回自己寄存的行李,描述符的原理和这个差不多。
当创建套接字后,我们就可以使用这个套接字来执行收发数据的操作了
只要我们出示描述符,协议栈就能够判断出我们希望用哪一个套接字来连接或者收发数据了。
将客户端创建的套接字与服务器那边的套接字连接起来。
应用程序通过调用Socket 库中的名为 connect 的程序组件来完成这一操作
第 1 个参数,即描述符,就是在创建套接字的时候由协议栈返回的那个描述符。
connect 会将应用程序指定的描述符告知协议栈
然后协议栈根据这个描述符来判断到底使用哪一个套接字去和服务器端的套接字进行连接,并执行连接的操作
第 2 个参数,即服务器 IP 地址,就是通过 DNS 服务器查询得到的我们要访问的服务器的 IP 地址
第 3 个参数,即端口号, IP 地址就像电话号码,只要知道了电话号码不就可以联系到对方了吗?
IP 地址是为了区分网络中的各个计算机而分配的数值 。
只要知道了 IP 地址,我们就可以识别出网络上的某台计算机。
但是,连接操作的对象是某个具体的套接字,因此必须要识别到具体的套接字才行,
而仅凭 IP 地址是无法做到这一点的。我们打电话的时候,
也需要通过“请帮我找一下某某某”这样的方式来找到具体的某个联系人,
而端口号就是这样一种方式。当同时指定 IP 地址和端口号时,
就可以明确识别出某台具体的计算机上的某个具体的套接字
IP 地址不是分配给每一台设备的,而是分配给设备中安装的网络硬件的
因此,如果一台设备中安装了多个网络硬件,那么就会有多个 IP 地址
如果说描述符是用来在一台计算机内部识别套接字的机制,那么端口号就是用来让通信的另一方能够识别出套接字的机制
服务器上所使用的端口号是根据应用的种类事先规定好的,仅此而已。比如 Web 是 80号端口,电子邮件是 25 号端口
描述符:应用程序用来识别套接字的机制
IP 地址和端口号:客户端和服务器之间用来识别对方套接字的机制
HTTP协议规定,当Web服务器发送完响应消息之后,应该主动执行断开操作
因此 Web 服务器会首先调用close来断开连接
断开操作传达到客户端之后,客户端的套接字也会进入断开阶段
当浏览器调用read 执行接收数据操作时,read 会告知浏览器收发数据操作已结束,连接已经断开。
浏览器得知后,也会调用 close 进入断开阶段
1) http://www.nikkeibp.co.jp/ 中的 http 代表HTTP 协议
2) sample 代表什么意思?
a. http://www.nikkeibp.co.jp/sample 代表文件名
b. http://www.nikkeibp.co.jp/sample/ 代表目录名
3)用来识别连接在互联网上的计算机和服务器的地址叫IP 地址
4) 根据 Web 服务器的域名来查询 IP 地址时所使用的服务器叫.DNS 服务器
5) 向 DNS 服务器发送请求消息的程序叫解析器
只要给解析器一个域名,解析器就能分析它并给我求出 IP 地址
协议栈(网络控制软件叫作协议栈),将从浏览器接收到的消息打包,然后加上目的地址等控制信息
如果拿邮局来比喻,就是把信装进信封,然后在信封上写上收信人的地址
当发生通信错误时重新发送包,或者调节数据发送的速率等
可以把它当作一位帮我们寄信的小秘书
协议栈会将包交给网卡(负责以太网或无线网络通信的硬件),网卡会将包转换为电信号并通过网线发送出去。这样一来,包就进入到网络之中了
1)最上面的部分是网络应用程序,也就是浏览器、电子邮件客户端、Web服务器、电子邮件服务器等程序,它们会将收发数据等工作委派给下层的部分来完成,不同的应用程序收发的数据内容不同,但收发数据的操作是共通的
应用程序的下面是 Socket 库,其中包括解析器,解析器用来向 DNS 服务器发出查询
2)操作系统内部
浏览器、邮件等一般应用程序收发数据时用 TCP
DNS 查询等收发较短的控制数据时用UDP
IP 协议控制网络包收发操作的部分。在互联网上传送数据时,数据会被切分成一个一个的网络包 ,而将网络包发送给通信对象的操作就是由 IP 来负责的。此外,IP 中还包括 ICMP 协议和 ARP 协议
ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息
ARP 用于根据 IP 地址查询相应的以太网 MAC 地址
3)IP 下面的网卡驱动程序负责控制网卡硬件
4)最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作
在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,例如通信对象的 IP 地址、端口号、通信操作的进行状态等。本来套接字就只是一个概念而已,并不存在实体,如果一定要赋予它一个实体,我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体
套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动,这就是套接字的作用。
PID:Process ID(进程标识符)的缩写,是操作系统为了标识程序而分配的编号,使用任务管理器可以查询所对应的程序名称
应用程序调用 socket 申请创建套接字,协议栈根据应用程序的申请执行创建套接字的操作,协议栈首先会分配用于存放一个套接字所需的内存空间。用于记录套接字控制信息的内存空间并不是一创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态。开始就存在的,因此我们先要开辟出这样一块空间来。
计算机内部会同时运行多个程序,如果每个程序都擅自使用内存空间的话,就有可能发生多个程序重复使用同一个内存区域导致数据损坏的
问题。为了避免出现这样的问题,操作系统中有一个“内存管理”模块,它相当于内存的管理员,负责根据程序的申请分配相应的内存空间,并
确保这些内存空间不会被其他程序使用。因此,分配内存的操作就是向内存管理模块提出申请,请它划分一块内存空间出来。
创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态。接下来,需要将表示这个套接字的描述符告知应用程序。描述符相当于用来区分协议栈中的多个套接字的号码牌 。
收到描述符之后,应用程序在向协议栈进行收发数据委托时就需要提供这个描述符。由于套接字中记录了通信双方的信息以及通信处于怎样的状态,所以只要通过描述符确定了相应的套接字,协议栈就能够获取所有的相关信息,这样一来,应用程序就不需要每次都告诉协议栈应该和谁进行通信了。
创建套接字之后,应用程序(浏览器)就会调用 connect,随后协议栈会将本地的套接字与服务器的套接字进行连接。
连接实际上是通信双方交换控制信息。
网络包中没有实际的数据,只有控制信息。这些控制信息位于网络包的开头,因此被称为头部。
此外,以太网和 IP 协议也有自己的控制信息,这些信息也叫头部,为了避免各种不同的头部发生混淆,我们一般会记作 TCP 头部、以太网头部 、IP 头部
客户端和服务器在通信中会将必要的信息记录在头部并相互确认,头部是用来记录和交换控制信息的
通信操作中使用的控制信息分为两类。
(1) 头部中记录的信息
(2) 套接字(协议栈中的内存空间)中记录的信息
connect(< 描述符 >, < 服务器 IP 地址和端口号 >, …)
首先,客户端先创建一个包含表示开始数据收发操作的控制信息的头部。头部包含很多字段,重点是发送方和接收方的端口号
到这里,客户端(发送方)的套接字就准确找到了服务器(接收方)的套接字,也就是搞清楚了我应该连接哪个套接字
然后,我们将头部中的控制位的 SYN 比特设置为 1,表示连接
连接操作的第一步是在 TCP 模块处创建表示连接控制信息的头部
通过 TCP 头部中的发送方和接收方端口号可以找到要连接的套接字
1)当 TCP 头部创建好之后,接下来 TCP 模块会将信息传递给 IP 模块并委托它进行发送
2)IP 模块执行网络包发送操作后,网络包就会通过网络到达服务器,然后服务器上的 IP 模块会将接收到的数据传递给 TCP 模块
3)服务器的 TCP 模块根据 TCP 头部中的信息找到端口号对应的套接字,也就是说,从处于等待连接状态的套接字中找到与 TCP 头部中记录的端口号相同的套接字就可以了。当找到对应的套接字之后,套接字中会写入相应的信息,并将状态改为正在连接 。
4)服务器的 TCP 模块会返回响应,这个过程和客户端一样,需要在 TCP 头部中设置发送方和接收方端口号以及 SYN 比特 。此外,在返回响应时还需要将 ACK 控制位设为 1 ,这表示已经接收到相应的网络包
网络中经常会发生错误,网络包也会发生丢失,因此双方在通信时必须相互确认网络包是否已经送达 ,而设置 ACK 比特就是用来进行这一确认的
5)服务器 TCP 模块会将 TCP 头部传递给 IP 模块,并委托 IP 模块向客户端返回响应
6)网络包就会返回到客户端,通过 IP 模块到达 TCP 模块,并通过 TCP 头部的信息确认连接服务器的操作是否成功。如果 SYN 为 1 则表示连接成功,这时会向套接字中写入服务器的 IP 地址、端口号等信息,同时还会将状态改为连接完毕。到这里,客户端的操作就已经完成
7)刚才服务器返回响应时将 ACK 比特设置为 1,相应地,客户端也需要将 ACK 比特设置为 1 并发回服务器,告诉服务器刚才的响应包已经收到。当这个服务器收到这个返回包之后,连接操作才算全部完成
8)现在,套接字就已经进入随时可以收发数据的状态了,大家可以认为这时有一根管子把两个套接字连接了起来。当然,实际上并不存在这么一根管子,不过这样想比较容易理解,网络业界也习惯这样来描述。这根管子,我们称之为连接 。只要数据传输过程在持续,也就是在调用 close 断开之前,连接是一直存在的
当控制流程从 connect 回到应用程序之后,接下来就进入数据收发阶段了。数据收发操作是从应用程序调用write 将要发送的数据交给协议栈开始的
协议栈并不关心应用程序传来的数据是什么内容。应用程序在调用 write 时会指定发送数据的长度,在协议栈看来,要发送的数据就是一定长度的二进制字节序列
协议栈并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据
如果一收到数据就马上发送出去,就可能会发送大量的小包,导致网络效率下降,因此需要在数据积累到一定量时再发送出去。至于要积累多少数据才能发送,不同种类和版本的操作系统会有所不同
第一个判断要素是每个网络包能容纳的数据长度,协议栈会根据一个叫作 MTU(最大传输单元) 的参数来进行判断。MTU表示一个网络包的最大长度,MTU 是包含头部的总长度,因此需要从 MTU 减去头部的长度,然后得到的长度就是一个网络包中所能容纳的最大数据长度,这一长度叫作MSS (最大分段大小)。当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题
在以太网中, MTU 为 1500,因此 MSS 就是 1460。TCP/IP 可以使用一些可选参数(protocol option),如加密等,这时头部的长度会增加,那么MSS 就会随着头部长度增加而相应缩短。
MTU:一个网络包的最大长度,以太网中一般为 1500 字节。
MSS:除去头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
另一个判断要素是时间。当应用程序发送数据的频率不高的时候,如果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长而造成发送延迟,这种情况下,即便缓冲区中的数据长度没有达到 MSS,也
应该果断发送出去。为此,协议栈的内部有一个计时器,当经过一定时间之后,就会把网络包发送出去
判断要素就是这两个,但它们其实是互相矛盾的。如果长度优先,那么网络的效率会提高,但可能会因为等待填满缓冲区而产生延迟;相反地,如果时间优先,那么延迟时间会变少,但又会降低网络的效率
协议栈给应用程序保留了控制发送时机的余地,
应用程序在发送数据时可以指定一些选项,如果指定“不等待填满缓冲区直接发送”,则协议栈就会按照要求直接发送数据。像浏览器这种会话型的应用程序在向服务器发送数据时,等待填满缓冲区导致延迟会产生很大影响,因此一般会使用直接发送的选项
HTTP 请求消息一般不会很长,一个网络包就能装得下,但如果其中要提交表单数据,长度就可能超过一个网络包所能容纳的数据量,比如在博客或者论坛上发表一篇长文就属于这种情况。
发送缓冲区中的数据就会超过 MSS 的长度,这时我们当然不需要继续等待后面的数据了。发送缓冲区中的数据会被以 MSS 长度为单位进行拆分,拆分出来的每块数据会被放进单独的网络包中。
根据发送缓冲区中的数据拆分的情况,当判断需要发送这些数据时,就在每一块数据前面加上 TCP 头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给 IP 模块来执行发送数据的操作
到这里,网络包已经装好数据并发往服务器了,但数据发送操作还没有结束。TCP 具备确认对方是否成功收到网络包,以及当对方没收到时进行重发的功能,因此在发送网络包之后,接下来还需要进行确认操作
首先,TCP 模块在拆分数据时,会先算好每一块数据相当于从头开
始的第几个字节,接下来在发送这一块数据时,将算好的字节数写在 TCP 头部中,“序号”字段就是派在这个用场上的。然后,发送数据的长度也需要告知接收方,不过这个并不是放在 TCP 头部里面的,因为用整个网络包的长度减去头部的长度就可以得到数据的长度,所以接收方可以用这种方法来进行计算。有了上面两个数值,我们就可以知道发送的数据是从第几个字节开始,长度是多少了。
接收方还能够检查收到的网络包有没有遗漏。例如,假设上次接收到第 1460 字节,那么接下来如果收到序号为 1461 的包,说明中间没有遗漏;但如果收到的包序号为 2921,那就说明中间有包遗漏了
在实际的通信中,序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,这是因为如果序号都从 1 开始,通信过程就会非常容易预测,有人会利用这一点来发动攻击。
连接过程中,有一个将 SYN 控制位设为 1 并发送给服务器的操作,就是在这一步将序号的初始值告知对方的。实际上,在将 SYN 设为 1 的
同时,还需要同时设置序号字段的值,而这里的值就代表序号的初始值
TCP 采用这样的方式确认对方是否收到了数据,在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的 ACK 号,那么就重新发送这些包。
通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,无论网络中发生任何错误,我们都可以发现并采取补救措施(重传网络包)
网卡、集线器、路由器都没有错误补偿机制,一旦检测到错误就直接丢弃相应的包。应用程序也是一样,因为采用 TCP 传输,即便发生一些错误对方最终也能够收到正确的数据,所以应用程序只管自顾自地
发送这些数据就好了。不过,如果发生网络中断、服务器宕机等问题,那么无论 TCP 怎样重传都不管用。这种情况下,无论如何尝试都是徒劳,因此 TCP 会在尝试几次重传无效之后强制结束通信,并向应用程序报错。
当网络传输繁忙时就会发生拥塞,ACK 号的返回会变慢,这时我们就必须将等待时间设置得稍微长一点,否则可能会发生已经重传了包之后,前面的 ACK 号才姗姗来迟的情况。这样的重传是多余的
TCP 采用了动态调整等待时间的方法,这个等待时间是根据 ACK 号返回所需的时间来判断的。具体来说,TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间 。
每发送一个包就等待一个 ACK 号的方式是最简单也最容易理解的,但在等待 ACK 号的这段时间中,如果什么都不做那实在太浪费了。为了减少这样的浪费
滑动窗口,就是在发送一个包之后,不等待 ACK 号返回,而是直接发送后续的一系列包。这样一来,等待 ACK 号的这段时间就被有效利用起来了
当接收方的 TCP 收到包后,会先将数据存放到接收缓冲区中。然后,接收方需要计算 ACK 号,将数据块组装起来还原成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了也不用担心,因为下一个包也会被暂存在接收缓冲区中。如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出。缓冲区溢出之后,后面的数
据就进不来了,因此接收方就收不到后面的包了,这就和中途出错的结果是一样的,也就意味着超出了接收方处理能力。
滑动窗口基本思路,首先,接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制,
滑动窗口,接收方将数据暂存到接收缓冲区中并执行接收操作。当接收操作完成后,接收缓冲区中的空间会被释放出来,也就可以接收更多的数据了,这时接收方会通过 TCP 头部中的窗口字段将自己能接收的数据量告知发送方。这样一来,发送方就不会发送过多的数据,导致超出接收方的处理能力了
这张图是为了讲解方便,故意体现一种接收方来不及处理收到的包,导致缓冲区被填满的情况。实际上,接收方在收到数据之后马上就会开始进行处理,如果接收方的性能高,处理速度比包的到达速率还快,缓冲区马上就会被清空,并通过窗口字段告知发送方
接收的最大数据量称为窗口大小 ,它是 TCP 调优参数中非常有名的一个
当接收方将数据传递给应用程序,导致接收缓冲区剩余容量增加时,就需要告知发送方,这就是更新窗口大小的时机
接收方在发送 ACK 号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作,这样就可以把两种通知合并在一个包里面发送了
举个例子,在等待发送 ACK 号的时候正好需要更新窗口,这时就可以把 ACK 号和窗口更新放在一个包里发送,从而减少包的数量。当需要连续发送多个 ACK 号时,也可以减少包的数量,这是因为 ACK 号表示的是已收到的数据量,也就是说,它是告诉发送方目前已接收的数据的最后位置在哪里,因此当需要连续发送 ACK 号时,只要发送最后一个 ACK 号就可以了,中间的可以全部省略
当需要连续发送多个窗口更新时也可以减少包的数量,因为连续发生窗口更新说明应用程序连续请求了数据,接收缓冲区的剩余空间连续增加。这种情况和ACK 号一样,可以省略中间过程,只要发送最终的结果就可以了
发送 HTTP 请求消息后,接下来还需要等待 Web 服务器返回响应消息。对于响应消息,浏览器需要进行接收操作,这一操作也需要协议栈的参与。
浏览器在委托协议栈发送请求消息之后,会调用 read 程序来获取响应消息。然后,控制流程会通过 read 转移到协议栈 ,然后协议栈会执行接下来的操作。和发送数据一样,接收数据也需要将数据暂存到接收缓冲区中,这里的操作过程如下。
首先,协议栈尝试从接收缓冲区中取出数据并传递给应用程序,但这个时候请求消息刚刚发送出去,响应消息可能还没返回。
响应消息的返回还需要等待一段时间,因此这时接收缓冲区中并没有数据,那么接收数据的操作也就无法继续。
这时,协议栈会将应用程序的委托,也就是从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起 ,等服务器返回的响应消息到达之后再继续执行接收操作
收发数据结束的时间点应该是应用程序判断所有数据都已经发送完毕的时候。这时,数据发送完毕的一方会发起断开过程,但不同的应用程序会选择不同的断开时机。
以 Web 为例,浏览器向 Web 服务器发送请求消息,Web 服务器再返回响应消息,这时收发数据的过程就全部结束了,服务器一方会发起断开过程 。当然,可能也有一些程序是客户端发送完数据就结束了,不用等服务器响应,这时客户端会先发起断开过程。这一判断是应用程序作出的,协议栈在设计上允许任何一方先发起断开过程。
假设客户端计算机是连接到家庭或公司的局域网中,然后再通过 ADSL 和光纤到户(FTTH)等宽带线路接入互联网
网卡发送的包会经过交换机等设备,到达用来接入互联网的路由器。
路由器的后面就是互联网,网络运营商会负责将包送到目的地,
就好像我们把信投到邮筒中之后,邮递员会负责把信送给收件人一样
完成数据发送的一方会发起断开过程,这里我们以服务器一方发起断开过程为例来进行讲解。
1)首先,服务器一方的应用程序会调用 Socket 库的 close 程序。然后,服务器的协议栈会生成包含断开信息的 TCP 头部,具体来说就是将控制位中的 FIN 比特设为 1。协议栈会委托 IP 模块向客户端发送数据(①),同时,服务器的套接字中也会记录下断开操作的相关信息
2)客户端收到服务器发来的 FIN 为 1 的 TCP 头部时,客户端的协议栈会将自己的套接字标记为进入断开操作状态。为了告知服务器已收到 FIN 为 1 的包,客户端会向服务器返回一个 ACK 号(②),这些操作完成后,协议栈就可以等待应用程序来取数据了
应用程序就会调用 read 来读取数据,协议栈不会向应用程序传递数据 ,而是会告知应用程序(浏览器)来自服务器的数据已经全部收到了。根据规则,服务器返回请求之后,Web 通信操作就全部结束了,因此只要收到服务器返回的所有数据,客户端的操作也就随之结束了。
3)客户端应用程序会调用 close 来结束数据收发操作,这时客户端的协议栈也会和服务器一样,生成一个 FIN 比特为 1 的TCP 包,然后委托 IP 模块发送给服务器(③)
4)一段时间之后,服务器就会返回 ACK 号(④)到这里,客户端和服务器的通信就全部结束了。
和服务器的通信结束之后,用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。
如果则断开的操作顺序如下:
(1)客户端发送 FIN
(2)服务器返回 ACK 号
(3)服务器发送 FIN
(4)客户端返回 ACK 号
如果最后客户端返回的 ACK 号丢失了,结果会如何呢?这时,服务器没有接收到 ACK 号,可能会重发一次 FIN。如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。
这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号 ,而服务器重发的 FIN 正好到达,会怎么样呢?本来这个 FIN 是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个 FIN 就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。之所以不马上删除套接字,就是为了防止这样的误操作。一般来说会等待几分钟之后再删除套接字。
客户端的端口号是从空闲的端口号中随意选择的。
创建套接字之后,客户端会向服务器发起连接操作,三次握手
1)客户端会生成一个 SYN 为 1 的 TCP 包并发送给服务器(①)
这个 TCP 包的头部还包含了客户端向服务器发送数据时使用的初始序号,以及服务器向客户端发送数据时需要用到的窗口大小
2)当这个包到达服务器之后,服务器会返回一个 SYN 为 1 的TCP 包(②)。和 ①一样,这个包的头部中也包含了序号和窗口大小,此外还包含表示确认已收到包①的 ACK 号
3)当这个包到达客户端时,客户端会向服务器返回一个包含表示确认的 ACK 号的TCP 包(③)。到这里,连接操作就完成了,双方进入数据收发阶段
数据收发阶段的操作根据应用程序的不同而有些差异,以 Web 为例
客户端向服务器发送请求消息时
1)TCP 会将请求消息切分成一定大小的块,并在每一块前面加上 TCP 头部,然后发送给服务器(④)。TCP 头部中包含序号,它表示当前发送的是第几个字节的数据。
2)当服务器收到数据时,会向客户端返回 ACK 号( ⑤)。在最初的阶段,服务器只是不断接收数据,随着数据收发的进行,数据不断传
递给应用程序,接收缓冲区就会被逐步释放。这时,服务器需要将新的窗口大小告知客户端。
当服务器收到客户端的请求消息后,会向客户端返回响应消息,这个过程和刚才的过程正好相反(⑥⑦)
服务器的响应消息发送完毕之后,数据收发操作就结束了,这时就会开始执行断开操作。以 Web 为例,四次挥手
1)服务器会先发起断开过程 。在这个过程中,服务器先发送一个 FIN 为 1 的 TCP 包( ⑧)
2)然后客户端返回一个表示确认收到的 ACK 号( ⑨)
3)接下来,双方还会交换一组方向相反的 FIN 为 1 的TCP 包(⑩)
4和包含 ACK 号的 TCP 包(序号11)
最后,在等待一段时间后,套接字会被删除
TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成包发送给通信对象。
包是由头部和数据两部分构成的。头部包含目的地址等控制信息,大家可以把它理解为快递包裹的面单;头部后面就是委托方要发送给对方的数据,也就相当于快递包裹里的货物。
发送方的网络设备会负责创建包,创建包的过程就是生成含有正确控制信息的头部,然后再附加上要发送的数据。接下来,包会被发往最近的网络转发设备
经过多个转发设备的接力之后,包最终就会到达接收方的网络设备。当然,发送方向接收方发送一个包,接收方可能也会向发送方返回一个包,此时的发送方到了接下来的某个时刻就会变成接收方。
因此,我们不需要把发送方和接收方明确区分开来,在这里我们把发送方和接收方统称为终端节点 。
路由器根据目标地址判断下一个路由器的位置
集线器在子网中将网络包传输到下一个路由
集线器是按照以太网规则传输包的设备,而路由器是按照 IP 规则传输包的设备
1)IP 协议根据目标地址判断下一个 IP 转发设备的位置
2)子网中的以太网协议将包传输到下一个转发设备
TCP/IP 包 包含如下两个头部
a)MAC 头部(用于以太网协议)
b)IP 头部(用于 IP 协议)
网络包在传输过程中( ①)会经过集线器,集线器是根据以太网协议工作的设备。为了判断包接下来应该向什么地方传输,集线器里有一张表(用于以太网协议的表),可根据以太网头部中记录的目的地信
息查出相应的传输方向。这张图中只有一个集线器,当存在多个集线器时,网络包会按顺序逐一通过这些集线器进行传输。
2)包会到达下一个路由器(②)。路由器中有一张 IP 协议的表,可根据这张表以及 IP 头部中记录的目的地信息查出接下来应该发往哪个路由器。为了将包发到下一个路由器,我们还需要查出下一个路由器的 MAC 地址,并记录到 MAC 头部中,大家可以理解为改写了 MAC 头部 。这样,网络包就又被发往下一个节点了。
3)网络包会通过路由器到达下一个路由器 R2。这个过程不断重复,最终网络包就会被送到目的地,当目的地设备成功接收之后,网络包的传输过程就结束了。
以太网的部分也可以替换成其他的东西,例如无线局域、ADSL、FTTH 等,它们都可以替代以太网的角色帮助 IP 协议来传输网络包
包收发操作的起点是 TCP 模块委托 IP 模块发送包的操作(①)。这个委托的过程就是TCP 模块在数据块的前面加上 TCP 头部,然后整个传递给 IP 模块,这部分就是网络包的内容。与此同时,TCP 模块还需要指定通信对象的 IP 地址,也就是需要写清楚“将什么内容发给谁”。
收到委托后,IP 模块会将包的内容当作一整块数据,在前面加上包含控制信息的头部。IP模块会添加 IP 头部和 MAC 头部这两种头部。
IP 头部中包含 IP 协议规定的、根据 IP 地址将包发往目的地所需的控制信息;MAC 头部包含通过以太网的局域网将包传输至最近的路由器所需的控制信息 。加上这两个头部之后,一个包就封装好了
IP 模块负责添加如下两个头部。
(1)MAC 头部:以太网用的头部,包含 MAC 地址
(2)IP 头部:IP 用的头部,包含 IP 地址
接下来,封装好的包会被交给网络硬件(②),例如以太网、无线局域网等。网络硬件可能是插在计算机主板上的板卡,也可能是笔记本电脑上的 PCMCIA 卡,或者是计算机主板上集成的芯片,不同形态的硬件名字也不一样,本书将它们统称为网卡 。
传递给网卡的网络包是由一连串 0 和 1 组成的数字信息,网卡会将这些数字信息转换为电信号或光信号,并通过网线(或光纤)发送出去,然后这些信号就会到达集线器、路由器等转发设备,再由转发设备一步一步地送达接收方
包送达对方之后,对方会作出响应。返回的包也会通过转发设备发送回来,然后我们需要接收这个包。接收的过程和发送的过程是相反的,信息先以电信号的形式从网线传输进来,然后由网卡将其转换为数字信息并传递给 IP 模块(③接收)。接下来,IP 模块会将 MAC 头部和 IP 头部后面的内容,也就是TCP 头部加上数据块,传递给 TCP 模块。
TCP 模块在收发数据时会分为好几个阶段,并为各个阶段设计了实现相应功能的网络包,但 IP 的包收发操作都是相同的,并不会因包本身而有所区别。因为 IP 模块会将 TCP 头部和数据块看作一整块二进制数据,在执行收发操作时并不关心其中的内容,也不关心这个包是包含 TCP头部和数据两者都有呢,还是只有 TCP 头部而没有数据。当然,IP 模块也不关心 TCP 的操作阶段,对于包的乱序和丢失也一概不知。总之,IP 的职责就是将委托的东西打包送到对方手里,或者是将对方送来的包接收下来
也就是 Interface 列,表示网卡等网络接口,这些网络接口可以将包发送给通信对象。
Gateway 列表示下一个路由器的 IP 地址,将包发给这个 IP 地址,该地址对应的路由器 就会将包转发到目标地址 。
路由表的第 1 行中,目标地址和子网掩码 都是 0.0.0.0,这表示默认网关,如果其他所有条目都无法匹配,就会自动匹配这一行
IP 头部中的接收方IP 地址表示网络包的目的地,通过这个地址我们就可以判断要将包发到哪里,但在以太网的世界中,TCP/IP 的这个思路是行不通的。以太网在判断网络包目的地时和 TCP/IP 的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而 MAC 头部就是干这个用的。
IP 地址的长度为 32 比特,而 MAC 地址为 48 比特
MAC 地址是在网卡生产时写入 ROM 里的
使用 ARP(地址解析协议) ,它其实非常简单。在以太网中,有一种叫作广播的方法,可以把包发给连接在同一以太网中的所有设备。ARP 就是利用广播对所有设备提问:“×× 这个 IP 地址是谁的?请把你的 MAC地址告诉我。”然后就会有人回答:“这个 IP 地址是我的,我的 MAC 地址是 ××××。”
不过,如果每次发送包都要这样查询一次,网络中就会增加很多 ARP 包,因此我们会将查询结果放到一块叫作 ARP 缓存的内存空间中留着以后用。也就是说,在发送包时,先查询一下 ARP 缓存,如果其中已经保存了对方的 MAC 地址,就不需要发送 ARP 查询,直接使用 ARP 缓存中的地址,而当 ARP 缓存中不存在对方 MAC 地址时,则发送 ARP 查询。
以太网基本的 3 个性质至今仍未改变
1)将包发送到 MAC 头部的接收方 MAC地址代表的目的地
2)用发送方 MAC 地址识别发送方
3)用以太类型识别包的内容
IP 生成的网络包只是存放在内存中的一串数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电或光信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。
负责执行这一操作的是网卡,但网卡也无法单独工作,要控制网卡还需要网卡驱动程序。
在操作系统启动并完成初始化操作之后,网卡就可以等待来
自 IP 的委托了。
网卡是如何将包转换成电信号并发送到网线中的。网卡驱动从 IP 模块获取包之后,会将其复制到网卡内的缓冲区中,然后向 MAC 模块发送 发送包的命令。接下来就轮到 MAC 模块进行工作了。
首先,MAC 模块会将包从缓冲区中取出,并在开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧(包)校验序列
报头是一串像 10101010…这样 1 和 0 交替出现的比特序列,长度为 56 比特,它的作用是确定包的读取时机。当这些 1010 的比特序列被转换成电信号后,会形成波形。
用电信号来表达数字信息时,我们需要让 0 和 1 两种比特分别对应特定的电压和电流,电信号就可以表达数字信息。通过电信号来读取数据的过程就是将这种对应关系颠倒过来。也就是说,通过测量信号中的电压和电流变化,还原出 0 和 1 两种比特的值。
末尾的 FCS(帧校验序列)用来检查包传输过程中因噪声导致的波形紊乱、数据错误
发送和接收同时并行的方式叫作“全双工”,相对地,某一时刻只能进行发送或接收其中一种操作的叫作“半双工”
将数字信息转换为电信号的速率就是网络的传输速率
如果有其他设备同时发送信号,这些信号就会通过接收线路传进来
在使用集线器的半双工模式中,一旦发生这种情况,两组信号就会相互叠加,无法彼此区分出来,这就是所谓的信号碰撞。这种情况下,继续发送信号是没有意义的,因此发送操作会终止。为了通知其他设备当前线路已发生碰撞,还会发送一段时间的阻塞信号 ,然后所有的发送操作会全部停止
等待一段时间之后,网络中的设备会尝试重新发送信号。但如果所有设备的等待时间都相同,那肯定还会发生碰撞,因此必须让等待的时间相互错开。具体来说,等待时间是根据 MAC 地址生成一个随机数计算出来的。
当网络拥塞时,发生碰撞的可能性就会提高,重试发送的时候可能又会和另外一台设备的发送操作冲突,这时会将等待时间延长一倍,然后再次重试。以此类推,每次发生碰撞就将等待时间延长一倍,最多重试 10次,如果还是不行就报告通信错误。
如果 FCS 校验没有问题,接下来就要看一下 MAC 头部中接收方 MAC 地址与网卡在初始化时分配给自己的MAC 地址是否一致,以判断这个包是不是发给自己的。我们没必要去接收发给别人的包,因此如果不是自己的包就直接丢弃,如果接收方 MAC 地址和自己 MAC 地址一致,则将包放入缓冲区中 。到这里,MAC 模块的工作就完成了,接下来网卡会通知计算机收到了一个包。
通知计算机的操作会使用一个叫作中断的机制。在网卡执行接收包的操作的过程中,计算机并不是一直监控着网卡的活动,而是去继续执行其他的任务。因此,如果网卡不通知计算机,计算机是不知道包已经收到了这件事的。
中断的工作过程是这样的。首先,网卡向扩展总线中的中断信号线发送信号,该信号线通过计算机中的中断控制器连接到 CPU。当产生中断信号时,CPU 会暂时挂起正在处理的任务,切换到操作系统中的中断处理程序 。然后,中断处理程序会调用网卡驱动,控制网卡执行相应的接收操作
中断是有编号的,网卡在安装的时候就在硬件中设置了中断号,在中断处理程序中则将硬件的中断号和相应的驱动程序绑定
如果接收方 IP 地址不是自己的地址,那一定是发生了什么错误。客户端计算机不负责对包进行转发,因此不应该收到不是发给自己的包 。当发生这样的错误时,IP 模块会通过 ICMP 消息将错误告知发送方
服务器的操作系统具备和路由器相同的包转发功能
如果接收方 IP 地址正确,则这个包会被接收下来。
IP 协议有一个叫作分片的功能,网线和局域网中只能传输小包,因此
需要将大的包切分成多个小包。如果接收到的包是经过分片的,那么 IP 模块会将它们还原成原始的包。分片的包会在 IP 头部的标志字段中进行标记,当收到分片的包时,IP 模块会将其暂存在内部的内存空间中,然后等待 IP 头部中具有相同 ID 的包全部到达,这是因为同一个包的所有分片都具有相同的 ID。此外,IP头部还有一个分片偏移量字段,它表示当前分片在整个包中所处的位置。根据这些信息,在所有分片全部收到之后,就可以将它们还原成原始的包,这个操作叫作分片重组
IP 模块工作结束,接下来包会被交给 TCP 模块。TCP 模块会根据 IP 头部中的接收方和发送方 IP 地址,以及 TCP 头部中的接收方和发送方端口号来查找对应的套接字 。找到对应的套接字之后,就可以根据套接字中记录的通信状态,执行相应的操作了。
例如,如果包的内容是应用程序数据,则返回确认接收的包,并将数据放入缓冲区,等待应用程序来读取;如果是建立或断开连接的控制包,则返回相应的响应控制包,并告知应用程序建立和断开连接的操作状态
向 DNS 服务器查询 IP 地址的时候我们用的是 UDP 协议
最简单的方法是数据全部发送完毕之后让接收方返回一个接收确认。这样一来,如果没收到直接全部重新发送一遍就好了,根本不用像 TCP 一样要管理发送和确认的进度
UDP 没有 TCP 的接收确认、窗口等机制,因此在收发数据之前也不需要交换控制信息,也就是说不需要建立和断开连接的步骤,只要在从应用程序获取的数据前面加上UDP 头部,然后交给 IP 进行发送就可以了
接收也很简单,只要根据 IP 头部中的接收方和发送方 IP 地址,以及 UDP 头部中的接收方和发送方端口号,找到相应的套接字并将数据交给相应的应用程序就可以了
UDP 协议没有其他功能了,遇到错误或者丢包也一概不管。因为 UDP 只负责单纯地发送包而已,并不像 TCP 一样会对包的送达状态进行监控
音频和视频数据必须在规定的时间内送达,一旦送达晚了,就会错过播放时机,导致声音和图像卡顿
在这些无需重发数据,或者是重发了也没什么意义的情况下,使用 UDP 发送数据的效率会更高
假设我们有一段程序,把它“咔”一下插到一个套接字里,于是我们就可以开始通信了,就跟灯泡插进去就亮一样
套接字的背后就是传输数据的通道,这个通道和我们的通信对象是相连接的,就像流过电线的电流一样,数据就在这个通道中流动,所以我们插进去一个程序,就可以和对方通信了,
1)表示网络包收件人的接收方 IP 地址位于 IP 头部
2)端口号用来指定服务器程序的种类位于 TCP 头部
3)会对包是否正确送达进行确认的是 TCP
4) 根据 IP 地址查询 MAC 地址的机制是ARP
5)在收到 ACK 号之前继续发送下一个包的方式叫滑动窗口
集线器:信号从计算机中流出之后,会在网线中经过集线器等设备前进
交换机:并不只是简单地让信号流过,而是先接收信号并将其还原为数字信息,然后再重新转换成信号并发送出去的过程
路由器:和交换机一样也负责对包进行转发,但工作方式有一些差异。交换机是基于以太网规格工作的设备,而路由器是基于 IP 工作的
所有的包在传输到目的地的过程中都是独立的,相互之间没有任何关联。
即便线路条件很好,没有噪声,信号在传输过程中依然会发生失真,如果再加上噪声的影响,失真就会更厉害。噪声根据强度和类型会产生不同的影响,无法一概而论,但如果本来就已经衰减的信号再进一步失真,就会出现对 0 和 1 的误判,这就是产生通信错误的原因
局域网网线使用的是双绞线,其中“双绞”的意思就是以两根信号线为一组缠绕在一起,这种拧麻花一样的设计是为了抑制噪声的影响。
产生噪声的原因是网线周围的电磁波,当电磁波接触到金属等导体时,在其中就会产生电流。因此,如果网线周围存在电磁波,就会在网线中产生和原本的信号不同的电流
当信号到达集线器后,会被广播到整个网络中。以太网的基本架构就是将包发到所有的设备,然后由设备根据接收方 MAC 地址来判断应该接收哪些包,而集线器就是这一架构的忠实体现,它就是负责按照以太网的基本架构将信号广播出去。
交换机会自行更新或删除地址表中的记录,不需要手动维护 。当地址表的内容出现异常时,只要重启一下交换机就可以重置地址表,也不需要手动进行维护。
首先,计算机 A 发送的包到达集线器后会被集线器转发到所有
端口上,也就是会到达交换机和计算机 B(①)。这时,交换机转发这个包之后,这个包会原路返回集线器(②),然后,集线器又把包转发到所有端口,于是这个包又到达了计算机 A 和计算机 B。所以计算机 B 就会收到两个相同的包,这会导致无法正常通信。因此,当交换机发现一个包要发回到原端口时,就会直接丢弃这个包。
全双工模式是交换机特有的工作模式,它可以同时进行发送和接收操作,集线器不具备这样的特性。
相互连接的双方探测对方是否支持全双工模式,并自动切换成相应的工作模式,除此之外,还能探测对方的传输速率并进行自动切换。这种自动切换的功能称为自动协商
交换机只将包转发到具有特定 MAC 地址的设备连接的端口,其他端口都是空闲的,这些端口可以传输其他的包,因此交换机可以同时转发多个包
网络包经过集线器和交换机之后,现在到达了路由器,并在此被转发到下一个路由器。
路由器的各个端口都具有 MAC 地址和 IP 地址。
路由器在转发包时,首先会通过端口将发过来的包接收进来,这一步的工作过程取决于端口对应的通信技术。对于以太网端口来说,就是按照以太网规范进行工作,而无线局域网端口则按照无线局域网的规范工作,总之就是委托端口的硬件将包接收进来。接下来,转发模块会根据接收到的包的 IP 头部中记录的接收方 IP 地址,在路由表中进行查询,以此判断转发目标。然后,转发模块将包转移到转发目标对应的端口,端口再按照硬件的规则将包发送出去,也就是转发模块委托端口模块将包发送出去。
目标地址:记录的是接收方的信息。实际上这里的 IP 地址只包含表示子网的网络号部分的比特值,而表示主机号部分的比特值全部为 0 。
路由器会将接收到的网络包的接收方IP 地址与路由表中的目标地址进行比较,并找到相应的记录。交换机在地址表中只匹配完全一致的记录,而路由器则会忽略主机号部分,只匹配网络号部分。打个比方,路由器在转发包的时候只看接收方地址属于哪个区,××区发往这一边,××区发往那一边。
子网掩码:通过这个值就可以判断出网络号的比特数
网关和接口两列,它们表示网络包的转发目标。根据目标地址和子网掩码匹配到某条记录后,路由器就会将网络包交给接口列中指定的网络接口 ,并转发到网关列中指定的 IP 地址
跃点计数,它表示距离目标 IP 地址的距离是远还是近。这个数字越小,表示距离目的地越近;数字越大,表示距离目的地越远
路由聚合会将几个子网合并成一个子网,并在路由表中只产生一条记录
对路由表进行维护的方法有几种,大体上可分为以下两类。
(a)由人手动维护路由记录
(b)根据路由协议机制,通过路由器之间的信息交换由路由器自行维护路由表的记录
首先,信号到达网线接口部分,其中的 PHY(MAU)模块和 MAC 模块将信号转换为数字信息,然后通过包末尾的 FCS 进行错误校验,如果没问题则检查 MAC 头部中的接收方 MAC 地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包。
路由器的端口都具有 MAC 地址,只接收与自身地址匹配的包,遇到不匹配的包则直接丢弃。
通过路由器转发的网络包,其接收方 MAC 地址为路由器端口的 MAC 地址。
路由器会根据 MAC 头部后方的 IP 头部中的内容进行包的转发操作。首先是查询路由表判断转发目标。假设地址为 10.10.1.101 的计算机要向地址为 192.168.1.10 的服务器发送一个包,这个包先到达图中的路由器。判断转发目标的第一步,就是根据包的接收方 IP 地址查询路由表中的目标地址栏,以找到相匹配的记
录。这个匹配并不是匹配全部 32 个比特,而是根据子网掩码列中的值判断网络号的比特数,并匹配相应数量的比特 。子网掩码列为 255.255.255.0,就表示需要匹配从左起 24 个比特。网络包的接收方 IP 地址和路由表中的目标地址左起 24 个比特的内容都是 192.168.1,因此两者是匹配的,该行记录就是候选转发目标之一。
相比服务器所属的子网来说,直接指定服务器本身的地址时范围更小,因此这里应该选择第 4 行作为转发目标。按照最长匹配原则筛选后,如果只剩一条候选记录,则按照这条记录的内容进行转发。
有时候路由表中会存在网络号长度相同的多条记录,例如考虑到路由器或网线的故障而设置的备用路由就属于这种情况。这时,需要根据跃点计数的值来进行判断。跃点计数越小说明该路由越近,因此应选择跃点计数较小的记录
路由表中子网掩码为 0.0.0.0 的记录表示“默认路由”
从路由表中查找到转发目标之后,网络包就会被转交给输出端口,并最终发送出去,但在此之前,路由器要更新 IP 头部中的 TTL(Timeto Live,生存时间)字段,表示包的有效期,包每经过一个路由器的转发,这个值就会减 1,当这个值变成 0 时,就表示超过了有效期,这个包就会被丢弃。
TCP 拆分数据的操作是在将数据装到包里之前进行的,换句话说,拆分好的一个数据块正好装进一个包里。
从 IP 分片的角度来看,这样一个包其实是一个未拆分的整体,也就是说,分片是对一个完整的包再进行拆分的过程
MTU:一个包能传输的最大数据长度
路由器判断下一个转发目标的方法如下。
如果路由表的网关列内容为 IP 地址,则该地址就是下一个转发目标。
如果路由表的网关列内容为空,则 IP 头部中的接收方 IP 地址就是下一个转发目标。
路由器也会使用 ARP 来查询下一个转发目标的 MAC 地址
网络包完成后,接下来会将其转换成电信号并通过端口发送出去。这一步的工作过程和计算机也是相同的。
当以太网工作在半双工模式时,需要先确认线路中没有其他信号后才能发送,如果检测到碰撞,则需要等待一段时间后重发。
如果以太网工作在全双工模式,则不需要确认线路中的信号,可以直接发送。
IP(路由器)负责将包发送给通信对象这一整体过程,而其中将包传输到下一个路由器的过程则是由以太网(交换机)来负责的
IP 本身不负责包的传输,而是委托各种通信技术将包传输到下一个路由器
当公司内网和互联网连接的时候,即将公司内网分成两个部分,一部分是对互联网开放的服务器,另一部分是公司内部设备。其中对互联网开放的部分分配公有地址,可以和互联网直接进行通信,这一部分和之前介绍的内容是一样的。
相对地,内网部分则分配私有地址,内网中的设备不能和互联网直接收发网络包,而是通过一种特别的机制进行连接,这个机制就叫地址转换。
私有地址->地址转换设备->公有地址->访问服务器->返回->
公有地址->地址转换设备->私有地址
用公有地址加上端口的组合对应一个私有地址,一个公有地址就可以对应几万个私有地址,这种方法提高了公有地址的利用率。
除非公司主动允许,否则是无法从互联网向公司内网发送网络包的。这种机制具有防止非法入侵的效果。
之所以无法从互联网访问内网,是因为对应表里没有相应的记录,那么我们只要事先手动添加这样的记录就可以了,用于外网访问的服务器可以放在地址转换设备的外面并为它分配一个公有地址,也可以将服务器的私有地址手动添加到地址转换设备中,这样就可以从互联网访问到这台具有私有地址的服务器了
包过滤就是在对包进行转发时,根据 MAC 头部、IP 头部、TCP 头部的内容,按照事先设置好的规则决定是转发这个包,还是丢弃这个包。我们通常说的防火墙设备或软件,大多数都是利用这一机制来防止非法入侵的
互联网的入口线路称为接入网
接入网连接到签约的网络运营商,并接入被称为接入点的设备
接入点的实体是一台专为运营商设计的路由器,我们可以把它理解为离你家最近的邮局
从各个邮筒中收集来的信件会在邮局进行分拣,然后被送往全国甚至全世界
互联网也是一样,网络包首先通过接入网被发送到接入点
然后再从这里被发送到全国甚至全世界。接入点的后面就是互联网的骨干部分了
在骨干网中存在很多运营商和大量的路由器,这些路由器相互连接,组成一张巨大的网
而我们的网络包就在其中经过若干路由器的接力,最终被发送到目标Web服务器上
以太网采用的是用方波信号表示 0 和 1 的方式,
同样是将数字信息转换成模拟信号ADSL 采用的方法要复杂一些
一个原因是方波信号的波形容易失真,随着距离的延长错误率也会提高;另一个原因是方波信号覆盖了从低频到高频的宽广频段,信号频率越高,辐射出来的电磁噪声就越强,因此信号频谱太宽就难以控制噪声
ADSL Modem 采用了一种用圆滑波形(正弦波)对信号进行合成来表示 0 和 1 的技术,这种技术称为调制。调制有很多方式,ADSL 采用的调制方式是振幅调制(ASK)和相位调制(PSK)相结合的正交振幅调制(QAM) 方式。
不过,数字信息并不能一下子变成光信号,先将数字信息转换成电信号,然后再将电信号转换成光信号。
这里的电信号非常简单,1 用高电压表示,0 用低电压表示。
将这样的电信号输入 LED、激光二极管等光源后,这些光源就会根据信号电压的变化发光,高电压发光亮,低电压发光暗。
这样的光信号在光纤中传导之后,就可以通过光纤到达接收端。接收端有可以感应光线的光敏元件,光敏元件可以根据光的亮度产生不同的电压。当光信号照射到上面时,光亮的时候就产生高电压,光暗的时候就产生低电压,这样就将光信号转换成了电信号。最后再将电信号转换成数字信息,我们就接收到数据了。
在 TCP 连接中,我们从一侧的出口(套接字)放入数据,数据就会原封不动地从另一个出口出来,隧道也是如此。也就是说,我们将包含头部在内的整个包从隧道的一头扔进去,这个包就会原封不动地从隧道的另一头出来,就好像在网络中挖了一条地道,网络包从这个地道里穿过去一样。
如果在 BAS 和运营商路由器之间的 ADSL/FTTH 接入服务商的网络中建立一条隧道,将用户到BAS 的接入网连接起来,就形成了一条从用户一直到运营商路由器的通道,网络包通过这条通道,就可以进入互联网内部了,这样的机制就类似于将接入网一直延伸到运营商路由器。
DHCP (动态主机配置协议)
经常用于通过公司网络向客户端计算机下发 TCP/IP 配置信息,
首先客户端请求配置信息(图 4.22 ①)
然后 DHCP 服务器下发配置信息(②),
不需要像 PPP那样需要多个步骤,也不需要验证用户名和密码。没有用户名和密码,就意味着无法通过用户名来切换运营商网络,但这种方式也有优势,它可以单纯地直接传输以太网包,不需要添加额外的 PPP 头部,因此不会占用 MTU。
通过骨干网之后,网络包最终到达了 Web 服务器所在的局域网中
,防火墙会对进入的包进行检查
大家可以把防火墙想象成门口的保安,他会检查所有进入的包,看看有没有危险的包混在里面
网络包接下来可能还会遇到缓存服务器。网页数据中有一部分是可以重复利用的
这些可以重复利用的数据就被保存在缓存服务器中
如果要访问的网页数据正好在缓存服务器中能够找到,那么就可以不用劳烦 Web 服务器,直接从缓存服务器读出数据
在大型网站中,可能还会配备将消息分布到多台 Web服务器上的负载均衡器,还有可能会使用通过分布在整个互联网中的缓存服务器来分发内容的服务
经过这些机制之后,网络包才会到达 Web 服务器
防火墙的作用类似于海关,它只允许发往指定服务器的指定应用程序的网络包通过,从而屏蔽其他不允许通过的包。
即便应用程序存在安全漏洞,也可以降低相应的风险。因为防火墙屏蔽了不允许从外部访问的应用程序,所以即便这些程序存在安全漏洞,用于攻击的网络包也进不来 。
即便如此风险也不会降到零,因为如果允许外部访问的应用程序中有安全漏洞,还是有可能遭到攻击的 ,但怎么说也远比完全暴露安全漏洞的风险要低得多。这就是防火墙的作用。
路由器在对包进行转发时会改写 MAC 地址,将转发目标路由器的 MAC 地址设为接收方 MAC 地址,将自己的MAC 地址设为发送方 MAC 地址。通过发送方 MAC 地址,可以知道上一个转发路由器的 MAC 地址
包过滤方式的防火墙可根据接收方 IP 地址、发送方 IP 地址、接收方端口号、发送方端口号、控制位等信息来判断是否允许某个包通过。
防火墙可以根据包的起点和终点来判断是否允许其通过,但仅凭起点和终点并不能筛选出所有有风险的包。假设 Web 服务器在收到含有特定数据的包时会引起宕机。防火墙只关心包的起点和终点,即便包中含有特定数据,防火墙也无法发现,于是包就被放行了,当包到达 Web 服务器时,就会引发服务器宕机。只有检查包的内容才能识别这种风险,因此防火墙对这种情况无能为力。
使用多台服务器来分担负载的方法,这种架构统称为分布式架构
必须有一个机制将客户端发送的请求分配到每台服务器上。最简单的一种是通过 DNS 服务器来分配。当访问服务器时,客户端需要先向 DNS 服务器查询服务器的 IP 地址,如果在 DNS 服务器中填写多个名称相同的记录,则每次查询时DNS 服务器都会按顺序返回不同的 IP 地址。
但这种方式是有缺点的。假如多台 Web 服务器中有一台出现了故障,这时我们希望在返回 IP 地址时能够跳过故障的 Web 服务器,然而普通的 DNS 服务器并不能确认 Web 服务器是否正常工作,因此即便 Web 服务器宕机了,它依然可能会返回这台服务器的 IP 地址 。
轮询分配还可能会引发一些问题。在通过 CGI 等方式动态生成网页的情况下,有些操作是要跨多个页面的,如果这期间访问的服务器发生了变化,这个操作就可能无法继续。例如在购物网站中,可能会在第一个页面中输入地址和姓名,在第二个页面中输入信用卡号,这就属于刚才说的那种情况
为了避免出现前面的问题,可以使用一种叫作负载均衡器的设备。使用负载均衡器时,首先要用负载均衡器的 IP 地址代替 Web 服务器的实际地址注册到 DNS 服务器上。假设有一个域名 www.lab.glasscom.com,我们将这个域名对应的 IP 地址设置为负载均衡器的 IP 地址并注册到 DNS 服务器上。客户端会认为负载均衡器就是一台 Web 服务器,并向其发送请求,然后由负载均衡器来判断将请求转发给哪台 Web 服务器。
使用多台功能相同的 Web 服务器时,避免负载集中在某一台服务器上
1)负载均衡器可以定期采集 Web 服务器的 CPU、内存使用率,并根据这些数据判断服务器的负载状况,如果过于密集地去查询服务器的负载,这个查询操作本身就会增加 Web 服务器的负载。
2)根据事先设置的服务器性能指数,按比例来分配请求
3)可以在发送表单数据时在里面加上用来表示关联的信息,或者对 HTTP 规格进行扩展,在 HTTP 头部字段中加上用来判断相关性的信息(Cookie) 。负载均衡器就可以通过这些信息来作出判断,将一系列相关的请求发送到同一台 Web 服务器,对于不相关的请求则发送到负载较低的服务器了。
除了使用多台功能不同的 Web 服务器分担负载
将整个系统按功能分成不同的服务器 ,如 Web 服务器、数据库服务器。缓存服务器就是一种按功能来分担负载的方法。
缓存服务器是一台通过代理机制对数据进行缓存的服务器。代理介于 Web 服务器和客户端之间,具有对Web 服务器访问进行中转的功能。当进行中转时,它可以将 Web 服务器返回的数据保存在磁盘中,并可以代替 Web 服务器将磁盘中的数据返回给客户端。这种保存的数据称为缓存,缓存服务器指的也就是这样的
功能。
Web 服务器需要执行检查网址和访问权限,以及在页面上填充数据等内部操作过程,因此将页面数据返回客户端所需的时间较长。相对地,缓存服务器只要将保存在磁盘上的数据读取出来发送给客户端就可以了,因此可以比 Web 服务器更快地返回数据。
这种在客户端和 Web 服务器之间充当中间人的方式就是代理的基本原理
代理本来的意思并不是“转发”消息,而是先把消息收下来,然后“伪装”成原始客户端向 Web 服务器发出访问请求。
正向代理转发消息的过程也和服务器端的缓存服务器有一些不同,不同点在于对转发目标 Web 服务器的判断上。使用正向代理时,URI 部分为 http://… 这样的完整网址,因此可以根据这个网址来转发,不需要像服务器端的缓存服务器一样实现设置好转发目标 Web 服务器,而且可以发给任意 Web 服务器。而服务器端的缓存服务器只能向事先设置好的目标进行转发,这就是两者不同的地方。
的使用正向代理需要在浏览器中进行设置,这可以说是识别正向代理的一个特征。设置浏览器非常麻烦,如果设置错误还可能导致浏览器无法正常工作。
如果我们想把代理放在服务器端,那么服务器不知道谁会来访问,也没办法去设置客户端的浏览器,因此无法采用这种方法来实现。
改良:不需要在浏览器中设置代理也可以使用。可以通过将请求消息中的 URI 中的目录名与 Web 服务器进行关联,使得代理能够转发一般的不包含完整网址的请求消息。我们前面介绍的服务器端的缓存服务器采用的正是这种方式,这种方式称为反向代理。
透明代理:缓存服务器判断转发目标的方法还有一种,那就是查看请求消息的包头部。因为包的 IP 头部中包含接收方IP 地址,只要知道了这个地址,就知道用户要访问哪台服务器了 ,更倾向于将透明代理说成是缓存
让客户端访问最近的缓存服务器的方法
1)依次查询所有路由器的路由表之后,我们就可以通过比较找出哪一台路由器距离客户端 DNS 服务器最近。提供路由表的路由器位于缓存服务器的位置,而客户端 DNS 服务器也应该和客户端在同一位置,这样就等于估算出了缓存服务器与客户端之间的距离,从而能够判断出哪台缓存服务器距离客户端最近了
2)HTTP 规格中定义了很多头部字段,其中有一个叫作Location 的字段。当 Web 服务器数据转移到其他服务器时可以使用这个字段,它的意思是“您要访问的数据在另一台服务器上,请访问那台服务器吧。”这种将客户端访问引导到另一台 Web 服务器的操作称为重定向,通过这种方法也可以将访问目标分配到最近的缓存服务器。
当网络包到达 Web 服务器后,数据会被解包并还原为原始的请求消息,然后交给 Web 服务器程序
和客户端一样,这个操作也是由操作系统中的协议栈(网络控制软件)来完成的。
接下来,Web 服务器程序分析请求消息的含义,并按照其中的指示将数据装入响应消息中,然后发回给客户端。
响应消息回到客户端的过程和之前我们介绍的过程正好相反。
当响应到达客户端之后,浏览器会从中读取出网页的数据并在屏幕上显示出来
到这里,访问 Web 服务器的一系列操作就全部完成了
多任务:操作系统提供的一种功能,可以让多个任务(程序)同时运行。实际上,一个处理器在某一个瞬间只能运行一个任务,但通过短时间内在不同的任务间切换,看起来就好像是同时运行多个任务一样。有些操作系统称之为“多进程”。
多任务和多线程的区别在于任务和线程的区别。在操作系统内部,任务是作为单独的程序来对待的,而线程则是一个程序中的一部分。
这个区别体现在如何调用 Socket 库上。
首先,客户端的数据收发需要经过下面 4 个阶段。
(1)创建套接字(创建套接字阶段)
(2)用管道连接服务器端的套接字(连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)
相对地,服务器是将阶段(2)改成了等待连接,具体如下。
(1)创建套接字(创建套接字阶段)
(2-1)将套接字设置为等待连接状态(等待连接阶段)
(2-2)接受连接(接受连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)
创建套接字操作的本质是分配用于套接字的内存空间
端口号是用来识别套接字的
使用描述符来指代套接字的原因如下。
(1)等待连接的套接字中没有客户端 IP 地址和端口号
(2)使用描述符这一种信息比较简单
如果收到的是发起连接的包,则 TCP 模块会(1) 确认 TCP 头部的控制位SYN;(2) 检查接收方端口号;(3) 为相应的等待连接套接字复制一个新的副本;(4) 记录发送方 IP 地址和端口号等信息。
拼合数据块的操作在每次收到数据包时都会进行,而不是等所有数据全部接受完毕之后再统一拼合的。
Web 服务器的基本工作方式就是根据请求消息的内容判断数据源,并从中获取数据返回给客户端,不过在执行这些操作之前,Web 服务器还可以检查事先设置的一些规则,并根据规则允许或禁止访问。这种根据规则判断是否允许访问的功能称为访问控制,一些会员制的信息服务需要限制用户权限的时候会使用这一功能,公司里也可以利用访问控制只允许某些特定部门访问。