Error:注意:这篇文章的内容由AI生成。
Prompt:我想理顺互联网是怎么连接的。通过我自己从服务器开始自己搭个人博客的经历,我大体能理清“客户端(浏览器)发送请求(关于请求方法、端口等等)->DNS解析->服务器端Nginx->/var/www/真正的页面、数据、逻辑等等->Nginx->客户端”这样一条线,但我知道这肯定是不准确不完整的(譬如我知道还有像协议、套接字……这些名词)。你能为我完整清楚地讲解一下吗?

一次完整的 Web 访问,比你想象的要多绕几个弯,但抓住一条主线就能串起所有概念。下面我以你访问自己搭建的博客(假设是 https://blog.yourdomain.com)为例,把从浏览器敲回车到页面出现的完整链路讲清楚。


1. 拆解 URL,决定“怎么去”

浏览器先把 https://blog.yourdomain.com/articles/1 拆开:

  • 协议https → 用 TLS 加密的 HTTP,目标端口默认 443
  • 主机名blog.yourdomain.com → 需要先变成 IP
  • 路径/articles/1 → 请求的具体资源

协议和端口是后续构造请求的基础,但首要问题是:blog.yourdomain.com 的 IP 是什么? 这就进入 DNS。


2. DNS:把名字变成 IP

DNS 不是“查一次就完事”,而是一个层级递归/迭代的过程:

  1. 浏览器缓存操作系统缓存hosts 文件 → 如果都没有,就向本地 DNS 服务器(通常是路由器或 ISP 提供的)发起查询。
  2. 本地 DNS 服务器从 根域名服务器 问到 .com 的顶级域服务器,再问到 yourdomain.com 的权威 DNS 服务器,最终拿到一条 A 记录(IPv4)或 AAAA 记录(IPv6)。
  3. 假设得到 IP 为 203.0.113.5,这就是你服务器的公网地址。

DNS 本身跑在 UDP 53 端口(也支持 TCP),对整个流程至关重要的是:现在浏览器知道了服务器的 IP 和端口(443),可以建连接了。


3. 套接字(Socket)登场,建立 TCP 连接

套接字 = IP 地址 + 端口号 + 协议,是操作系统提供给应用程序收发数据的“门把手”。
  • 浏览器调用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 套接字
  • 内核自动分配一个 随机源端口(如 54123),源 IP 是你本机的内网 IP(如 192.168.1.10)。
  • 随后 connect() 到目标 203.0.113.5:443。内核开始 TCP 三次握手

    • 你 → 服务器:SYN(序列号 x)
    • 服务器 → 你:SYN+ACK(序列号 y,确认 x+1)
    • 你 → 服务器:ACK(确认 y+1)

连接建立后,这对套接字 192.168.1.10:54123 <-> 203.0.113.5:443 就成为浏览器和服务器之间专属的“双向管道”。

那你的个人电脑怎么出得去公网?——NAT

你的 192.168.x.x 是私有地址。家庭路由器做 SNAT(源地址转换),把你的数据包的源 IP 换成路由器的公网 IP,并分配一个临时端口(如 33333)。服务器看到的实际是 路由器公网IP:33333。路由器记住这个映射,把返回的包再转回给你的内网机器。整个过程对浏览器透明。


4. TLS 握手:在 TCP 上建一条加密隧道

因为用了 https,TCP 连接建立后还不能直接发 HTTP 请求,必须先进行 TLS 握手(简版流程):

  1. 浏览器发送 ClientHello(支持的加密套件、随机数等)。
  2. 服务器(Nginx)回复 ServerHello(选定加密套件、随机数) + 证书链(包含域名 blog.yourdomain.com 的公钥)。
  3. 浏览器验证证书:域名匹配、由受信任的 CA 签发、未过期。
  4. 双方通过密钥交换算法(ECDHE 等)协商出 对称会话密钥
  5. 后续 HTTP 数据全用这个对称密钥加密,外面看到的都是密文。

至此,一条安全、可靠的 TCP + TLS 字节流 已经就绪。


5. 构造并发送 HTTP 请求

浏览器拼出 HTTP 请求报文(明文,但即将被 TLS 加密):

GET /articles/1 HTTP/1.1
Host: blog.yourdomain.com
User-Agent: Mozilla/5.0 ...
Accept: text/html,...

把这个字符串通过之前创建的套接字 write() 进去。操作系统内核做这些事:

  • TCP 层:把数据切成合适的段(segment),加上 TCP 头(源/目标端口、序列号、校验和等)。
  • IP 层:封装为 IP 包,源 IP(经过 NAT 后为路由器公网 IP),目标 IP 203.0.113.5,协议号 6(TCP)。
  • 链路/物理层:查路由表,找到下一跳网关。在以太网中,通过 ARP 获取网关 MAC 地址,封装成帧发送。

这包数据就在网线/Wi-Fi/光纤中,经过多个路由器逐跳转发,最终到达你服务器的网卡。


6. 服务器端接收,内核将数据交给 Nginx

服务器网卡收到帧,内核协议栈逐层剥离:

  1. 检查 MAC 地址,确认是给自己的。
  2. 网络层看到目标 IP 是自己,协议是 TCP,递交给传输层。
  3. TCP 层看到目标端口 443,查找此端口对应的 监听套接字——正是你的 Nginx 进程。

Nginx 内部做了什么?

Nginx 的 master 进程提前创建了一个 监听套接字 绑定在 0.0.0.0:443,并调用 listen()。当三次握手完成、TLS 握手完成后,内核把这个已建立的连接放入 接受队列。Nginx 的 worker 进程通过 epoll/kqueue 等机制获知新连接,调用 accept() 得到一个 新的连接套接字(源端口 33333,与你通信)。之后 Nginx 使用这个套接字读写数据。

TLS 解密后,Nginx 读到了完整的 HTTP 请求:

  • 检查 Host 头,匹配 server_name blog.yourdomain.com 的虚拟主机配置。
  • 看到路径 /articles/1,按 location 规则处理:

    • 静态文件:如果请求的是 /static/logo.png,Nginx 直接从 /var/www/... 读取文件内容,构造 200 响应返回。
    • 动态请求:如果后端是 PHP(你提到的 “真正的页面/数据/逻辑”),Nginx 会通过 FastCGI 协议 把请求转发给 php-fpm

      • php-fpm 通常监听一个 Unix Domain Socket(如 /run/php-fpm.sock)或 TCP 端口(127.0.0.1:9000)。
      • 这也是套接字!Nginx 作为客户端,php-fpm 作为服务端,通过这个套接字传输 FastCGI 协议数据。
      • php-fpm 执行你的 PHP 代码(读取数据库、生成 HTML),把结果返回给 Nginx。

Nginx 将响应组装成 HTTP 报文:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 5123
...(其他头部)
<html>...真正的页面...</html>

将这段数据通过之前与浏览器建立的连接套接字写回,内核负责加密(TLS)、分段、打包,沿原路发回给客户端。


7. 响应回程与浏览器渲染

回程的 IP 包目标地址是你路由器的公网 IP,NAT 设备根据之前记录的映射,把目标地址转回你的内网 IP 192.168.1.10:54123,发给你的电脑。

浏览器从自己的套接字读取到 HTTP 响应,解析状态码、头部、HTML。然后:

  • 解析 HTML 过程中,遇到图片、CSS、JS 等外部资源,会发起新的请求(可能复用已有的 TCP 连接,即 HTTP/1.1 Keep-Alive 或 HTTP/2 多路复用,避免重复握手)。
  • 渲染引擎逐步构建 DOM、CSSOM,最终呈现页面。

你那条线的“升级版”

你原先的链路:

客户端 → DNS → Nginx → /var/www → Nginx → 客户端

现在可以理解为一条充分展开的版本:

浏览器(URL解析,构造HTTP)
  → DNS(递归/迭代获取IP)
  → 创建套接字,TCP三次握手 + 可能的TLS握手
  → NAT转换(客户端侧)
  → 互联网路由(多跳转发)
  → 服务器内核协议栈(IP/TCP解包)
  → Nginx监听套接字接受连接,解密TLS,解析HTTP
  → 若静态:直接读 /var/www;若动态:通过 FastCGI/uWSGI 等套接字与后端(PHP/Python等)通信
  → Nginx 构造HTTP响应
  → 经内核、网络、NAT原路返回
  → 浏览器解析渲染

协议分层是贯穿始终的骨架:

  • 应用层:HTTP、DNS、FastCGI
  • 传输层:TCP、UDP
  • 网络层:IP、路由
  • 链路层:以太网、ARP

套接字则是应用层与传输层之间的编程界面,让 Nginx、浏览器等不必操心 TCP 细节,就能“像读写文件一样”收发网络数据。你搭博客时也许没直接碰过 socket 代码,但 Nginx 的 listen 443;、php-fpm 的 listen = /run/php-fpm.sock 本质上都是在配置套接字。

这样,整个“互联网是怎么连接的”就有了一个丰满、准确的图景。