找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4602

积分

0

好友

600

主题
发表于 昨天 20:59 | 查看: 7| 回复: 0

当你在浏览器输入一个网址,真正发生了什么?

打开浏览器,输入:

https://example.com

按下回车。几百毫秒之后,一张网页出现在屏幕上。对于绝大多数人来说,这个动作简单得近乎没有存在感。但从计算机系统的角度看,这其实是一场跨越多个系统层次的协作。

你的键盘产生输入,浏览器解析 URL,系统尝试找到域名对应的 IP 地址,网络协议栈准备数据。可能需要建立 TCP 连接;如果是 HTTPS,还需要建立 TLS 会话并验证服务器身份。HTTP 请求被封装进网络数据包,数据经过操作系统、网卡、家庭路由器、运营商网络、互联网骨干网络、中间路由设备,最终抵达服务器所在的网络。

服务器收到数据后,再把它交给自己的网络协议栈、Web Server、应用程序。应用程序开始执行:可能查询数据库,可能访问缓存,可能调用其他服务,可能读取文件,然后生成 HTTP 响应。响应再次经过网络返回浏览器。最后,浏览器把 HTML、CSS、JavaScript、图片、字体等资源组织起来,交给渲染引擎。

你看到的,不是一台计算机完成的结果,而是一整套分层系统完成的结果。

互联网并不是“浏览器连接服务器”这么简单。

真正重要的问题是:浏览器为什么能够找到服务器?服务器为什么能够知道请求来自谁?数据为什么能够跨越这么多网络设备最终抵达目标?TCP、TLS、HTTP 分别解决什么问题?当网络发生丢包、延迟、拥塞时,系统又是如何继续工作的?

而更重要的是:

为什么今天的 Web 是这样设计的?

理解这些问题,才算真正开始进入网络编程。


一、先不要学 Go:先理解互联网到底解决了什么问题

互联网最本质的问题其实很简单:

两台不认识的计算机,如何交换数据?

假设你的电脑是:

Client

服务器是:

Server

最开始,我们可以非常粗暴地想:

Client ----------------------> Server
             "Hello"

看起来没有什么复杂的。但这个模型马上会遇到问题。服务器在哪里?如果服务器地址是:

192.168.1.10

那互联网里的几十亿设备怎么知道它在哪里?如果网络中间有设备丢掉了数据怎么办?如果数据被别人偷看怎么办?如果客户端一次发送 1 KB,服务器只收到 500 字节怎么办?如果服务器同时面对一万个客户端怎么办?如果客户端突然断网怎么办?如果一个服务器有多个网站怎么办?

如果一个网页包含:

index.html
style.css
app.js
logo.png
avatar.jpg
font.woff2

浏览器又该如何知道这些资源从哪里获取?

所以互联网真正解决的,从来不是单纯的“把数据从 A 发给 B”,而是:

在一个不可靠、规模巨大、设备类型复杂、网络质量不断变化的环境中,让不同计算机能够可靠地交换信息。

这也是网络协议存在的原因。协议不是为了增加复杂度,恰恰相反,协议是在控制复杂度。


二、为什么不能让所有程序直接通信?

假设我们没有任何协议,浏览器自己规定一套格式:

HELLO|URL|DATA

另一个程序又规定:

REQUEST@URL@DATA

第三个程序可能使用:

<request>
    <url>...</url>
</request>

那么互联网很快就会变成一片混乱。所以计算机网络采取了一个非常重要的思想:

分层。

一个系统不要试图一次解决所有问题,而是让每一层只解决自己负责的问题。可以把一次 Web 请求粗略理解成:

应用层
│
│ HTTP
▼
传输层
│
│ TCP / QUIC
▼
网络层
│
│ IP
▼
链路层
│
│ Ethernet / Wi-Fi
▼
物理介质
│
▼
电信号 / 无线信号

这意味着:HTTP 不需要知道电缆是怎么传输比特的,TCP 不需要知道 HTTP 请求到底是网页、JSON 还是图片,IP 不需要知道上面传的是 HTTP 还是其他协议。每层处理自己的问题,于是复杂系统被拆成了一组可以组合的系统。

这套思想后来影响了整个软件工程。微服务、模块化、操作系统、编译器、网络协议,本质上都在不断重复这个模式:

把一个巨大的复杂问题拆成多个边界清晰的问题。


三、第一步:浏览器为什么知道 example.com 在哪里?

你输入的是:

https://example.com

但计算机真正进行网络通信时,需要的是 IP 地址。例如:

93.184.216.34

这里存在一个巨大的认知差异:人类喜欢名字,机器更容易使用地址。DNS 就是解决这个问题的系统。DNS,全称 Domain Name System,是一个分层、分布式的命名系统,用来把人类可读的域名映射到 IP 地址等资源。于是:

example.com
       │
       │ DNS
       ▼
93.184.216.34

这一步经常被理解成“DNS = 域名转 IP”,但这个理解还不够。DNS 真正有价值的地方,是它把:

“服务的名字”与“服务当前位于哪里”

进行了分离。这是一种极其重要的架构思想。


四、为什么不能直接写死 IP?

假设我们直接把 example.com 写成 93.184.216.34,看起来更直接。但现实系统很快会崩掉。因为服务器会扩容,可能从 1 台变成 10 台,再变成 100 台,甚至不同区域拥有不同服务器。

于是 example.com 并不一定只对应一个 IP,它更像:

example.com
       │
       ├── IP A
       ├── IP B
       ├── IP C
       └── IP D

DNS 由此成为整个互联网基础设施里非常重要的一层。这也是为什么域名不仅仅是一个“好记的名字”,它实际上是一层:

位置抽象。

应用不需要知道服务器的物理位置,只需要知道服务名称。这种思想在后面的服务发现、微服务、API Gateway、Kubernetes Service、Service Mesh 中还会再次出现。所以网络知识学到最后,你会发现:DNS 并不只是一个协议,它体现的是一种现代系统设计思想:

名称与位置解耦。


五、找到服务器以后,下一步是什么?

假设 DNS 已经给我们:

93.184.216.34

现在问题变成:我的数据怎么到这台机器?这时候 IP 出场。可以先把 IP 理解成:

互联网世界里的寻址系统。

数据被切分成网络层能够处理的数据包,然后经过一个又一个网络节点:

Browser
   │
   ▼
Operating System
   │
   ▼
Wi-Fi / Ethernet
   │
   ▼
Home Router
   │
   ▼
ISP
   │
   ▼
Backbone Network
   │
   ▼
Router
   │
   ▼
Data Center Network
   │
   ▼
Server

这里有一个很重要的事实:

互联网不是一条从浏览器直接连到服务器的线路。

而是一系列互联网络组成的网络。每一个路由设备都在做同一个判断:“这个包下一步应该往哪里走?”因此:

A → B → C → D → Server

并不是一个永久固定的物理路径。网络状况发生变化时,路径可能改变。这就是为什么 IP 解决的是“往哪里走”,而不是“最终一定能完整地把数据交付给应用程序”。后一个问题需要更高的协议来解决。


六、IP 能把包送到服务器,但它保证不了什么?

假设浏览器发出去:

1
2
3
4
5

网络中发生:

1 ✅
2 ✅
3 ❌
4 ✅
5 ✅

服务器收到:

1
2
4
5

IP 不负责解决“第 3 个数据哪里去了”,这就是传输层的问题。


七、TCP:互联网为什么还能“可靠”工作?

TCP 是 Internet 中最重要的传输协议之一。它负责为应用程序提供可靠的字节流传输机制,并定义了连接建立、数据传输、重传等行为。现代 TCP 的标准规范已经由 RFC 9293 统一整理。

可以简单理解成:

HTTP
  │
  ▼
TCP
  │
  ▼
IP

HTTP 关心的是“我要请求什么资源”,TCP 关心的是“这些数据怎么可靠地送过去”。这两个问题完全不同。


八、TCP 的核心不是“三次握手”,而是可靠传输

网络教程最喜欢讲 TCP 三次握手,然后背:

SYN
SYN + ACK
ACK

这当然没有错。但如果只记住“三次握手”,实际上几乎没有理解 TCP。TCP 真正解决的是:

在一个可能丢包、乱序、延迟、重复的数据网络之上,为应用提供可靠、有序的字节流。

例如应用程序发送:

ABCDEFGHIJ

网络中可能发生:

A B C D
      ↓
      丢失
E F G
        ↓
        延迟
H I J

TCP 在两端维护状态,通过序列号、确认、重传、流量控制、拥塞控制等机制,让应用层最终看到的是一个连续的数据流。于是应用程序不需要自己处理“第 17 个包在哪里”“第 18 个包有没有重复”“刚才是不是丢包”“网络是不是拥塞”,这些复杂性被封装在 TCP 里面。这就是分层设计真正的价值:

应用程序把网络传输的复杂性下沉给传输层。


九、但 TCP 也不是万能的

TCP 的可靠性是有成本的。它需要维护:

Sequence Number
ACK
Retransmission
Receive Window
Congestion Window
Connection State

这些状态都会产生开销。尤其是在现代互联网环境中,一个页面可能同时涉及很多资源。于是新的问题出现:如果一个 TCP 连接中的某部分数据出现问题,会发生什么?

在 HTTP/2 中,多路请求可以共享一个 TCP 连接,这样连接数量减少了。但 TCP 自己的可靠字节流语义仍然存在,这意味着某些情况下,一个丢失的 TCP 数据包会影响该连接上其他正在等待的数据。

HTTP/3 采用了不同的路径:

HTTP/3
   │
   ▼
QUIC
   │
   ▼
UDP
   │
   ▼
IP

HTTP/3 是把 HTTP 语义映射到 QUIC 之上,而 QUIC 提供多流、每流流控、低延迟建立和连接迁移等能力。这不是“UDP 比 TCP 高级”,真正的变化是:

一些原本属于 TCP 的传输控制能力,被重新组织到了 QUIC 中。

这是网络工程非常典型的演进方式:不是抛弃旧问题,而是重新划分边界。


十、现在回到浏览器:我们真正访问的是 HTTPS

现实世界中的浏览器访问,大量使用的是 HTTPS,而不是裸 HTTP。HTTPS 的核心思想是:

HTTP
+
TLS

TLS 的目标是让客户端和服务器之间的通信具备机密性、完整性,并帮助完成身份认证。TLS 1.3 的标准定义在 RFC 8446 中。所以:

HTTP
  ↓
TLS
  ↓
TCP
  ↓
IP

可以先这么理解。


十一、为什么需要 TLS?

假设你在浏览器里登录:

username = alice
password = 123456

如果数据直接裸奔:

Internet
─────────────────────

username=alice
password=123456

中间任何能够观察网络流量的设备,都可能看到数据。所以需要加密。最终你看到的是类似:

Client
   │
   │ encrypted data
   ▼
Internet
   │
   ▼
Server

中间节点看到的是密文,而不是你的 HTTP 内容。但 TLS 不只是加密。真正完整的问题是:

我怎么知道对面的服务器真的是我想访问的服务器?

这就是数字证书、证书链和身份认证等机制发挥作用的地方。


十二、一次 HTTPS 请求,究竟经历了什么?

现在把前面的知识串起来。假设你输入:

https://example.com

可以建立一个非常简化的模型:

① 浏览器解析 URL

https://example.com/
        │
        ▼

② DNS 查询

example.com
        │
        ▼
IP 地址

③ 建立传输连接

TCP / QUIC
        │
        ▼

④ 建立加密会话

TLS
        │
        ▼

⑤ 发送 HTTP 请求

GET /
Host: example.com

        │
        ▼

⑥ 服务器处理

        │
        ▼

⑦ HTTP Response

200 OK
...

这才是浏览器打开网页的基本骨架。


十三、HTTP 到底是什么?

HTTP 是应用层协议,它解决的问题非常具体:

客户端和服务器之间,如何表达请求与响应。

例如:

GET /index.html HTTP/1.1
Host: example.com
Accept: text/html

服务器可能返回:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234

再加上真正的数据:

<!doctype html>
<html>
    ...
</html>

所以 HTTP 并不负责 DNS、路由、TCP 可靠性、电缆传输、网卡驱动。HTTP 只需要定义“请求长什么样”以及“响应长什么样”。这就是应用层协议的边界。


十四、HTTP 为什么没有直接把互联网设计完?

因为 HTTP 不应该负责所有事情。这就是软件架构里的一个重要原则:

职责边界。

HTTP 负责资源、请求、响应;TCP 负责可靠传输;IP 负责寻址与路由;TLS 负责安全通信;DNS 负责名称解析;浏览器负责资源加载、页面执行与渲染;服务器应用负责业务逻辑;数据库负责数据持久化。最终形成一张非常复杂但边界清晰的系统。


15. HTTP 本身也一直在进化

Web 最初非常简单。早期 HTTP 的目标只是让浏览器能够获取超文本,但网页后来越来越复杂。一个页面可能包含 HTML、CSS、JavaScript、Image、Video、Font、JSON、WebSocket 等资源。浏览器与服务器之间的通信压力越来越大,因此 HTTP 一直在演进。可以粗略理解成:

HTTP/0.9
   ↓
HTTP/1.0
   ↓
HTTP/1.1
   ↓
HTTP/2
   ↓
HTTP/3

HTTP/1.1 的消息结构非常直观,但随着 Web 复杂度增加,连接复用、多路复用、头部压缩等需求越来越明显。HTTP/2 引入了二进制分帧和多路复用。而 HTTP/3 则把 HTTP 语义映射到了 QUIC 上——RFC 9114 明确规定 HTTP/3 使用 QUIC 作为传输层,并利用其流和流控能力。

因此,HTTP/3 并不是“HTTP 4 的重新发明”,而是:

重新思考 HTTP 与传输层之间的边界。


十六、为什么 HTTP/3 会使用 UDP?

这是网络学习里最容易出现误解的地方。很多人会说“UDP 更快,所以 HTTP/3 用 UDP”,这句话太简单。真正的原因是:

QUIC 希望在用户空间重新构建现代传输协议所需要的机制,同时利用 UDP 作为底层数据报承载。

QUIC 本身提供流、流量控制、低延迟连接建立、丢包检测、连接迁移、加密能力。因此更准确的关系是:

HTTP/3
   ↓
QUIC
   ↓
UDP
   ↓
IP

而不是 HTTP/3 直接跳到 UDP。它们之间还隔着一个非常重要的传输协议:QUIC。


十七、服务器收到 HTTP 请求以后,发生了什么?

现在终于可以进入服务器。例如:

GET /users/123 HTTP/1.1
Host: example.com

数据到达服务器之后,并不会直接跑进 func main() 中。中间还隔着操作系统网络栈。可以粗略理解成:

Network Card
      │
      ▼
Linux Kernel
      │
      ▼
TCP / UDP
      │
      ▼
Socket
      │
      ▼
Go Runtime / net package
      │
      ▼
net/http
      │
      ▼
Handler
      │
      ▼
Business Logic

这个结构非常重要,因为它解释了:

网络编程为什么从来不仅仅是“写一个 HTTP Handler”。

你写的业务代码其实处在整个网络栈的最上层。


十八、Go 是怎么进入这个体系的?

这时候 Go 才真正出现。Go 标准库提供了 net 处理网络基础能力,以及 net/http 处理 HTTP。所以一个最简单的 Go HTTP 服务器可以写成:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello from Go 1.26.5")
    })

    log.Println("server listening on :8080")

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

使用 Go 1.26.5 运行,然后浏览器访问 http://127.0.0.1:8080/。浏览器发起 GET /,Go 程序返回:

Hello from Go 1.26.5

看起来只有几十行代码,但实际上,这几十行代码站在了非常厚的系统之上。


十九、这就是 Go 最厉害的地方之一

你没有亲自实现 TCP、DNS、HTTP parser、socket、epoll、线程池,但是你可以直接写:

http.HandleFunc(...)

这并不意味着 Go“把网络变简单了”,真正发生的是:

Go 把复杂性封装到了标准库、Runtime 和操作系统接口之后。

与此同时,它又没有把底层彻底隐藏。你依然可以继续向下:

net/http
   ↓
net
   ↓
Syscall
   ↓
Kernel

因此 Go 很适合作为服务器语言,因为它在工程效率和系统控制能力之间提供了一个很有价值的平衡。


二十、Go 的 HTTP 服务器为什么能同时处理很多请求?

例如:

http.HandleFunc("/", handler)

你没有写 ThreadPool,也没有自己创建 pthread,但服务器依然可以同时处理大量请求。这是因为 Go 的并发模型和 Runtime。典型的 Go 网络服务器会大量使用 Goroutine。可以简化理解成:

Incoming Request
       │
       ├───────────────┐
       │               │
       ▼               ▼
   Goroutine A     Goroutine B
       │               │
       ▼               ▼
    Handler          Handler

这并不意味着“一请求 = 一操作系统线程”。Go Runtime 会负责 Goroutine 的调度。所以:

大量请求
   ↓
大量 Goroutine
   ↓
Go Scheduler
   ↓
有限数量 OS Threads
   ↓
CPU

这正是前面并发章节中 G-P-M 模型真正开始与网络结合的地方。


二十一、网络 IO 与 Goroutine 为什么能结合得很好?

这里进入 Go 网络编程非常关键的一层:netpoll。

网络程序最浪费时间的东西,通常不是 CPU,而是等待。比如等待客户端发送数据、等待服务器读取数据、等待网络数据到达等。假设一个线程正在等待网络:

Thread
  │
  ▼
recv()
  │
  │ waiting...
  │
  │
  │

如果每一个连接都占用一个操作系统线程,那么连接数量一大,系统就会越来越昂贵。现代操作系统提供了高效的 I/O 多路复用机制。Linux 上典型的是 epoll,macOS/BSD 家族常见的是 kqueue。Go Runtime 把这些系统能力封装进了自己的网络轮询机制。所以可以粗略理解为:

Go Program
    │
    ▼
   net/http
    │
    ▼
    net
    │
    ▼
   netpoll
    │
    ├──── epoll
    ├──── kqueue
    └──── 其他平台机制
    │
    ▼
Operating System

最终形成:

Goroutine + Scheduler + Netpoll + OS Kernel

这一整套组合。这才是 Go 网络服务器性能的重要基础。


二十二、所以“Go 能处理百万连接”到底是什么意思?

这里必须非常谨慎。“百万连接”不是写一行 Go 代码然后自动获得百万并发。真实世界远比这复杂。连接数量受到内存、文件描述符、CPU、带宽、内核参数、网络设备、Goroutine 数量、业务逻辑、数据库、缓存、锁竞争、GC 等大量因素影响。

Go 能做的,是提供非常合适的基础模型。例如:

大量连接
      │
      ▼
网络事件
      │
      ▼
Netpoll
      │
      ▼
Goroutine 唤醒
      │
      ▼
Scheduler
      │
      ▼
Handler

所以真正值得学习的不是“Go 可以百万连接”,而是:

为什么一种编程语言能够把大量并发 I/O 映射到相对有限的系统线程和 CPU 资源上。

这才是系统工程问题。


二十三、一个 HTTP 请求真正穿过了多少层?

现在重新看一次:

浏览器
  │
  │ URL
  ▼
DNS
  │
  │ IP
  ▼
TCP / QUIC
  │
  ▼
TLS
  │
  ▼
HTTP
  │
  ▼
网卡
  │
  ▼
操作系统内核
  │
  ▼
网络设备
  │
  ▼
互联网
  │
  ▼
服务器
  │
  ▼
网卡
  │
  ▼
操作系统
  │
  ▼
Socket
  │
  ▼
Go Runtime
  │
  ▼
net/http
  │
  ▼
Handler
  │
  ▼
业务系统
  │
  ├── Cache
  ├── Database
  ├── RPC
  └── File

于是你会发现:

一行 http.Get() 背后,可能隐藏着整个互联网。


二十四、浏览器拿到 HTML 以后,事情还没有结束

这是很多初学者容易忽略的地方。服务器返回:

<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="app.js"></script>
</head>
<body>
<img src="logo.png">
</body>
</html>

浏览器发现 style.cssapp.jslogo.png 又要发请求。于是:

GET /
   ↓
HTML
   ↓
发现 CSS
   ↓
GET /style.css

发现 JS
   ↓
GET /app.js

发现 Image
   ↓
GET /logo.png

所以一个“打开网页”往往不是一次网络请求,而是一组请求。这也是为什么现代浏览器需要连接复用、HTTP/2 多路复用、HTTP/3、缓存、CDN、压缩、资源预加载等大量技术。Web 的复杂度,就是这样一点点增长起来的。


二十五、CDN 为什么会出现?

假设服务器在美国,用户在东京。如果所有内容都必须从东京到美国往返,延迟自然会增加。所以现代系统会增加 CDN Node,形成:

User
  │
  ▼
Nearby CDN
  │
  ├── Cache Hit
  │
  └── Cache Miss
         │
         ▼
      Origin Server

这又是一个和 DNS 类似的思想:用户不关心真正的源服务器在哪里。而系统通过 DNS、路由、CDN、Anycast、负载均衡等方式,把请求导向更合适的位置。你会发现:整个互联网一直在做同一件事情:

隐藏复杂性。


二十六、反向代理又是什么?

真实生产环境里,浏览器通常不会直接连接 Go 应用。更常见的结构是:

Browser
   │
   ▼
Internet
   │
   ▼
Load Balancer
   │
   ▼
Reverse Proxy
   │
   ├──── Go Server A
   ├──── Go Server B
   └──── Go Server C

为什么?因为业务服务器不应该承担所有边缘工作。反向代理可以负责 TLS、连接管理、限流、路由、压缩、缓存、负载均衡。Go 应用则专注业务逻辑。于是:

HTTP 服务器只是整个互联网系统中的一个组件。

不是全部。


二十七、Go 在这套体系里到底站在哪里?

现在终于可以给 Go 定位:

                Browser
                   │
                   ▼
                Internet
                   │
                   ▼
           TCP / QUIC / TLS
                   │
                   ▼
              HTTP Layer
                   │
                   ▼
              Go net/http
                   │
                   ▼
             Go Runtime
                   │
                   ▼
              OS Kernel
                   │
                   ▼
              Hardware

Go 并没有创造 TCP、IP、HTTP、DNS、TLS。Go 真正做的是:

把复杂的系统能力组织成一套开发者可以高效使用的工程工具。

这也是为什么到了 Day 61,你已经不能只把 Go 当作一门语法简单的语言。现在应该开始把 Go 放回它真正的位置:

系统软件和网络软件的工程工具。


二十八、和 C、C++、Rust、Java 比,Go 到底强在哪里?

没有一种语言在所有场景都最好。

C

C 给你极高的控制能力,你可以非常直接地接近 Memory、CPU、Syscall、Socket、Kernel。但代价是开发成本高、内存安全问题多、并发代码复杂、工程维护成本高。

C++

C++ 在 C 的基础上提供了大量抽象能力,优势非常强大,但复杂度也非常高。尤其是在大型工程里,Templates、Object Lifetime、ABI、Build System、Toolchain 都会增加认知负担。

Rust

Rust 非常强调内存安全与接近零成本抽象,对于系统软件、网络基础设施、高性能组件、安全关键代码非常有价值。但它把大量复杂性提前交给编译器和类型系统,这意味着学习成本通常比 Go 更高。

Java

Java 最大的优势之一是巨大生态、JVM、成熟企业工具链,特别适合企业应用、大型业务系统、长期维护系统。但它的运行时模型与 Go 不同。

Go

Go 的设计目标非常不同。它并不追求让语言本身拥有最多功能,而更关注简单语法、快速编译、清晰工程结构、高效并发、成熟网络库、可部署性、可维护性。因此 Go 的核心价值从来不是“性能绝对第一”,而是:

在现代服务器开发中,把系统复杂度控制在工程团队能够长期承受的范围内。


二十九、性能真正应该看什么?

网络程序性能不能只看 QPS。至少应该同时看 Throughput、Latency、CPU、Memory、Connections、GC、Network I/O、System Calls。

例如 QPS = 100,000 听起来很厉害,但是如果 P99 = 5s,那系统一样可能不可用。反过来 P99 = 5ms 但只能处理 500 QPS,同样可能不满足业务需求。所以真正的工程问题是:

在一定资源预算下,系统能否稳定地处理目标负载?

这才是性能。


三十、Cache Miss 为什么也会影响网络服务器?

假设一个请求需要访问 CPU Cache 和 Memory。CPU Cache 的速度远高于主内存,所以 Cache Hit 和 Cache Miss 可能产生明显差异。网络服务器又是高度并发的,如果多个线程或者 Goroutine 频繁访问共享数据,可能产生 Cache Miss、False Sharing、Lock Contention、Memory Traffic。

因此高性能网络服务不是“把代码写得漂亮”就够了。你最终必须理解 CPU、Cache、Memory、Scheduler、Kernel、Network 这些层如何共同作用。


三十一、系统调用也不是免费的

假设服务器频繁进行 read()write()send()recv(),每次进入内核都存在成本。可以简单理解成:

User Space
   │
   │ syscall
   ▼
Kernel Space
   │
   │ work
   ▼
Return

所以高性能服务器一直在思考:能不能减少系统调用次数?例如批处理、Buffer、零拷贝思想、连接复用、多路复用、内核旁路等,本质上都在试图减少不必要的成本。


三十二、真正优秀的网络程序,优化的不是某一行代码

它优化的是整个路径:

Browser
   ↓
DNS
   ↓
Network
   ↓
TLS
   ↓
HTTP
   ↓
Kernel
   ↓
Socket
   ↓
Runtime
   ↓
Scheduler
   ↓
Handler
   ↓
Database

任何一层成为瓶颈——CPU、Memory、Network、Kernel、Lock、Database——最终都可能拖慢整个系统。所以系统性能分析真正应该问的是“瓶颈在哪里”,而不是“哪一行代码最快”。


三十三、Go 1.26.5 在这里有什么意义?

本文虽然讨论的是互联网基础,但最终我们要用 Go 实现网络系统,所以版本必须明确。本文以:

go1.26.5

为基准。Go 1.26 是 2026 年 2 月发布的大版本,之后继续发布了多个维护版本;官方发布历史显示,Go 1.26.5 于 2026 年 7 月 7 日发布,包含针对 crypto/tlsos、编译器、Runtime、go 命令以及 netsyscall 等组件的安全修复和 bug 修复。Go 1.26 本身还带来了新的 Runtime 与工具链变化,例如 Green Tea GC 默认启用,以及部分编译器和运行时性能改进。

不过对于 Day 61 来说,一个非常重要的原则是:

不要因为 Go 版本升级,就把网络基础学习变成 API 版本学习。

TCP 仍然是 TCP,DNS 仍然是 DNS,HTTP 的分层思想也没有改变。版本变化真正影响的是:实现这些概念时,Go 的标准库、Runtime 和工具链今天是什么样子。


三十四、用 Go 观察一次 HTTP 请求

我们可以先写一个简单的服务器:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("method:", r.Method)
    fmt.Println("url:", r.URL.String())
    fmt.Println("host:", r.Host)
    fmt.Println("remote:", r.RemoteAddr)

    fmt.Fprintln(w, "hello")
}

func main() {
    http.HandleFunc("/", handler)

    log.Println("listening on :8080")

    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

浏览器访问 http://127.0.0.1:8080/,服务器可能打印:

method: GET
url: /
host: 127.0.0.1:8080
remote: 127.0.0.1:xxxxx

这时候你应该开始意识到:GET/HostRemoteAddr 都不是 Go 创造出来的。Go 只是把 HTTP 世界映射到了程序模型。也就是说:

Internet Protocol
        ↓
Go API
        ↓
Go Program

这是非常重要的认知。


三十五、再写一个 Go HTTP Client

Go 不只能够创建服务器,也能发起 HTTP 请求:

package main

import (
    "fmt"
    "log"
    "net/http"
    "io"
)

func main() {
    resp, err := http.Get("https://example.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("status:", resp.Status)
    fmt.Println(string(body))
}

这个程序看起来非常简单。但 http.Get(...) 背后可能涉及 URL 解析、DNS 查询、TCP 建连、TLS 握手、HTTP 请求、Socket 操作、Runtime 调度、Kernel 系统调用。这就是为什么网络编程值得单独拿出一个阶段来学习。


三十六、为什么网络编程必须从“请求的生命历程”开始学习?

因为网络 API 很容易让人产生错觉。例如 http.Get(url) 可能让你以为“发送 HTTP 请求就是调用一个函数”。但真正的事实是:

Function Call
    ↓
HTTP Client
    ↓
Connection
    ↓
TLS
    ↓
Socket
    ↓
Kernel
    ↓
Network
    ↓
Remote Kernel
    ↓
Remote Socket
    ↓
HTTP Server
    ↓
Application

所以真正成熟的网络工程师,遇到问题时不会只看 http.Get(...),而会问:DNS 是不是慢?TCP 建连是不是慢?TLS 握手是不是慢?服务端是不是没及时接受连接?内核是不是存在队列压力?Handler 是不是阻塞?数据库是不是慢?网络是不是丢包?

这就是:

从 API 思维走向系统思维。


三十七、互联网最大的工程思想:每一层只做自己的事情

从今天开始,你应该慢慢建立一张这样的地图:

DNS
│
└── 我是谁?

IP
│
└── 我去哪里?

TCP / QUIC
│
└── 怎么传?

TLS
│
└── 怎么安全地传?

HTTP
│
└── 我们要交换什么?

Go net/http
│
└── 程序怎么处理?

Go Runtime
│
└── 并发任务怎么运行?

OS Kernel
│
└── 网络设备怎么工作?

Hardware
│
└── 最终怎么发送出去?

这张地图比记住几十个 API 更重要。


三十八、从这里开始,你已经进入真正的后端世界

Day 1 学的是“什么是程序”,Day 10 开始认识“为什么 Go 值得学习”,Day 30 完成 Go 语言基础,Day 45 进入软件工程,Day 46 开始并发。到了今天 Day 61:

我们终于把 Go 放到了互联网里。

接下来,Go 不再只是 varfuncstructinterface,而开始变成 Server、Socket、Connection、HTTP、TCP、Concurrency、Runtime、Network。这也是 Go 真正擅长的领域开始出现的地方。


三十九、Go 为什么在服务器领域拥有如此强的生命力?

因为现代服务器面对的核心问题,恰好是 Go 非常关注的问题:很多并发请求、很多网络连接、大量 I/O、快速部署、长期维护、团队协作、系统可观测性。Go 用 Goroutine、Channel、Context、net、net/http、Runtime 构建出了一套相对完整的服务器工程模型。

但是一定要注意:

这不意味着 Go 在所有服务器问题上都最优。

极致性能底层组件可能更偏向 C、C++、Rust;大量历史企业系统依然可能使用 Java、C#;某些 CPU 密集型任务也可能更适合其他方案。Go 的优势不是“天下第一”,而是:

在现代网络服务中,以较低的工程复杂度获得足够高的性能与并发能力。

这才是客观的评价。


四十、从今天开始,不要再把“网站”理解成一个页面

一个网站,真正可能是:

Browser
    │
    ▼
DNS
    │
    ▼
CDN
    │
    ▼
Load Balancer
    │
    ▼
Reverse Proxy
    │
    ▼
API Gateway
    │
    ├──────────────┐
    ▼              ▼
Go Service A    Go Service B
    │              │
    ▼              ▼
Redis           Database
    │
    ▼
Message Queue
    │
    ▼
Other Services

而这还只是应用层。下面还存在 TLS、TCP/QUIC、IP、Network、Kernel、NIC、Hardware。所以现代互联网不是一个浏览器加一个服务器,而是一座巨大的分布式系统。


四十一、真正值得思考的问题

理解互联网之后,一个更有意思的问题会出现:如果今天重新设计互联网,我们还会按照几十年前的方式设计吗?

事实上,答案已经正在发生。我们已经看到:

HTTP/1.1
    ↓
HTTP/2
    ↓
HTTP/3

TCP
    ↓
QUIC

Static Server
    ↓
CDN

Single Server
    ↓
Load Balancer

Monolith
    ↓
Microservices

Machine IP
    ↓
Service Discovery

整个互联网几十年来一直在变化。但有一些东西几乎没有变化:分层、抽象、解耦、隐藏复杂性。这可能才是互联网留给软件工程最重要的遗产之一。


四十二、而 Go 真正要解决的,也不是“写网页”

到了这里,我们终于可以重新理解 Go。Go 并不是专门为了写网页,而是非常适合:

构建长期运行的网络系统。

比如 HTTP Server、API Gateway、Reverse Proxy、RPC Service、Message Service、Storage Service、Control Plane、Cloud Service、Container Runtime、Infrastructure Software。这也是为什么网络会成为 Go 学习路线后半段的核心。


四十三、未来的网络程序会变成什么样?

未来的服务器不会只是 Client → Server,而会越来越像:

Client
  ↓
Edge
  ↓
CDN
  ↓
Gateway
  ↓
Service Mesh
  ↓
Microservices
  ↓
Cache
  ↓
Database
  ↓
Distributed Storage
  ↓
AI Service

协议也不会停留在 TCP + HTTP/1.1,而会继续向 HTTP/2、HTTP/3、QUIC 以及下一代传输与安全技术演进。所以学习网络的真正价值,并不是为了知道 http.Get() 怎么调用,而是为了知道:

一条数据从一个程序走向另一个程序时,整个系统究竟发生了什么。


结语:当你输入一个网址的时候,互联网其实已经开始“工作”了

下次你在浏览器输入 https://example.com,不要再把它理解成“浏览器打开了一个网页”。试着把整个过程在脑子里重新播放一遍:

URL
  ↓
DNS
  ↓
IP
  ↓
TCP / QUIC
  ↓
TLS
  ↓
HTTP
  ↓
Network
  ↓
Router
  ↓
Server
  ↓
Kernel
  ↓
Socket
  ↓
Go Runtime
  ↓
net/http
  ↓
Handler
  ↓
Business Logic
  ↓
Database
  ↓
Response
  ↓
Browser
  ↓
Render

你看到的那一张网页,只是整个系统最终露在你眼前的一小部分。真正值得学习的,是网页背后的那座机器。

因为当你开始理解——数据究竟是怎样从一台计算机,穿过一个由无数机器组成的全球网络,最终抵达另一台计算机的——你学习的就已经不再只是 Go。你开始学习的是:

现代计算机系统究竟是如何彼此协作的。

而这,才是网络编程真正的起点。

下一天,我们将不再停留在“互联网是什么”。我们会进一步把一条连接拆开:

TCP 到底是怎么工作的?

从一次连接的建立开始,看到 SYN、ACK、序列号、确认、重传、流量控制与拥塞控制。到了那里,你会第一次真正看到:一个看似简单的 conn.Read(),下面究竟藏着多少系统设计。

如果你对这类从底层协议到工程实践的完整拆解感兴趣,欢迎常来云栈社区逛逛,那里有不少同样在啃网络与系统方向的开发者,大家一起交流、沉淀、避坑。




上一篇:百万Goroutine全靠GMP调度?Go 1.26 Runtime系统能力拆解
下一篇:Superpowers 6.0 更新:你的 Agent 该上规矩了
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-12 00:17 , Processed in 0.343550 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表