一文解密网络背后的秘密
- 网络的发展史
- 从键入网址到网页进行显示期间法上了什么?
- 首先我们需要了解一下URL
- DNS解析
- 建立连接
- 什么是三次握手,可不可以两次呢?
- 本地发送请求,服务器处理请求
- 断开连接
- 实践一下
相信大家对网络都不陌生了,进入21世纪依赖,我国的互联网行业迅猛发展,在此期间诞生了一家又一家的大型互联网公司。那么,究竟什么是网络,为什么我们点开不同的软件就可以看到不同的东西呢?
网络的发展史
回顾互联网的发展史, 我们经历了web1.0的静态数据页面,早期的搜狐就是一个例子。
在1.0的时代的只读时代,我们只能被动的获取互联网上的信息,所有的内容只是单纯的发布到网络上共网民进行浏览。
而到后来随着互联网进步,交互的方式也进步了。我们可以开始写了,这最主要的一个象征就是 blog 的出现。还有早期的天涯,你发自己的帖子,大量的用户开始贡献内容。那一时刻开始,互联网不但可以读了而且还可读可写了。这就是 web2.0。
目前大家都都生活在Web 2.0时代,也离不开Web 2.0带来的便捷。那么Web 3.0的出现是否能解决Web 2.0的缺点,它的缺点又是什么呢?
首先说明下web2.0时代应用程序是如何让工作的:
这里的一个主要缺陷是所有数据都存储在由公司控制的集中式服务器上。很多公司开始将用户数据存储在他们的服务器中,以便他们可以通过网络为我们提供更好的内容。这反过来又会使我们在他们的网站上停留的时间更长,从而为这些公司提供更多的广告收入。这些公司最终开始将我们的信息出售给广告商,侵犯个人隐私外还在我们身上盈利!无疑这个是中心化的时代。而且很多时候会给我们推送一些我们不想看到的东西,但是我们想要的部分却无法找到。
Web3.0 概念旨在创建一个去中心化但安全的互联网,人们可以在其中安全地交换金钱和信息,而无需中间商或大型科技公司。
从键入网址到网页进行显示期间法上了什么?
现在我们经常会接触到一些东西,像是逛淘宝或者百度一些东西,我们可以发现不同网站的网址是不一样的,那么计算机是如何让识别出这些网址是属于 哪一个网站的呢?
首先我们需要了解一下URL
URL也就是我们常说的网址,含有超文本传输协议,web服务器的代表名称,以及访问源文件的目录。
那么是否这个网址就是真实的服务器地址呢?当然不是的,我们可以使用ping命令试着ping一下某一个网址例如:
我们发现在网址后面出现了一串数字,这个才是我们服务器的真实ip地址,我们发现这串数字其实是由3个英文句号分割成的四个字符串,这就是IPV4地址,也是我们在互联网中的唯一标识。
那么,浏览器是如何将域名识别为这个IP地址的呢?
DNS解析
在发送之前,还有一项工作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。
比如我们打电话的时候,必须要知道对方的电话号码,但由于电话号码难以记忆,所以通常我们会将对方电话号 + 姓名保存在通讯录里。
所以,有一种服务器就专门保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器。我们所看到的域名都是注册在DNS服务器中的,当我们需要用到服务器真实IP地址的时候就会通过DNS去查询域名所对应的IP地址。我们常见的域名都是
== www.**.== 但是其实在最后还有一个“ . ”,代表根域名。
域名的解析流程:
- 客户端首先会发出一个 DNS 请求,问 www.server.com 的 IP 是啥,并发给本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。
- 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到 www.server.com,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器:“老大, 能告诉我 www.server.com 的 IP 地址吗?” 根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。
- 根 DNS 收到来自本地 DNS 的请求后,发现后置是 .com,说:“www.server.com 这个域名归 .com 区域管理”,我给你 .com 顶级域名服务器地址给你,你去问问它吧。”
- 本地 DNS 收到顶级域名服务器的地址后,发起请求问“老二, 你能告诉我 www.server.com 的 IP 地址吗?”
- 顶级域名服务器说:“我给你负责 www.server.com 区域的权威 DNS 服务器的地址,你去问它应该能问到”。
- 本地 DNS 于是转向问权威 DNS 服务器:“老三,www.server.com对应的IP是啥呀?” server.com 的权威 DNS 服务器,它是域名解析结果的原出处。为啥叫权威呢?就是我的域名我做主。
- 权威 DNS 服务器查询后将对应的 IP 地址 X.X.X.X 告诉本地 DNS。
- 本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。
建立连接
确定好真实的IP地址后,就可以开始准备通信了,之前说到我们输入网址的“//”前面代表协议,"http://www.wall-e.icu/"我们看这个网站,他的协议就是http协议,在通信之前我们还需要与目标服务器建立稳固的链接,防止信息在传递的时候丢失,这就需要老师会经常说的“三次握手,四次挥手”中的三次握手。
什么是三次握手,可不可以两次呢?
我们需要先知道一些基础的概念,例如
- SYN: 请求建立连接
- ACK: 对请求做出响应
- SYN_SEND: 等待匹配连接的请求
- ESTABLISHED:代表一个打开的连接
三次握手需要保证客户端和服务端都要保证一发、一收信息,过程如下:
- 一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SEND 状态,等待服务器的确认;
- 二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态
- 三次握手:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成 TCP 三次握手。
形象的比喻一下就是使用对讲机的场景:
A: 你好,请听到我说话吗?
B: 是的,我可以听到,你呢?能听到我说话吗?
A: 可以听到。
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常三次握手就能确认双方收发功能都正常,缺一不可。
本地发送请求,服务器处理请求
接下来就会通过建立好的连接进行通信,既然要通信,那么我们是不是要定义一个规则呢,不然打比方说,你们两个互不了解,对方是外国人,怎样让对方理解你的意思,是不是要约定一些东西,http或者https就是这个规定,定义了我们想服务器通信的规则。
超文本传输协议(HTTP,HyperText Transfer Protocol) 是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。HTTP 使用客户端-服务器模型,客户端向服务器发送 HTTP Request(请求),服务器响应请求并返回 HTTP Response(响应),整个过程如下图所示。
HTTP 是应用层协议,它以 TCP(传输层)作为底层协议。
- 服务器在 80 端口等待客户的请求。
- 浏览器发起到服务器的 TCP 连接(创建套接字 Socket)。
- 服务器接收来自浏览器的 TCP 连接。
- 浏览器(HTTP 客户端)与 Web 服务器(HTTP 服务器)交换 HTTP 消息。关闭 TCP 连接
HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证。默认端口号是 443。HTTPS 协议中,SSL 通道通常使用基于密钥的加密算法,密钥长度通常是 40 比特或 128 比特。
断开连接
既然服务器已经做出了回应,那么就代表这一次的请求就完成了,为了不浪费计算机资源,我们是不是应该断开连接。
于是经典的“四次挥手就出现了”。
具体过程:
客户端主动调用关闭连接的函数,于是就会发送 FIN 报文,这个 FIN 报文代表客户端不会再发送数据了,进入 FIN_WAIT_1 状态;
服务端收到了 FIN 报文,然后马上回复一个 ACK 确认报文,此时服务端进入 CLOSE_WAIT 状态。在收到 FIN 报文的时候,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,服务端应用程序可以通过 read 调用来感知这个 FIN 包,这个 EOF 会被放在已排队等候的其他已接收的数据之后,所以必须要得继续 read 接收缓冲区已接收的数据;
接着,当服务端在 read 数据的时候,最后自然就会读到 EOF,接着 read() 就会返回 0,这时服务端应用程序如果有数据要发送的话,就发完数据后才调用关闭连接的函数,如果服务端应用程序没有数据要发送的话,可以直接调用关闭连接的函数,这时服务端就会发一个 FIN 包,这个 FIN 报文代表服务端不会再发送数据了,之后处于 LAST_ACK 状态;
客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
客户端经过 2MSL 时间之后,也进入 CLOSE 状态;
你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
举个例子:A 和 B 打电话,通话即将结束后。
- 第一次挥手:A 说“我没啥要说的了”
- 第二次挥手:B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话
- 第三次挥手:于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
- 第四次挥手:A 回答“知道了”,这样通话才算结束
实践一下
也就是使用Socket编程,我们在本地分别建立两个Socket连接监听同一个端口号,通过该端口发送接收信息。
建立Socket服务器和客户端1
import jdk.nashorn.internal.ir.VarNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* @author zrq
* @ClassName Scoket
* @date 2023/10/12 16:53
* @Description TODO
*/
public class SocketDemo {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(8080);
socket = serverSocket.accept();
String msg = sc.next();
OutputStream outputStream = socket.getOutputStream();
outputStream.write(msg.getBytes());
InputStream inputStream = socket.getInputStream();
byte[] data = new byte[1024];
int len = inputStream.read(data);
System.out.println(new String(data, 0, len));
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
serverSocket.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
建立客户端2
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* @author zrq
* @ClassName TestTCPReceive
* @date 2023/10/12 16:58
* @Description TODO
*/
public class TestTCPReceive {
public static void main(String[] args) {
Socket socket = null;
try {
//对服务端发起连接请求
socket = new Socket("localhost", 8080);
//接受服务端消息并打印
InputStream is = socket.getInputStream();
byte[] data = new byte[1024];
int len = is.read(data);
System.out.println(new String(data, 0, len));
//给服务端发送响应信息
OutputStream os = socket.getOutputStream();
os.write("yes,I have received you message!".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
这样我们就可以实现一个简单的聊天功能了。