1.1生成HTTP请求消息
1.2向DNS服务器查询Web服务器的IP地址
1.3全世界DNS服务器的大接力
1.4委托协议栈发送消息
1.1生成HTTP请求消息
1.1.1输入网址UR(统一资源定位符)
由于浏览器是一个具备多种功能的客户端软件,因此需要不同的URL格式来判断使用其中的哪种功能访问相应的数据,比如:访问web服务器时用"http:",而访问ftp服务器时用"ftp:"。
FTP(文件传送协议):这是一种在上传和下载文件时使用的协议,使用ftp协议来传送文件的程序也被叫做FTP。
1.1.2浏览器解析URL
解析过程(以访问Web服务器为例讲解)
首先将整个URL解析成如下格式:
然后通过拆分的URL元素可以确定URL代表的含义:其中包括访问数据源的机制:协议;web服务器名;表示数据源(文件)的路径名。
例如:http://www.lab.glasscom.com/dir/file1.html这个URL表示要访问www.lab.glasscom.com这个web服务器上路径名为/dir/file1.html的文件,也就是位于/dir/目录下的file1.html这个文件。
假如URL中省略了最后的数据源的路径名,服务器就会访问默认的文件名,一般是index.html或者default.html之类的。
1.1.3HTTP协议简介
HTTP协议规定了客户端和服务器之间交互的消息内容和步骤。首先,客户端会向服务器发送请求消息,请求消息中包含的内容是"对什么"(成为URI:其内容是一个存放网页数据的文件名)和"进行怎样的操作"(称为方法)两个部分。
URI:其内容是一个存放网页数据的文件名
方法:表示让服务器完成怎样的工作,其中典型的例子包括读取URI表示的数据、将客户端输入的数据发送给URI表示的程序等。
HTTP协议的主要方法如下图所示:
客户端向服务器端发送请求消息中最常用的HTTP方法是GET和POST方法。
其中GET方法一般用于通过web服务器获取网页数据时。其访问过程是:首先,在请求消息中写上GET方法,然后在URI中写上存放网页数据的文件名"/dir/file1.html",这就表示我们需要获取/dir/file1.html文件中的数据。当web服务器收到消息后,会打开/dir/file1.html文件并读取里面的数据存放到响应消息中,并返回给客户端,最后,客户端浏览器会收到这些数据并显示在屏幕上。
此外POST方法是将我们在表单中填写的数据发送给web服务器。使用该方法时,URI会指向web服务器中运行的一个应用程序的文件名,典型的例子包括"index.cgi","index.php"等,然后,在请求消息中,除了方法和URI之外,还要加上传递给应用程序和脚本的数据,这里的数据也就是用户在输入框里填写的信息,当服务器收到消息后,web服务器会将请求的消息中的数据发送给URI指定的应用程序,最后,web服务器从应用程序接收输出的结果,会将它存放到响应消息中并返回客户端。
1.1.4生成HTTP请求消息
对URL进行解析后,浏览器确定了web服务器和文件名,于是可以根据这些信息来生成HTTP请求消息了。
HTTP消息的格式如下图所示:
请求消息格式包括四个部分:
第一行是请求行:包括方法,URI,HTTP版本
其中方法用于告诉web服务器应该进行怎样的操作。由于方法有多种,我们需要根据实际场景来确定。
第二行开始为消息头:用来存放一些额外的详细信息,如:日期,客户端支持的数据类型,语言,,,,
写完消息头后需要添加一个完全没有内容的空行
最有一部分为消息体:实际要发送的数据(使用GET方法时该部分为空,POST方法中实际的数据就是:用户表单中填写的数据)
1.1.5浏览器发送请求后会收到web服务器的响应
响应消息的格式如下图所示:
响应消息的格式与请求消息的格式和思路基本一致,最大的差别在于第一行:
响应消息第一行内容为状态码和响应短语:用来表示请求的执行结果是成功还是出错。常用的状态码和含义如下图所示:
返回响应消息后,浏览器会将数据提取出来并显示在屏幕上(网页)。如果网页中只包含文字,则处理完毕,如果网页中包含图片等资源则还有下一步。
当网页中包含图片时,会在网页中的相应位置嵌入表示图片文件的标签的控制信息。浏览器会在显示文字时搜索相应的标签,当遇到图片相关的标签时吗会在屏幕上留出用来显示图片的空间,然后再次访问web服务器,按照标签中指定的文件名向web服务器请求获取相应的图片并显示在预留的空间中。
一条请求消息中只能写一个URI,如果需要获取多个文件,必须对每个文件单独发送1条请求。比如:一个网页包含3张图片,那么获取网页加上获取图片一共需要向web服务器发送4条请求。
发送HTTP请求消息和收到响应消息的详细过程如下图所示:
1.2向DNS服务器查询Web服务器的IP地址
1.2.1 IP地址的作用及其格式
浏览器解析网址并生成HTTP请求消息后,由于其自身不具备将消息发送到网络中的功能,因此这一功能需要委托操作系统来实现,此时需要提供的网址中服务器域名对应的IP地址。
IP地址的格式如下图所示:
IP地址共分为网络号和主机号两部分,总共32位。但这两部分的具体结构是不固定的,因此需要额外附加信息——子网掩码。
子网掩码也是32位,其左边一般是1,右边一般是0,子网掩码为1的部分表示网络号,为0的部分表示主机号。IP地址的结构如下图所示:
此外,子网掩码中主机号部分全部为0代表整个子网而不是子网中的某台设备,主机号部分全部为1代表向子网上所有设备发送包,即广播。
1.2.2域名和IP地址并用的理由
1)当浏览器发送请求时不直接使用IP地址而是使用域名的原因:
IP地址是一个32位的01数字,不容易记忆
2)当委托操作系统发送请求消息时不直接使用域名而是使用IP地址的原因:
IP地址的长度为32位,也就是4字节,而域名最短也要十几个字节最长达到255字节,因此使用IP地址只需处理4字节的数字,而使用域名则需要处理更多的字符,这增加了路由器的负担,传送时间也会话费更多。并外IP地址长度固定,域名长度不固定,机器处理固定长度的数据要比不固定长度的效率高得多。
1.2.3 Socket库提供查询IP地址的功能
要查询服务器的IP地址,只需向DNS服务器发送查询消息即可。通过DNS查询IP地址的操作称为域名解析,因此负责执行解析的这一操作的就叫解析器了。
库就是一堆通用组件的集合,其他的应i 用程序都需要使用其中的组件。Socket库也是一种库,其中包含的程序组件可以让其他应用程序调用操作系统的网络功能(即:Socket库包含很多用于发送和接收数据的程序组件),而解析器就是这个库中的其中一种程序组件。
1.2.4 通过解析器向DNS服务器发出查询
Socket库中的程序都是标准组件,只要从应用程序中进行调用就可以了。解析器的调用方法如下:
调用解析器后,解析器会向DNS服务器发送查询消息,然后DNS服务器会返回响应消息,响应消息中包含查询到的IP地址,解析器会取出IP地址,并将其写入浏览器指定的内存地址中。
当浏览器向web服务器发送消息时,只要从该内存地址取出IP地址,并将它与HTTP请求消息一起交给操作系统就可以了。
1.2.5 解析器的内部原理
网络应用(浏览器)调用解析器时,程序的控制流程会转移到解析器的内部(1),此时解析器会生成要发送给DNS服务器的查询消息(2),这个过程与浏览器生成要发送给web服务器的HTTP请求消息的过程类似。解析器会根据DNS的规格,生成一条表示"请告诉我www.lab.glasscom.com的IP地址"的数据,并将它发生给DNS服务器(3),发送消息并不是由解析器自身来完成,而是委托给操作系统内部的协议栈来执行。解析调用协议栈后,控制流程会再次转移,协议栈会执行发送消息的操作,然后通过网卡将消息发送给DNS服务器(4,5)。
如果要访问的web服务器已经在DNS服务器上注册,那么DNS服务器根据消息中查询内容就可以找到该记录,然后将IP地址写入响应消息并返回给客户端(6)。接着,消息经过网络到达客户端,再经过协议栈被传递给解析器(7,8),然后解析器读出消息取出IP地址,并将IP地址传递给应用程序(9),实际上,解析器会将取出的IP地址写入应用程序指定的内存地址中。
1.3全世界DNS服务器的大接力
1.3.1DNS服务器的基本工作
DNS服务器的基本工作就是接受来自客户端的查询消息,然后根据消息的内容返回响应,其中,来自客户端的查询消息包含以下3种信息:
a)域名:服务器,邮件服务器(邮件地址中@后面的部分)的名称
b) Class:用来识别网络的信息
c)记录类型:表示域名对应何种类型的记录。例如,当类型为A时,表示域名对应的是IP地址,当类型为MX时,表示域名对应的是邮件服务器
DNS服务器上事先保存有前面3种信息对应的记录数据,如下图所示。
DNS服务器就是根据这些记录查找符合查询请求的内容并对客户端做出响应的。
例如:要查询www.lab.gclasscom.com这个域名对应的IP地址,首先,客户端会向DNS服务器发送包含以下信息的查询消息:
(a)域名=www.lab.gclasscom.com
(b)Class=IN
©记录类型=A
然后,DNS服务器会从已有的记录中查找域名,Class,记录类型全部匹配的记录。假如DNS服务器中记录如上图所示,那么第一行记录与查询消息中的3个项目完全一致。于是,DNS服务器会将记录中的192.0.2.226这个值返回给客户端。
在查询IP地址时我们使用A这个记录类型,查询邮件服务器时则使用MX类型。因为在DNS服务器中,IP地址是保存在A记录中的,而邮件服务器则是保存在MX记录中的,例如,对于一个邮件地址tone@gclasscom.com,当需要知道这个地址对应的邮件服务器时,我们需要提供@后面的那一串名称,查询消息的内容如下:
(a)域名=gclasscom.com
(b)Class=IN
©记录类型=MX
DNS服务器会返回10和mail.glasscom.com这两条信息。当记录类型为MX时,DNS服务器会在记录中保存两种信息,分别是邮件服务器的域名和优先级。此外,MX记录的返回消息还包括邮件服务器mail.glasscom.com的IP地址。
1.3.2域名的层次结构
互联网中存在不计其数的服务器,将这些服务器的信息全部保存在一台DNS服务器中是不可能的,因此,DNS服务器中的所有信息都是按照以分层次的结构来保存的。
DNS中域名是用句点来分隔的,比如www.lab.glasscom.com,这里的句点代表了不同层次之间的界限。在域名中,越靠右的位置表示其层级越高,比如,比如www.lab.glasscom.com这个域名如果按照公司的组织结构来说,大概就是"com事业集团glasscom部lab科的www"。其中,相当于一个层级的部分称为域,因此,com域的下一层是glasscom域,再下一层是lab域,再下面才是www这个名字。
1.3.3寻找相应的DNS服务器并获取IP地址
下面再来看一看如何找到DNS服务器中存放的信息,这里的关键在于如何找到我们要访问的web服务器的信息归哪一台DNS服务器管。
首先,将负责管理下级域的DNS服务器的iP地址注册到它们的上级DNS服务器中,然后上级DNS服务器的IP地址再注册到更上一级的DNS服务器中,依此类推。
也就是说,负责管理lab.glasscom.com这个域的DNS服务器的IP地址需要注册到glasscom.com域的DNS服务器中,而glasscom.com域的DNS服务器的IP地址又需要注册到com域的DNS服务器中。这样,我们就可以通过上级DNS服务器查询出下级DNS服务器的IP地址,也就可以向下级DNS服务器发送查询请求了。
DNS服务器之间的查询操作过程如图所示:
首先,客户端会先访问最近的一台DNS服务器(也就是客户端的TCP/IP设置中填写的DNS服务器地址),假设我们要查询www.lab.glasscom.com这台web服务器的相关信息(1)。由于最近的DNS服务器中没有存放www.lab.glasscom.com这一域名对应的信息,所以我们需要从顶层开始往下查找。最近的DNS服务器中保存了根域DNS服务器的信息,因此它会将来自客户端的查询消息转发给根域DNS服务器(2)。根域服务器中也没有www.lab.glasscom.com这个域名,但根据域名结构可以判断这个域名属于com域,因此根域DNS服务器会返回它所管理的com域中的DNS服务器的IP地址。
接下来,最近的DNS服务器又会向com域的DNS服务器发送查询消息(3)。com域中也没有www.lab.glasscom.com这个域名的信息,和刚才一样,com域服务器会返回它下面的glasscom.com域的DNS服务器的IP地址。
依次类推, 重复前面的步骤就可以找到目标DNS服务器,只要向目标DNS服务器发送查询消息就能获得www.lab.glasscom.com的IP地址。
收到客户端的查询消息之后,DNS服务器会按照前面的方法来查询IP地址,并返回给客户端(6)。这样,客户端就知道了web服务器的IP地址,也就能够对其进行访问了。
1.3.4通过缓存加快DNS服务器的响应
有时候并不需要从最上级的根域开始查找,因为DNS服务器有一个缓存功能,可以记住之前查询过的域名,如果要查询的域名和相关的信息已经在缓存中,那么就可以直接返回响应,接下来的查询可以从缓存的位置开始向下进行,相比每次都从根域开始查找,缓存可以减少查询所需的时间。
DNS服务器中保存的信息都设置了有效期,当缓存中的信息超过了有效期后,数据就会从缓存中删除。
1.4委托协议栈发送消息
1.4.1数据收发操作概览
知道了IP地址之后,就可以委托操作系统内部的协议栈向这个目标IP地址,也就是我们要访问的web服务器发送消息了。
和向DNS服务器查询IP地址的操作一样,这里也需要使用Socket库中的程序组件,不过查询IP地址只需要调用一个程序组件就可以了,而这里需要按照指定的顺序调用多个程序组件。
使用Socket库来收发数据的操作过程如下:
首先,服务器一方先创建套接字,然后等待客户端向该套接字连接管道。当服务器进入等待状态,客户端就可以连接管道了。具体来说,客户端会先创建一个套接字,然后从该套接字延伸出管道,最后管道连接到服务器端的套接字上。
连接完成后,只要将数据送入套接字就可以收发数据了。
收发数据结束后,可由客户端或服务器端任一方发起断开管道,当管道断开后,套接字也会被删除,至此,通信结束。
收发数据可总结给4个阶段:
1)创建套接字(创建套接字阶段)
2) 将管道连接到服务器端的套接字上(连接阶段)
3)收发数据(通信阶段)
4)断开管道并删除套接字(断开阶段)
在每个阶段mSocket库中的程序组件都会被调用来执行相关的数据收发操作,这四个阶段实际均由操作系统的协议栈来执行。
1.4.2创建套接字阶段
客户端创建套接字只需调用Socket库中的socket程序组件就可以完成创建(其内部操作在第二章)。完成后,协议栈会返回一个描述符,应用程序会将收到的描述符放在内存中。
描述符是用来识别不同的套接字的。比如,可以同时打开两个浏览器窗口,同时访问两台web服务器,这时,有两个数据收发操作在同时进行,也就需要创建两个不同的套接字,即计算机上可能同时存在多个套接字,描述符就用域识别出某个特定的套接字,可以说,描述符就是分配给某个套接字的编号。
1.4.3连接阶段:把管道连接上去
应用程序通过调用Socket库中的名为connect的程序组件来完成,需要注意的是:调用connect时,需要指定描述符,服务器IP地址和端口号
第一个参数,即描述符,就是在创建套接字的时候由协议栈返回的那个描述符。connect会将应用程序指定的描述符告知协议栈,然后协议栈根据这个描述符来判断到底使用哪一个套接字去和服务器端的套接字进行连接,并执行连接的操作。
第二个参数,即服务器的IP地址,就是通过DNS服务器查询得到的我们要访问的服务器额IP地址。
第三个参数,即端口号,用来让通信的另一方能够识别出套接字的机制,只要指定了事先规定好的端口号,就可以连接到相应的服务器程序的套接字。一般情况下,服务器上所使用的端口号时根据应用的种类实现规定好的,比如:web是80端口号,电子邮件是25号端口号。
总之,当调用connect时,协议栈就会执行连接操作,当连接成功后,协议栈就会将对方的IP地址和端口号等信息保存在套接字中,这样我们就可以收发数据了。
1.4.4通信阶段:传递消息
当套接字连接起来后,只要通过调用Socket库中的write组件将数据送入套接字即可。具体过程如下:
首先,应用程序需要在内存中准备好要发送的数据。根据用于输入的网址生成的HTTP请求消息就是我们要发送的数据。
接下来,当调用write时,需要指定描述符和发送数据,然后协议栈就会将数据发送到服务器。(由于套接字中已经保存了已连接的通信对象的相关信息,所以只要通过描述符指定套接字,就可以识别出通信对象,并向其发送数据,接着,发送数据会通过网络到达我们要访问的服务器)
然后,服务器执行接受操作,解析收到的数据内容并执行相应的操作,向客户端返回响应消息。
最后,当消息返回后,需要通过Socket库中的read程序组件委托协议栈进行接收消息。调用read时需要指定用域存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区。于是,当服务器返回响应消息时,read就会负责将接收到的响应消息存放到接收缓冲区中。由于接收缓冲区是一块位于应用程序内部的内存空间,因此当消息被存放到接收缓冲区中时,就相当于已经转交给应用程序了。
1.4.5断开阶段:收发数据结束
当浏览器收到数据后,收发数据的过程就结束了,于是我们需要调用Socket库的close程序组件进入断开阶段,最终,连接在套接字之间的管道会被断开,套接字本身也会被删除。
断开的过程如下:
web使用的HTTP协议规定,当web服务器发送完响应消息之后,应该主动执行断开操作,因此,web服务器会首先调用close来断开连接,断开操作传达到客户端之后,客户端的套接字也会进入断开阶段。接下来,当浏览器调用read执行接收数据操作时,read会告知浏览器接收发数据操作已经结束,连接已经断开。浏览器得知后,也会调用close进入断开阶段。
参考书籍:《网络是怎样连接的》[日] 户根勤 / 著 周自恒 / 译