服务器原理 (Server)

1. 定义

服务器 (Server) 本质上就是一台一直开着、等着响应别人请求的电脑。它和你自己的电脑没有本质区别,区别只在角色配置侧重

  • 角色:你的电脑是客户端 (Client),主动发请求;服务器是服务端,被动等请求、给回应。
  • 配置:服务器强调稳定和并发(多核 CPU、大内存、冗余电源、7×24 不关机),而不是好看的屏幕或游戏显卡。

一句话概括:服务器 = 一个死循环——绑定端口 → 监听 → 收到请求 → 处理 → 返回 → 继续监听。

类比:服务器像餐厅的后厨窗口。窗口一直开着(监听),顾客(客户端)来点单(请求),后厨做好从窗口递出(响应),然后继续等下一位。你(客户端)的角色是来点单的顾客,不是开窗口的。

2. “服务器”这个词的三层含义(最容易混)

中文里”服务器”经常混用,其实指三种不同的东西:

层面指的是例子
硬件一台物理机器机房里的主机、买的云服务器 ECS/EC2
软件处理请求的程序Nginx、Apache、一个 Node.js 进程
服务提供的某种能力Web 服务、数据库服务、邮件服务

关键认知:一台硬件服务器上可以同时跑很多个软件服务器,提供很多种服务。 说”我买了台服务器”通常指硬件;说”起了个服务器”通常指软件进程。别把三者划等号。

3. 核心模型:C/S 与请求-响应

整个服务器世界都围绕一个循环转——请求-响应 (Request-Response)

客户端  ────────── 请求 (Request) ─────────▶  服务器
客户端  ◀───────── 响应 (Response) ─────────  服务器
  • C/S 模型 (Client/Server):一方发起、一方响应的分工模式。浏览器、App、命令行工具都是客户端。
  • 无状态 (Stateless):HTTP 本身每次请求相互独立,服务器默认不记得你上次是谁。“记住登录状态”要靠额外机制(Cookie/Session/Token)补上,见第 9 节。
  • 对比 P2P:C/S 是”中心化”的(大家都找同一台服务器);P2P(如 BT 下载)没有固定服务器,每个节点既是客户端又是服务器。

4. 怎么找到一个服务:IP + 端口

网络上定位一个服务靠两样东西:

概念作用类比
IP 地址定位到哪台机器小区的门牌号
端口号 (Port)定位到机器上的哪个程序门牌号里的哪个房间
  • 一台机器有 0~65535 共 65536 个端口,一个端口同一时刻通常只能被一个程序占用(所以会有”端口被占用”报错)。
  • 约定俗成的端口:
端口服务端口服务
80HTTP443HTTPS
22SSH3306MySQL
5432PostgreSQL6379Redis
27017MongoDB25SMTP(邮件)

http://1.2.3.4:80 读作”去 1.2.3.4 这台机器的 80 号窗口办事”。浏览器访问 http:// 默认补 80,https:// 默认补 443,所以平时不用写端口。

域名(如 example.com)只是给 IP 起的好记名字,访问前要先经过 DNS 解析翻译成 IP,见 dns-binding.md

5. 服务器程序的本质:监听循环

剥开框架,任何服务器程序核心都是这几步(以 Web 服务器为例):

1. 创建 socket,绑定 (bind) 到某个 IP:端口   ← "我在 80 端口开张了"
2. 开始监听 (listen)                          ← "开门等客"
        │
        ▼   ┌──────────── 死循环 ────────────┐
3. 接受 (accept) 一个连接                      │  来一个客户端就建立一条连接
4. 读取请求 (read) → 解析                      │
5. 处理:查文件 / 跑逻辑 / 查数据库             │
6. 写回响应 (write)                            │
7. 关闭或复用连接 ────────── 回到 3 ───────────┘

最小可运行的例子(Node.js,能直观看到”监听循环”):

const http = require('http');
 
// createServer 传入的回调 = 每来一个请求就被调用一次(就是循环里的"处理"步)
const server = http.createServer((req, res) => {
  console.log(`收到请求: ${req.method} ${req.url}`); // 解析请求
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('你好,我是服务器\n');                      // 写回响应
});
 
server.listen(3000, () => {                          // 绑定 + 监听 3000 端口
  console.log('服务器在 http://localhost:3000 监听中...');
});
node server.js
# 另开终端测试:
curl http://localhost:3000        # 命令行当客户端
# 或浏览器打开 http://localhost:3000

你平时用的 Nginx、Express、Flask、Spring 等,只是把这套 socket 循环封装好了,让你专注写”第 5 步:处理逻辑”,不用手搓底层。

这里反复出现的 socketbindlistenaccept 到底是什么?它们是操作系统提供的网络编程接口,是服务器通信真正的地基,详见 socket.md

6. 一次 HTTP 请求的完整旅程

在浏览器输入 https://example.com/index.html 到看到页面,发生了什么:

1. DNS 解析     example.com ──▶ 1.2.3.4          (见 dns-binding.md)
                注:第 2 步"建立连接"就是靠 socket 完成的,见 socket.md
2. 建立连接     与 1.2.3.4:443 建 TCP 连接(三次握手)
3. TLS 握手     HTTPS 协商加密密钥(http 则跳过这步)
4. 发送请求     GET /index.html HTTP/1.1
                Host: example.com
5. 服务器处理   找到文件 / 跑后端代码 / 查数据库
6. 返回响应     HTTP/1.1 200 OK
                Content-Type: text/html
                <html>...</html>
7. 浏览器渲染   解析 HTML,再去请求里面的 css/js/图片(回到第 1 步循环)
8. 关闭/复用    HTTP/1.1 默认长连接 (keep-alive),复用连接省握手开销

请求报文长什么样(可以用 curl -v 看到):

GET /index.html HTTP/1.1        ← 请求行:方法 + 路径 + 协议版本
Host: example.com               ← 请求头:一堆 key: value
User-Agent: curl/8.0
Accept: */*
                                ← 空行分隔头和体
(GET 通常没有请求体;POST 的表单/JSON 数据放这里)

响应报文

HTTP/1.1 200 OK                 ← 状态行:协议 + 状态码 + 描述
Content-Type: text/html         ← 响应头
Content-Length: 1234
                                ← 空行
<html>...</html>                ← 响应体:真正的内容

常见状态码(服务器用它告诉客户端结果):

含义记忆
2xx成功200 OK201 Created
3xx重定向301 永久跳转、302 临时跳转、304 用缓存
4xx客户端错了400 请求格式错、401 没登录、403 没权限、404 找不到
5xx服务器错了500 内部错误、502 网关错误、503 服务不可用

记法:4xx 是”你(客户端)的锅”,5xx 是”我(服务器)的锅”。 排错时先看状态码就能定位是哪边的问题。

7. 并发:一台服务器怎么同时伺候成千上万人

这是服务器最核心的工程问题。如果第 5 节的循环一次只处理一个请求,第二个人就得干等。解决思路演进如下:

模型做法优点缺点代表
多进程每个请求 fork 一个进程隔离好、稳定进程重,开销大早期 Apache (prefork)
多线程每个请求开一个线程比进程轻线程多了切换开销大、易踩共享数据的坑传统 Java 服务器
IO 多路复用一个线程用 epoll/kqueue 同时盯上万个连接,谁有数据就处理谁单线程扛超高并发、省资源编程模型复杂(回调/事件)Nginx、Redis
异步/协程用 async/await、协程把”等 IO”的时间让出去干别的高并发 + 写起来接近同步代码需语言/框架支持Node.js、Go、Python asyncio

关键洞察:Web 服务大部分时间在(等数据库、等磁盘、等网络),CPU 其实很闲。所以与其”一个请求占一个线程死等”,不如”等的时候先去处理别人”——这就是 IO 多路复用和异步的核心思想,也是 Nginx/Node 能用很少资源扛高并发的原因。

对比:CPU 密集型任务(视频编码、大量计算)反而适合多进程/多线程吃满多核;IO 密集型(大部分 Web)适合异步。选型看你的瓶颈在 IO 还是 CPU。

8. 静态服务器 vs 动态服务器

按”服务器干多少活”分两类,这直接决定性能和架构:

静态服务器动态服务器
干什么请求啥就返回现成的文件现场跑代码、查库、拼出结果
速度快(直接发文件)较慢(要计算)
例子返回 html/css/js/图片返回个性化的用户主页、搜索结果
典型软件Nginx、CDNNode/Flask/Spring 后端

这块和”网页”层面的静态/动态是一回事的两面,详见 ../web/static-vs-dynamic-pages.md。实际架构里常组合使用:Nginx 扛静态资源和入口,动态请求转发给后端程序处理(见下一节反向代理)。

9. 常见配套角色(真实架构里绕不开的几个词)

正向代理 vs 反向代理

正向代理(帮客户端):  客户端 ─▶ 代理 ─▶ 目标服务器
                       (代理替客户端出面,如科学上网、公司统一出口)

反向代理(帮服务器):  客户端 ─▶ 反向代理 ─▶ 后端服务器群
                       (客户端以为代理就是服务器,代理背后藏着一堆真机器)
  • 反向代理(如 Nginx)是重点:它站在后端前面,负责转发、负载均衡、SSL 卸载、缓存、限流。你访问一个大网站,摸到的第一层几乎都是反向代理。

负载均衡 (Load Balancing)

一台机器扛不住,就用多台,前面放个负载均衡器按策略(轮询/最少连接/IP 哈希)把请求分给不同后端。这是”横向扩展 (scale out)“的基础——加机器而不是换更强的机器。

会话保持:Cookie / Session / Token

因为 HTTP 无状态(第 3 节),服务器要”认人”靠这些:

  • Cookie:服务器发给浏览器、浏览器每次自动带上的小纸条
  • Session:服务器端存用户状态,Cookie 里只放一个 sessionId 当钥匙
  • Token(如 JWT):把状态签名后交给客户端保管,服务器不用存,天然适合分布式/多机

⚠️ 一旦是多台服务器 + 负载均衡,Session 存在单机内存里就会出问题(下次请求被分到别的机器就”不认识你了”)。解决办法:Session 集中存 Redis,或改用无状态的 Token。这是从单机走向多机时最常踩的坑。

10. 服务器”跑在哪”:物理机 → 虚拟机 → 云 → 容器

形态是什么特点
物理机 (裸金属)一台真实的机器性能强、独占,但要自己维护机房、扩容慢
虚拟机 (VM)一台物理机切成多台”虚拟电脑”隔离好、灵活,带完整 OS 较重
云服务器云厂商按需租给你的虚拟机(ECS/EC2)分钟级开通、弹性扩缩、按量付费,免自建机房
容器 (Container)共享内核的轻量隔离环境秒级启动、极轻、一致性好,现代部署主流

演进逻辑:物理机太笨重 → 虚拟机把一台切多台提升利用率 → 云把虚拟机变成”水电”按需取用 → 容器进一步把”应用+环境”打包得更轻更快。容器详见 ../docker/docker.md;管一大群容器则用 Kubernetes。

11. 常见坑 / 误区

  • ❌ “服务器是一种特殊的机器” → 本质就是台一直开着当服务端的电脑,你的笔记本装个 Nginx 也能当服务器
  • ❌ “一台服务器只能跑一个服务” → 一台机器可跑很多软件服务器、监听很多端口
  • ❌ “端口随便填” → 1024 以下是系统保留端口(需管理员权限),且要避开已被占用的端口;lsof -i :3000 可查谁占用了端口
  • ❌ “改了代码服务器会自动生效” → 生产环境通常要重启进程或热重载才生效;开发时用 nodemon 等工具自动重启
  • ❌ “5xx 一定是我代码的错” → 502/504 常是反向代理连不上后端(后端挂了/超时),不一定是业务代码
  • ❌ “多台服务器直接堆上去就行” → Session 存单机内存会导致登录态错乱(见第 9 节),要先做无状态化
  • ❌ “本地能访问就是部署成功了” → localhost/127.0.0.1 只有本机能访问;要外部访问得监听 0.0.0.0 并开放防火墙/安全组端口
  • ❌ “服务器开着就安全” → 对外暴露的服务是攻击面,未加认证的接口、默认密码的数据库端口极危险,务必配好防火墙和鉴权

12. 延伸阅读 / 关联概念

  • Socket(套接字) — 服务器监听循环底下真正干活的通信端点,bind/listen/accept 的原理;见 socket.md
  • SSHsshd 如何在 22 端口提供加密远程登录、密钥认证和端口转发;见 ssh.md
  • 静态 vs 动态网页 — 服务器”干多少活”在页面层面的体现;见 ../web/static-vs-dynamic-pages.md
  • DNS 解析绑定 — 请求旅程的第一步,域名如何变成服务器 IP;见 dns-binding.md
  • ping / traceroute — 验证到服务器的网络是否可达;见 ping.md
  • Docker 容器 — 现代服务器部署的主流形态;见 ../docker/docker.md
  • HTTP / HTTPS — 服务器与客户端对话的应用层协议,请求-响应的载体;见 http.md
  • TCP / UDP — 服务器通信的传输层底座,三次握手、可靠性原理;见 tcp-udp.md
  • Nginx — 最常见的反向代理 + 静态服务器 + 负载均衡器
  • Kubernetes (k8s) — 管理成百上千容器化服务器的编排系统