名词解释
CGI:
访问控制:Web 服务器在根据请求消息的内容判断数据源,并从中获取数据返回给客户端之前,还可以检查事先设置的一些规则,并根据规则允许或禁止访问,这种根据规则判断是否允许访问的功能称为访问控制。一些会员制的信息服务需要限制用户权限的时候会使用这一功能,公司里也可以利用访问控制只允许某些特定部门访问。
服务器概览
-
客户端与服务器的区别
根据用途,服务器可以分为很多种类,其硬件和操作系统与客户端是有所不同的。但是,网络相关的部分,如网卡、协议栈、Socket 库等功能和客户端却并无二致。无论硬件和 OS 如何变化,TCP 和 IP 的功能都是一样的,或者说这些功能规格都是统一的。不过,它们的功能相同,不代表用法也相同。在连接过程中,客户端发起连接操作,而服务器则是等待连接操作,因此在 Socket 库的用法上还是有一些区别的,即应用程序调用的 Socket 库的程序组件不同。 -
服务器程序的结构
服务器需要同时和多个客户端通信,但一个程序来处理多个客户端的请求是很难的,因为服务器必须把握每一个客户端的操作状态。因此一般的做法是,每有一个客户端连接进来,就启动一个新的服务器程序,确保服务器程序和客户端是一对一的状态。
服务器程序的结构如图 6.1 所示。程序分成两个模块,即等待连接模块(图 6.1(a))和负责与客户端通信的模块(图6.1(b))。当服务器程序启动并读取配置文件完成初始化操作后,就会运行等待连接模块(a)。这个模块会创建套接字,然后进入等待连接的暂停状态。当客户端连发起连接时,这个模块会恢复运行并接受连接,然后启动客户端通信模块(b),并移交完成连接的套接字。通信结束后,这个模块就退出了。
通过这样的方式,可以降低程序编写的难度。服务器操作系统具有多任务、多线程功能,可以同时运行多个程 ,服务器程序的设计正是利用了这一功能。 -
服务器端的套接字和端口号
从数据收发的角度来看,发起连接的一方是客户端,等待连接的一方是服务器。这个区别体现在如何调用 Socket 库上。首先,客户端的数据收发需要经过下面 4 个阶段:(1)创建套接字(创建套接字阶段)
(2)用管道连接服务器端的套接字(连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)
服务器是将阶段(2)改成了等待连接,具体如下。
(1)创建套接字(创建套接字阶段)
(2-1)将套接字设置为等待连接状态(等待连接阶段)
(2-2)接受连接(接受连接阶段)
(3)收发数据(收发阶段)
(4)断开管道并删除套接字(断开阶段)
服务器端的具体工作过程为:协议栈调用 socket 创建套接字,接下来调用 bind 将端口号写入套接字中。在客户端发起连接的操作中,需要指定服务器端的端口号,这个端口号也就是在这一步设置的。例如Web 服务器使用 80 号端口。 设置好端口号之后,协议栈会调用 listen 向套接字写入等待连接状态这一控制信息这样一来,套接字就会开始等待来自客户端的连接网络包。然后,协议栈会调用 accept 来接受连接。
当 accept 结束之后,等待连接的过程也就结束了,这时等待连接模块会启动客户端通信模块,然后将连接好的新套接字转交给客户端通信模块,由这个模块来负责执行与客户端之间的通信操作。之后的数据收发操作和刚才说的一样,与客户端的工作过程是相同的。
端口号是用来识别套接字的,如果一个端口号对应多个套接字,就无法通过端口号来定位到某一个套接字了。当客户端的包到达时,如果协议栈只看 TCP 头部中的接收方端口号,是无法判断这个包到底应该交给哪个套接字的。这个问题可以用下面的方法来解决,即要确定某个套接字时,不仅使用服务器端套接字对应的端口号,还同时使用客户端的端口号再加上 IP 地址,总共使用下面 4 种信息来进行判断(图 6.4)。
• 客户端 IP 地址
• 客户端端口号
• 服务器 IP 地址
• 服务器端口号
服务器上可能存在多个端口号相同的套接字,但客户端的套接字都是对应不同端口号的,因此我们可以通过客户端的端口号来确定服务器上的某个套接字。不过,使用不同端口号的规则仅限一台客户端的内部,当有多个客户端进行连接时,它们之间的端口号是可以重复的。因此,我们还必须加上客户端的 IP 地址才能进行判断。例如,IP 地址为 198.18.203.154的客户端的 1025 端口,就和 IP 地址为 198.18.142.86 的客户端的 1025 端口对应不同的套接字。
服务器的接受操作
-
网卡将接收到的信号转换成数字信息
到达服务器的网络包其本质是电信号或者光信号,接收信号的过程和客户端是一样的。接收操作的第一步是网卡接收到信号,然后将其还原成数字信息。局域网中传输的网络包信号是由 1 和 0 组成的数字信息与用来同步的时钟信号叠加而成的,因此只要从中分离出时钟信号,然后根据时钟信号进行同步,就可以读取并还原出 1 和 0 的数字信息了。
信号的格式随传输速率的不同而不同,因此某些操作过程可能存在细微差异,例如 10BASE-T 的工作方式如图 6.5 所示。首先从报头部分提取出时钟信号(图 6.5 ①),报头的信号是按一定频率变化的,只要测定这个变化的频率就可以和时钟信号同步了。接下来,按照相同的周期延长时钟信号(图6.5 ②),并在每个时钟周期位置检测信号的变化方向(图 6.5 ③)。图中用向上和向下的箭头表示变化方向,实际的信号则是正或负的电压,这里需要检测电压是从正变为负,还是从负变为正,这两种变化方向分别对应 0 和 1(图 6.5 ④)。
在图中,向上的箭头为 1,向下的箭头为 0,实际上是从负到正变化为 1,从正到负变化为 0。这样,信号就被还原成数字信息了(图 6.6)。接下来需要根据包末尾的帧校验序列(FCS)来校验错误,然后与包末尾的 FCS 值进行比较。当 FCS 一致,即确认数据没有错误时,接下来需要检查 MAC 头部中的接收方 MAC 地址,看看这个包是不是发给自己的。以太网的基本工作方式是将数据广播到整个网络上,只有指定的接收者才接收数据,因此网络中还有很多发给其他设备的数据在传输,如果包的接收者不是自己,那么就需要丢弃这个包。
到这里,接收信号并还原成数字信息的操作就完成了,还原后的数字信息被保存在网卡内部的缓冲区中。上面这些操作都是由网卡的 MAC 模块来完成的。
网卡的 MAC 模块将网络包从信号还原为数字信息,校验 FCS 并存入缓冲区。网卡驱动会根据 MAC 头部判断协议类型,并将包交给相应的协 议栈。
-
IP 模块的接收操作
当网络包转交到协议栈时,IP 模块会首先开始工作,检查 IP 头部。IP模块首先会检查 IP 头部的格式是否符合规范,然后检查接收方 IP 地址,看包是不是发给自己的。当服务器启用类似路由器的包转发功能时,对于不是发给自己的包,会像路由器一样根据路由表对包进行转发。
确认包是发给自己的之后,接下来需要检查包有没有被分片。检查 IP头部的内容就可以知道是否分片,如果是分片的包,则将包暂时存放在内存中,等所有分片全部到达之后将分片组装起来还原成原始包;如果没有分片,则直接保留接收时的样子,不需要进行重组。到这里,我们就完成了包的接收。
接下来需要检查 IP 头部的协议号字段,并将包转交给相应的模块。例如,如果协议号为 06(十六进制),则将包转交给 TCP 模块;如果是 11(十六进制),则转交给 UDP 模块。 -
TCP 模块如何处理连接包
当 TCP 头部中的控制位 SYN 为 1 时,表示这是一个发起连接的包(图 6.7 ①)。这时,TCP 模块会执行接受连接的操作,不过在此之前,需要先检查包的接收方端口号,并确认在该端口上有没有与接收方端口号相同且正在处于等待连接状态的套接字。如果指定端口号没有等待连接的套接字,则向客户端返回错误通知的包。
这个包到达客户端之后,客户端会返回表示接收确认的 ACK 号,当这个 ACK 号返回服务器后,连接操作就完成了。 -
TCP 模块如何处理数据包
当数据包到达时 TCP 模块(图 6.7 ②),TCP 模块会检查收到的包对应哪一个套接字。在服务器端,可能有多个已连接的套接字对应同一个端口号,因此仅根据接收方端口号无法找到特定的套接字。这时我们需要根据 IP 头部中的发送方 IP 地址和接收方 IP 地址,以及 TCP 头部中的接收方端口号和发送方端口号共 4 种信息,找到上述 4 种信息全部匹配的套接字。之后,TCP 模块会对比该套接字中保存的数据收发状态和收到的包的 TCP 头部中的信息是否匹配,以确定数据收发操作是否正常。
当收到的数据进入接收缓冲区后,TCP 模块就会生成确认应答的 TCP头部,并根据接收包的序号和数据长度计算出 ACK 号,然后委托 IP 模块发送给客户端。接下来,应用程序会调用 Socket 库的 read(图 6.7 ③)来获取收到的数据,这时数据会被转交给应用程序。 -
TCP 模块的断开操作
当数据收发完成后,便开始执行断开操作。这个过程和客户端是一样的,这里不再做详细介绍。
Web 服务器程序解释请求消息并作出响应
-
将请求的 URI 转换为实际的文件名
图 6.7 展示了服务器程序的工作过程,这个过程不仅限于 Web 服务器,对于各种服务器程序都是共通的,收发数据的过程也是大同小异的。Web 服务器中,图 6.7 的 read 获取的数据内容就是 HTTP 请求消息。服务器程序会根据收到的请求消息中的内容进行相应的处理,并生成响应消息,再通过 write 返回给客户端。请求消息包括一个称为“方法”的命令,以及表示数据源的 URI(文件路径名),服务器程序会根据这些内容向客户端返回数据,但对于不同的方法和 URI,服务器内部的工作过程会有所不同。
最简单的一种情况如图 6.8 中的例子所示,请求方法为 GET,URI 为一个 HTML 文件名。这种情况只要从文件中读出 HTML 文档,然后将其作为响应消息返回就可以了。
但是,如果完全按照 URI 中的路径和文件名读取,那就意味着磁盘上所有的文件都可以访问,Web 服务器的磁盘内容就全部暴露了,这很危险。Web 服务器公开的目录其实并不是磁盘上的实际目录,而是如图 6.9这样的虚拟目录,而 URI 中写的就是在这个虚拟目录结构下的路径名。因此,当读取文件时,需要先查询虚拟目录与实际目录的对应关系,并将URI 转换成实际的文件名后,才能读取文件并返回数据。
举个例子,假设我们的虚拟目录结构如图 6.9 所示,如果请求消息中的 URI 如下页(1)所示,那么因为 /~user2/…对应的实际目录为 /home/user2/…,所以将 URI 转换成实际文件名后应该是如下页(2)。
/~user2/sub-user2/sample.html (1)
/home/user2/sub-user2/sample.html (2)
于是,服务器就会根据上述路径从磁盘中读取相应的文件,然后将数据返回给客户端。
有些 Web 服务器程序还具有文件名改写功能,只要设置好改写的规则,当 URI 中的路径符合改写规则时,就可以将 URI 中的文件名改写成其他的文件名进行访问。当出于某些原因 Web 服务器的目录和文件名发生变化,但又希望用户通过原来的网址进行访问的时候,这个功能非常有用。 -
运行 CGI 程序
如果 URI 指定的文件内容为 HTML 文档或图片,那么只要直接将文件内容作为响应消息返回客户端就可以了。但 URI 指定的文件内容不仅限于 HTML 文档,也有可能是一个程序。Web 服务器可以启动的程序有几种类型,每种类型的具体工作方式有所区别,下面我们来看看 CGI 程序是如何工作的。
当需要 Web 服务器运行程序时,浏览器发送的 HTTP 请求消息内容会和访问 HTML 文档时不太一样。Web 服务器运行程序时,一般浏览器会将需要程序处理的数据放在 HTTP 请求消息中发送给服务器。这些数据有很多种类,例如购物网站订单表中的品名、数量、发货地址等,搜索引擎中输入的关键字也是一个常见的例子。浏览器需要在发送给 Web 服务器的请求消息中加入一些数据。一种是在 HTML 文档的表单中加上 method=“GET”,通过 HTTP 的 GET 方法,将输入的数据作为参数添加在 URI 后面发送给服务器。另一种方法是在 HTML 文档的表单中加上 method=“POST”,将数据放在 HTTP 请求消息的消息体中发送给服务器(图 6.10)。
收到请求消息之后,Web 服务器会进行下面的工作。首先,Web 服务器会检查 URI 指定的文件名,看一看这个文件是不是一个程序。这里的判断方法是在 Web 服务器中事先设置好的,一般是通过文件的扩展名来进行判断,例如将 .cgi、.php 等扩展名的文件设置为程序,当遇到这些文件时,Web 服务器就会将它们作为程序来对待。也可以设置一个存放程序的目录,将这个目录下的所有文件都作为程序来对待。此外,还可以根据文件的属性来进行判断。
如果判断要访问的文件为程序文件,Web 服务器会委托操作系统运行这个程序,然后从请求消息中取出数据并交给运行的程序。如果方法为GET,则将 URI 后面的参数传递给程序;如果方法为 POST,则将消息体中的数据传递给程序(图 6.11)。接下来,运行的程序收到数据后会进行一系列处理,并将输出的数据返回给 Web 服务器。输出数据的内容是由运行的程序生成的,Web 服务器并不过问,也不会去改变程序输出的内容。
-
Web 服务器的访问控制
Web 服务器的基本工作方式就是根据请求消息的内容判断数据源,并从中获取数据返回给客户端,不过在执行这些操作之前,Web 服务器还可以检查事先设置的一些规则,并根据规则允许或禁止访问。这种根据规则判断是否允许访问的功能称为访问控制,一些会员制的信息服务需要限制用户权限的时候会使用这一功能,公司里也可以利用访问控制只允许某些特定部门访问。Web 服务器的访问控制规则主要有以下 3 种。
(1)客户端 IP 地址
(2)客户端域名
(3)用户名和密码
根据客户端 IP 地址设置,在调用accept 接受连接时,就已经知道客户端的 IP 地址了,只要检查其是否允许访问就可以了。
当根据客户端域名设置规则时,需要先根据客户端 IP 地址查询客户端域名,这需要使用 DNS 服务器。一般我们使用 DNS 服务器都是根据域名查询 IP 地址,其实根据 IP 地址反查域名也可以使用 DNS 服务器。大致过程为,收到客户端的请求消息后,Web 服务器(图 6.12 ①)会委托协议栈告知包的发送方 IP 地址,然后用这个 IP 地址生成查询消息并发送给最近的 DNS 服务器(图 6.12 ②)。接下来,DNS 服务器找出负责管辖该 IP 地址的 DNS 服务器,并将查询转发给它(图 6.12 ③),查询到相应的域名之后返回结果(图 6.12 ④),然后 Web 服务器端的 DNS 服务器再将结果转发给 Web 服务器(图 6.12 ⑤)。这样一来,我们就可以根据发送方IP 地址查询到域名。接下来,为了保险起见,还需要用这个域名查询一下IP 地址,看看结果与发送方 IP 地址是否一致(图 6.12 ⑥)。
如果用户名和密码已设置好,那么情况如图 6.13。通常的请求消息中不包含用户名和密码,因此无法验证用户名和密码(图 6.13 ①)。因此,Web 服务器会向用户发送一条响应消息,告诉用户需要在请求消息中放入用户名和密码(图 6.13 ②)。浏览器收到这条响应消息后,会弹出一个输入用户名和密码的窗口,用户输入用户名和密码后(图 6.13 ③),浏览器将这些信息放入请求消息中重新发送给服务器(图 6.13 ④)。然后,Web 服务器查看接收到的用户名和密码与事先设置好的用户名和密码是否一致,以此判断是否允许访问,如果允许访问,则返回数据(图 6.13 ⑤)。
-
返回响应消息
当服务器完成对请求消息的各种处理之后,就可以返回响应消息了。这里的工作过程和客户端向服务器发送请求消息时的过程相同。
浏览器接收响应消息并显示内容
- 通过相应的数据类型判断其中的内容
Web 服务器发送的响应消息会被分成多个包发送给客户端,然后客户端需要接收数据。首先,网卡将信号还原成数字信息,协议栈将拆分的网络包组装起来并取出响应消息,然后将消息转交给浏览器。这个过程和服务器的接收操作相同。
要显示内容,首先需要判断响应消息中的数据属于哪种类型。Web 可以处理的数据包括文字、图像、声音、视频等多种类型,每种数据的显示方法都不同,因此必须先要知道返回了什么类型的数据,否则无法正确显示。我们需要一些信息才能判断数据类型,原则上可以根据响应消息开头的 Content-Type 头部字段的值来进行判断。这个值一般是下面这样的字符串。
Content-Type: text/html
其中“/”左边的部分称为“主类型”,表示数据的大分类;右边的“子类型”表示具体的数据类型。在上面的例子中,主类型是 text,子类型是html。表 6.1 列出了其中主要的一些类型。上面例子中的数据类型表示遵循 HTML 规格的 HTML 文档。
当数据类型为文本时,还需要判断编码方式,这时需要用charset 附加表示文本编码方式的信息,内容如下。
Content-Type: text/html; charset=utf-8
这里的 utf-8 表示编码方式为 Unicode,如果是 euc-jp 就表示 EUC 编码,iso-2022-jp 表示 JIS 编码,shift_jis 表示 JIS 编码。
有时候我们需要结合其他一些信息来综合判断数据类型,例如请求文件的扩展名、数据内容的格式等。比如,我们可以检查文件的扩展名,如果为 .html 或 .htm 则看作是 HTML 文件,或者也可以检查数据的内容,如果是以 开头的则看作是 HTML 文档。不仅是 HTML 这样的文本文件,图片也是一样。图片是经过压缩的二进制数据,但其开头也有表示内容格式的信息,我们可以根据这些信息来判断数据的类型。不过,这部分的逻辑并没有一个统一的规格,因此不同的浏览器以及不同的版本都会有所差异。 - 浏览器显示网页内容!访问完成!
判断完数据类型,接下来只要根据数据类型调用用于显示内容的程序,将数据显示出来就可以了。
不同类型的数据显示操作的过程也不一样,我们以 HTML 文档为例来介绍。HTML 文档通过标签表示文档的布局和字体等样式信息,浏览器需要解释这些标签的含义,按照指定的样式显示文档的内容。实际的显示操作是由操作系统来完成的,浏览器负责对操作系统发出指令,例如在屏幕上的什么位置显示什么文字、使用什么样的字体等。
网页中还可以嵌入图片等数据,HTML 文档和图片等数据是分别存在在不同的文件中的,HTML 文档中只有表示图片引用的标签 。在读取文档数据时,一旦遇到相应的标签,浏览器就会向服务器请求其中的图片文件。这个请求过程和请求 HTML 文档的过程是一样的,就是在 HTTP 请求消息的 URI 中写上图片文件的文件名即可。将这个请求消息发送给 Web 服务器之后,Web 服务器就会返回图片数据了。接下来,浏览器会将图片嵌入到标签所在的位置。
不过,Web 服务器可能还会返回其他一些类型的数据,如文字处理、幻灯片等应用程序的数据。这些数据无法由浏览器自行显示,这时浏览器会调用相应的程序。这些程序可以是浏览器的插件,也可以是独立的程序,无论如何,不同类型的数据对应不同的程序,这一对应关系是在浏览器中设置好的,只要按照这一对应关系调用相应的程序,并将数据传递给它就可以了。然后,被调用的程序会负责显示相应的内容。
到这里,浏览器的显示操作就完成了,可以等待用户的下一个动作了。当用户点击网页中的链接,或者在网址栏中输入新的网址时,访问 Web 服务器的操作就又开始了。
小节
关于网络连接的基础内容就到此为止了。以上内容,对互联网如何连接,有了大致认识,特别是对很多术语都有所了解。下面,就开始进入前端的学习内容了。首先是HTML和CSS。
ps: 前段时间突然接到了很多任务,工作量剧增,一时间实在是有些招架不住。不过,相对增长的是,用python处理数据的能力(主要是Pandas,Scipy两个强大的三方库)。