当人们从浏览器输入了一个 URL 后,
浏览器从浏览器缓存,系统缓存,路由缓存查找缓存记录,查到了直接使用 URL 对应的 IP 地址;
如果没有,客户端从 URL 中解析出服务端的主机号;
DNS 将主机号转化为 IP 地址;
客户端首先会访问最近的 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。
由于最近的 DNS 服务器中没有存放需查找域名的对应信息,就要从顶部(根域)向下一层一层的查找。
这里的一层一层向下查找还是不方便的。有时候并不需要从最上级的根域开始查找,因为DNS服务器有一个缓存功能,可以记住之前查询过的域名。如果要查询的域名和相关信息已经在缓存中,那么直接从缓存中得到所需信息,接着从缓存位置向下查找即可。
需要注意的是,缓存机制还是存在一定问题的。那就是信息缓存后,原有的注册信息可能被改变,这时候缓存的信息就有可能是不正确的,所以就给缓存设置了一个有效期,超过有效期,数据就会从缓存中删除,在对查找进行响应时,DNS服务器也会告知客户端响应结果的来源是缓存还是负责管理域名的DNS服务器。
直到找到目标DNS服务器。
客户端再从 URL 中获取端口号;
得到目的主机的 IP 地址和端口号后,调用系统库函数 Socket。请求一个 TCP 流的套接字;
向操作系统内部的协议栈发出委托,需要按照指定的顺序来调用Socket库中的程序组建。
首先,服务器一方先创建套接字,然后等待客户端向该套接字连接管道,客户端也会创建一个套接字(协议栈会随机给套接字分配一个端口号,连接操作时再将这个端口号通知给服务端),从该套接字延伸出管道(管道在连接时,是由客户端发起的)连接到服务端的套接字上,此时通信准备就完成了。当一方断开连接时,另一方也断开连接。管道断开时,套接字也被删除。
具体流程如下:
1. 创建套接字(创建套接字阶段)
调用Socket中的socket程序组件创建套接字,协议栈返回一个描述符。
2. 将管道连接到服务器端的套接字上(连接阶段)
调用connect(<描述符>,<IP地址和端口号>,…),协议栈就会执行连接操作。
3. 收发数据(通信阶段)
调用write,协议栈就会把描述符和发送数据发送到服务端。
调用read,将接收到的响应信息存放到接受缓冲区(指定用于存放响应消息的内存地址)中,就相当于已经转交给应用程序,被客户端接受到。
4. 断开管道并删除套接字(断开阶段)
服务端会先调用close断开连接,客户端再调用close断开连接。在HTTP 1.1的版本,当所有数据都请求完成后,客户端会主动触发断开连接操作。
客户端向服务端发送 HTTP 请求;
应用层:客户端发送 HTTP 请求报文;
传输层:加入源端口和目的端口,建立连接。在此之前,客户端和服务端(三次握手)建立 TCP 连接请求;
网络层:(加入 IP 头),路由寻址;
数据链路层:(加入 Frame),传输数据;
物理层:传输 bit;
服务端(经物理层——数据链路层——网络层——传输层——应用层)逆序解析 HTTP 请求报文,想客户端发送 HTTP 响应报文;
关闭连接(四次挥手);
客户端解析 HTTP 响应报文,浏览器显示静态资源。