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

2758

积分

0

好友

354

主题
发表于 10 小时前 | 查看: 2| 回复: 0

上期我们聊了TCP三次握手背后的故事,很多朋友问:“为什么每次登录网站都要输密码?Cookie到底是干嘛的?”

今天我们来追溯一下,一个工程师在咖啡时间的20分钟灵感,如何彻底改变了整个互联网的交互方式。

这个故事的主角叫 Lou Montulli,网景公司(Netscape)年仅23岁的工程师。他面临并解决了一个当时困扰所有人的核心问题:HTTP协议为什么如此“健忘”?

1994年:网上购物的技术噩梦

时间回到1994年,网景公司总部。

产品经理冲进工程部,一脸绝望:“Lou!我们的网上商城没法用了!用户把商品加入购物车,点下一页,购物车就空了!”

Lou Montulli 皱了皱眉:“怎么可能?”

产品经理打开浏览器演示:

1. 用户选择:耐克鞋(加入购物车)
2. 继续浏览,点击“下一页”
3. 购物车显示:空

用户崩溃:“我刚加的鞋呢?!”

问题出在哪?

Lou 立刻意识到了根源所在:HTTP协议是“无状态”的。

HTTP的“失忆症”:无状态协议的含义

什么叫无状态?让我们用两个场景来类比一下:

场景1:你去便利店买东西

你: “老板,来瓶可乐”
老板: “好的,10块钱”
你: “老板,再来包薯片”
老板: “好的,5块钱”
你: “老板,一共多少钱?”
老板: “15块钱”(老板记得你买了什么)

场景2:HTTP服务器

客户端: “GET /商品A”
服务器: “这是商品A的信息”(返回后断开连接)

客户端: “GET /购物车”
服务器: “购物车是什么?我不记得你买过商品A”

每次HTTP请求对服务器来说都是全新的、独立的,它不记得之前的你是谁!

这就是 Tim Berners-Lee 在1991年设计 HTTP 时的核心选择:“让协议简单点,不要维护状态,每次请求独立处理。”

这个设计在查看静态网页时完全没有问题,但一旦涉及到需要保持状态的交互(比如购物车),就立刻傻眼了。

咖啡时间的20分钟灵光一现

1994年5月,网景公司的咖啡角。

Lou Montulli 端着咖啡,盯着购物车的代码发呆。

突然,他想起了一个来自 Unix 系统的术语:Magic Cookie。

这指的是“一小段数据,在程序之间传递,用来标识身份”。

Lou 的脑子里灵光一闪: “既然 HTTP 服务器不记得状态,那我让浏览器帮忙记住不就行了?”

他在纸巾上画了个草图:

第一次请求:
客户端 → 服务器: “给我首页”
服务器 → 客户端: “给你首页,顺便这是你的ID卡(Cookie)”

第二次请求:
客户端 → 服务器: “给我购物车(带上ID卡)”
服务器: “看到ID卡了,你是刚才那个人,购物车里有耐克鞋”

20分钟后,Lou 回到工位,开始写代码。

Cookie的第一行代码:核心机制

Lou 在网景浏览器(Netscape Navigator)里增加了一个功能:当服务器发送特殊的 HTTP 头时,浏览器会把这段数据存下来,并在下次向同一服务器发起请求时自动带上。

服务器端(设置Cookie):

// 1994年网景服务器代码(简化版)
void send_response(int client_fd, const char *user_id) {
    char response[1024];

    // 构造HTTP响应
    sprintf(response,
        "HTTP/1.0 200 OK\r\n"
        "Set-Cookie: user_id=%s\r\n" // ← 关键的一行!
        "Content-Type: text/html\r\n"
        "\r\n"
        "<html><body>欢迎回来!</body></html>",
        user_id
    );

    send(client_fd, response, strlen(response), 0);
}

核心就是这一行: Set-Cookie: user_id=123456

浏览器端(保存Cookie):

// 网景浏览器代码(简化版)
void parse_response(const char *response) {
    // 查找 Set-Cookie 头
    const char *cookie_header = strstr(response, “Set-Cookie:”);
    if (cookie_header) {
        // 提取Cookie内容
        char cookie[256];
        sscanf(cookie_header, “Set-Cookie: %[^\r\n]“, cookie);

        // 保存到本地文件(最早的实现)
        FILE *fp = fopen(“cookies.txt”, “a”);
        fprintf(fp, “%s\n“, cookie);
        fclose(fp);

        printf(“[浏览器] 保存Cookie: %s\n“, cookie);
    }
}

下次请求时(发送Cookie):

void send_request(int server_fd, const char *path) {
    char request[1024];
    char cookies[256] = “”;

    // 读取本地保存的Cookie
    FILE *fp = fopen(“cookies.txt”, “r”);
    if (fp) {
        fgets(cookies, sizeof(cookies), fp);
        fclose(fp);
    }

    // 构造HTTP请求,带上Cookie
    sprintf(request,
        “GET %s HTTP/1.0\r\n”
        “Cookie: %s\r\n” // ← 自动带上!
        “\r\n“,
        path, cookies
    );

    send(server_fd, request, strlen(request), 0);
}

就这么简单! 一个 Set-Cookie 头用于下发,一个 Cookie 头用于上传,状态管理的问题迎刃而解。

购物车功能因此“复活”

有了 Cookie,购物车的逻辑就变得清晰可行:

第1次请求(添加商品):
客户端: “GET /add?item=nike”
服务器: “收到,给你分配ID=abc123”
        “Set-Cookie: cart_id=abc123”
        【服务器在数据库里记录:abc123的购物车有nike】

第2次请求(查看购物车):
客户端: “GET /cart”
        “Cookie: cart_id=abc123”  (浏览器自动带上)
服务器: “看到abc123了,查数据库...”
        “你的购物车有:nike”

用户体验瞬间变得丝滑流畅!
产品经理测试后激动地喊:“Lou,你是天才!”

Cookie的命名之争与诞生

设计完成后,团队开会讨论:这东西叫什么名字?

有人提议:“SessionID?”
Lou 摇头:“太技术化了。”

有人说:“UserToken?”
Lou 还是摇头:“听起来像加密货币。”

Lou 坚持用“Cookie”: “Magic Cookie 是 Unix 的术语,程序员都知道。而且 Cookie 这个词很亲切,像小饼干一样,人畜无害。”

1994年10月,网景发布了 Cookie 规范(草案)。
但当时没有人想到,这块“小饼干”后来会引发持续数十年的隐私大战。

Cookie的“原罪”时刻:跨站追踪

1996年,广告公司 DoubleClick 发现了 Cookie 的一个“黑暗用法”:跨站追踪。

正常的Cookie使用:

你访问: taobao.com
淘宝设置: Cookie: user_id=123
下次访问淘宝: 浏览器带上 user_id=123
淘宝识别你,显示购物车

广告公司的追踪玩法:

你访问: 新闻网站A
网页里嵌入: <img src="https://ads.com/track.gif">
广告公司设置: Cookie: track_id=xyz

你访问: 购物网站B
网页里又嵌入: <img src="https://ads.com/track.gif">
浏览器带上: Cookie: track_id=xyz
广告公司: “哦?xyz在新闻网站看了财经,又来购物网站看手机”
            “给他推荐iPhone广告!”

你在不同网站的行为被同一个第三方广告商的 Cookie 串起来了! 这就是第三方 Cookie 追踪的原理。

Lou的后悔与辩护,以及法规的回应

2013年,Lou Montulli 接受采访时坦言:“我从没想过 Cookie 会被用来追踪用户。我只是想解决购物车问题。”

但争议没有停止。2018年,欧盟 GDPR(通用数据保护条例)法案出台,强制要求:“网站必须明确告知用户 Cookie 的用途,并征得同意。”

于是你现在打开任何欧洲网站,都会看到类似这样的提示:

┌────────────────────────────────────┐
│  本网站使用Cookie                 │
│                                    │
│ [接受所有] [拒绝] [自定义]         │
└────────────────────────────────────┘

Lou 20分钟的灵感,带来了持续20年的技术便利与隐私争论。

很多人容易混淆 Cookie 和 Session,让我们用代码把它们讲清楚:

Cookie(客户端存储):

// 服务器:把数据发给客户端保存
send_header(“Set-Cookie: username=xiaokang; cart=nike,adidas”);

// 客户端:下次请求带上所有数据
send_header(“Cookie: username=xiaokang; cart=nike,adidas”);

优点:服务器不用存储,省内存。
缺点:数据暴露在客户端,不安全(用户能改)。

Session(服务器存储):

// 服务器:生成一个ID,只把ID发给客户端
char session_id[32];
generate_random_id(session_id);
send_header(“Set-Cookie: session_id=abc123”);

// 服务器内存里存储真实数据
sessions[“abc123”] = {
    username: “xiaokang”,
    cart: [“nike”, “adidas”]
};

// 客户端:只传ID
send_header(“Cookie: session_id=abc123”);

// 服务器:用ID查内存
data = sessions[“abc123”];

优点:数据安全,客户端改不了。
缺点:服务器要存储,占内存。

实际开发中,两者通常结合使用: 敏感数据(如登录状态)用 Session 存储,非敏感数据(如主题偏好)可以用 Cookie 存储。

动手实验:用C语言编写一个简易Cookie解析器

想直观地看看 Cookie 在底层是如何被处理的吗?这里有一个用 C 语言编写的简易解析器:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// Cookie结构体
typedef struct {
    char name[64];
    char value[256];
} Cookie;

// 解析Cookie字符串
int parse_cookies(const char *cookie_header, Cookie *cookies, int max_count) {
    char buffer[1024];
    strncpy(buffer, cookie_header, sizeof(buffer));

    int count = 0;
    char *token = strtok(buffer, “;”);

    while (token && count < max_count) {
        // 跳过前导空格
        while (*token == ‘ ‘) token++;

        // 分割 name=value
        char *equal = strchr(token, ‘=’);
        if (equal) {
            *equal = ‘\0’;
            strncpy(cookies[count].name, token, sizeof(cookies[count].name));
            strncpy(cookies[count].value, equal + 1, sizeof(cookies[count].value));
            count++;
        }

        token = strtok(NULL, “;”);
    }

    return count;
}

int main() {
    // 模拟浏览器发送的Cookie
    const char *cookie_header = “user_id=123456; session=abc-def-ghi; theme=dark”;

    Cookie cookies[10];
    int count = parse_cookies(cookie_header, cookies, 10);

    printf(“解析到 %d 个Cookie:\n“, count);
    for (int i = 0; i < count; i++) {
        printf(”  %s = %s\n“, cookies[i].name, cookies[i].value);
    }

    // 查找特定Cookie
    printf(“\n查找session:\n“);
    for (int i = 0; i < count; i++) {
        if (strcmp(cookies[i].name, “session”) == 0) {
            printf(”  找到了!值是: %s\n“, cookies[i].value);
        }
    }

    return 0;
}

编译运行:

gcc -o cookie_parser cookie_parser.c
./cookie_parser

输出:

解析到 3 个Cookie:
  user_id = 123456
  session = abc-def-ghi
  theme = dark

查找session:
  找到了!值是: abc-def-ghi

通过这个简单的 C/C++ 示例,你可以清晰地看到 Cookie 在 HTTP 报文中的原始形态以及解析逻辑。

实战:如何查看真实网站设置的Cookie

想看看淘宝、谷歌这些网站到底在你的浏览器里存放了多少 Cookie 吗?

方法1:使用浏览器开发者工具

  1. 打开 Chrome,访问 taobao.com
  2. F12 打开开发者工具
  3. 点击 Application(应用) → Cookieshttps://www.taobao.com

你会看到可能有几十个Cookie! 每个都有其名称、值、域、路径、过期时间等属性。

方法2:使用 curl 命令行工具

curl -i https://www.taobao.com 2>&1 | grep -i “set-cookie”

输出类似:

Set-Cookie: cna=xxx; Domain=.taobao.com; Path=/; Expires=...
Set-Cookie: t=yyy; Domain=.taobao.com; Path=/; HttpOnly
Set-Cookie: _tb_token_=zzz; Domain=.taobao.com; Path=/

Cookie的安全属性演进:不断修补的“补丁”

现代 Cookie 早已不只是 name=value 这么简单,为了安全起见,增加了一系列属性:

Set-Cookie: session_id=abc123;
            Domain=.example.com;     # 哪些域名能访问此Cookie
            Path=/;                  # 哪些路径能访问
            Expires=Wed, 09 Jun 2025 10:18:14 GMT;  # 过期时间
            Secure;                  # 只能通过HTTPS传输
            HttpOnly;                # JavaScript无法读取(防XSS)
            SameSite=Strict          # 防CSRF攻击

关键属性讲解:

  1. HttpOnly(2002年引入)

    // 没有HttpOnly:黑客可以用JavaScript偷Cookie
    document.cookie  // 能看到所有Cookie
    
    // 有HttpOnly:JavaScript看不到
    document.cookie  // 看不到标记了HttpOnly的session_id
  2. Secure(只能通过HTTPS传输)

    HTTP请求:  Cookie不会被发送(防止中间人窃听)
    HTTPS请求: Cookie正常发送
  3. SameSite(防跨站请求伪造CSRF攻击)

    SameSite=Strict: 只有同站请求才发送Cookie
    SameSite=Lax:    大部分跨站请求不发送(如从外链跳转过来会发送)
    SameSite=None:   所有请求都发送(需配合Secure属性使用)

这些都是后来为了应对各种网络攻击而不断添加的“补丁”,1994年Lou发明的最初版Cookie可没有这些安全措施!

Cookie的大小限制及其原因

你知道浏览器对 Cookie 的大小有严格限制吗?

主流浏览器的典型限制:

  • 单个Cookie:最大 4KB
  • 单个域名:最多 50个 Cookie(Chrome)
  • 总大小:所有Cookie加起来约 200KB

为什么限制这么严格?
因为 Cookie 会在 每次HTTP请求 的头部中被发送到服务器!

想象一下,如果允许单个 Cookie 有 1MB 大小:

你访问: taobao.com/page1  (发送1MB Cookie)
你访问: taobao.com/page2  (又发送1MB Cookie)
你访问: taobao.com/page3  (又发送1MB Cookie)

加载一个网页:
  HTML: 10个请求 × 1MB = 10MB
  CSS:  5个请求 × 1MB = 5MB
  JS:   10个请求 × 1MB = 10MB
  图片: 20个请求 × 1MB = 20MB

总共浪费 45MB 网络带宽!用户体验将是一场灾难。

所以,Cookie 必须保持小巧! 这也是为什么敏感数据通常只用 Cookie 存一个 Session ID,真实数据存在服务器端的原因之一。

现代替代方案:Cookie的未来

随着隐私保护意识的增强,传统的第三方 Cookie 正在被限制和逐渐淘汰,出现了一些替代方案:

1. LocalStorage / SessionStorage(HTML5,2009年左右)

// 浏览器端存储,不会自动随请求发送
localStorage.setItem('user', 'xiaokang'); // 持久存储
sessionStorage.setItem('token', 'abc');   // 会话级存储
localStorage.getItem('user');  // ‘xiaokang’

优点:容量大(通常5-10MB)、不占用网络请求带宽。
缺点:只能通过 JavaScript 访问,服务器无法直接获取。

2. JWT(JSON Web Token)

# 服务器生成一个自包含的Token
Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzNDU2In0...

# 客户端将其放在请求头中(通常不是Cookie头)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

优点:无状态、服务器无需存储会话信息。
缺点:Token 一旦泄露,在过期前无法单方面撤销(除非维护一个黑名单)。

3. 浏览器指纹(有争议的追踪技术)

// 收集浏览器众多独有特征生成一个标识
fingerprint = hash(
    userAgent +
    screenResolution +
    installedFonts +
    timezone +
    ...
)

这甚至不需要 Cookie 就能近乎唯一地识别你! 更可怕的是,用户通常无法简单拒绝或清除这种识别,因为这些收集的都是浏览器的正常功能特征。

尾声:一个工程师的20分钟如何塑造互联网

Lou Montulli 的一次咖啡时间灵感,从根本上塑造了现代 Web 的形态。

Cookie 让 Web 从“无状态”变成了“有记忆”。

  • 没有 Cookie,就没有持久的登录状态。
  • 没有 Cookie,就没有电商购物车。
  • 没有 Cookie,个性化推荐也无从谈起。

但同时,它也无意中开启了隐私追踪的潘多拉魔盒。

技术本身往往是中性的,关键在于我们如何使用它。就像 Lou 本人后来反思的:“我发明 Cookie 是为了让用户体验更好,不是为了让广告公司赚钱。”

但历史的车轮,常常会驶向发明者最初意想不到的方向。从解决一个具体的技术痛点(购物车),到成为互联网的基础设施,再到陷入隐私争议的漩涡,Cookie 的故事是技术发展与社会伦理相互交织的一个经典缩影。

你对 Cookie 的工作原理、历史演变或隐私问题有什么看法?欢迎在 云栈社区网络/系统 板块与其他开发者一起深入探讨。如果你想更深入地理解这些网络协议如何在系统层面实现,动手编写相关的网络组件将是绝佳的学习路径。




上一篇:我的OpenClaw踩坑手记:从部署到玩出花样的实战指南
下一篇:RAUC实战指南:为嵌入式Linux实现安全可靠的OTA系统更新
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2026-2-3 18:25 , Processed in 0.276759 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

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