欢迎光临
我们一直在努力

【筑基·036】筑基毕业:能独立做一个项目

在这里插入代码片# 【筑基·036】筑基毕业:能独立做一个项目

码农修仙传 · 筑基期 · 第36篇 我是玄芯散人,带你从炼气修到大乘。


境界标识

╔══════════════════════════════════════╗
║ 筑基期 · 第36篇 ║
║ 毕业标准:独立做一个项目 ║
║ 预计阅读:12分钟 ║
╚══════════════════════════════════════╝


修仙引入

修真小说里,筑基期满的标志不是灵力有多少,是能不能独立下山办事。宗门给你一个任务,你一个人搞定,回来交差。搞不定,灵力再浑厚也是半成品。

程序员也一样。你读完了《CSAPP》,刷完了LeetCode 100题,能把TCP三次握手画得滚瓜烂熟。但问你:你能从零写一个能跑的项目吗?如果答不上来,你的筑基还差最后一脚。

上一篇034讲了四座地基是什么以及它们之间的关系。这一篇讲怎么用这四座地基做出一个能跑的项目,算是筑基期第一组内容的收尾。


硬核主体

一、什么叫"独立完成一个完整项目"

很多人觉得自己做过项目就算毕业了。但仔细一问,项目是跟着教程敲的,代码是复制粘贴改的,遇到bug就搜Stack Overflow照着改。这不叫独立完成,叫"陪练完成"。

独立完成的意思是:给你一个需求,你能自己拆解,自己设计,自己写代码,自己调试,自己测试,最后交付一个能跑的东西。全程不抄别人的代码,可以查文档查API,但不能照着教程一行一行抄。

合格项目的意思是:不是写一个函数,不是做一道算法题,是一个有输入有输出有数据流动的系统。它至少包含这些环节:

  • 需求拆解:把"做一个XX"拆成具体的功能模块
  • 技术选型:选什么数据结构,选什么通信方式
  • 代码实现:写出能编译能跑的代码
  • 调试排错:出了bug能自己定位到哪一行
  • 测试验证:怎么证明你的程序是对的

# 判断你的项目是否"独立完成"的自测
def is_independent_project(project):
checks = {
"需求自己拆": project.decomposed_by_self, # 不是教程帮你拆好的
"架构自己定": project.designed_by_self, # 不是照搬别人的目录结构
"代码自己写": project.coded_by_hand, # 不是复制粘贴改的
"bug自己调": project.debugged_independently, # 不是搜报错照着改的
"能跑能用": project.runs_correctly, # 不是跑起来就崩的
}
passed = sum(checks.values())
if passed == 5:
return "✅ 毕业级项目"
elif passed >= 3:
return "⚠️ 半独立,还不够"
else:
return "❌ 陪练级,重做"

# 诚实回答:你做过的"项目",几条打勾?

二、项目选择的三个标准

筑基期毕业项目不是随便找个项目做就行。它需要满足三个条件:

#mermaid-svg-3wB93iOfaPMOK3rA{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3wB93iOfaPMOK3rA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3wB93iOfaPMOK3rA .error-icon{fill:#552222;}#mermaid-svg-3wB93iOfaPMOK3rA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3wB93iOfaPMOK3rA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3wB93iOfaPMOK3rA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3wB93iOfaPMOK3rA .marker.cross{stroke:#333333;}#mermaid-svg-3wB93iOfaPMOK3rA svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3wB93iOfaPMOK3rA p{margin:0;}#mermaid-svg-3wB93iOfaPMOK3rA .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-3wB93iOfaPMOK3rA .cluster-label text{fill:#333;}#mermaid-svg-3wB93iOfaPMOK3rA .cluster-label span{color:#333;}#mermaid-svg-3wB93iOfaPMOK3rA .cluster-label span p{background-color:transparent;}#mermaid-svg-3wB93iOfaPMOK3rA .label text,#mermaid-svg-3wB93iOfaPMOK3rA span{fill:#333;color:#333;}#mermaid-svg-3wB93iOfaPMOK3rA .node rect,#mermaid-svg-3wB93iOfaPMOK3rA .node circle,#mermaid-svg-3wB93iOfaPMOK3rA .node ellipse,#mermaid-svg-3wB93iOfaPMOK3rA .node polygon,#mermaid-svg-3wB93iOfaPMOK3rA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3wB93iOfaPMOK3rA .rough-node .label text,#mermaid-svg-3wB93iOfaPMOK3rA .node .label text,#mermaid-svg-3wB93iOfaPMOK3rA .image-shape .label,#mermaid-svg-3wB93iOfaPMOK3rA .icon-shape .label{text-anchor:middle;}#mermaid-svg-3wB93iOfaPMOK3rA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3wB93iOfaPMOK3rA .rough-node .label,#mermaid-svg-3wB93iOfaPMOK3rA .node .label,#mermaid-svg-3wB93iOfaPMOK3rA .image-shape .label,#mermaid-svg-3wB93iOfaPMOK3rA .icon-shape .label{text-align:center;}#mermaid-svg-3wB93iOfaPMOK3rA .node.clickable{cursor:pointer;}#mermaid-svg-3wB93iOfaPMOK3rA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3wB93iOfaPMOK3rA .arrowheadPath{fill:#333333;}#mermaid-svg-3wB93iOfaPMOK3rA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3wB93iOfaPMOK3rA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3wB93iOfaPMOK3rA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wB93iOfaPMOK3rA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3wB93iOfaPMOK3rA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wB93iOfaPMOK3rA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3wB93iOfaPMOK3rA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3wB93iOfaPMOK3rA .cluster text{fill:#333;}#mermaid-svg-3wB93iOfaPMOK3rA .cluster span{color:#333;}#mermaid-svg-3wB93iOfaPMOK3rA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3wB93iOfaPMOK3rA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3wB93iOfaPMOK3rA rect.text{fill:none;stroke-width:0;}#mermaid-svg-3wB93iOfaPMOK3rA .icon-shape,#mermaid-svg-3wB93iOfaPMOK3rA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3wB93iOfaPMOK3rA .icon-shape p,#mermaid-svg-3wB93iOfaPMOK3rA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3wB93iOfaPMOK3rA .icon-shape .label rect,#mermaid-svg-3wB93iOfaPMOK3rA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3wB93iOfaPMOK3rA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3wB93iOfaPMOK3rA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3wB93iOfaPMOK3rA :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

项目选择三标准

标准一综合四座地基

标准二难度适中

标准三能跑能用能验证

组成原理 + 操作系统数据结构 + 网络至少涉及三座

比LeetCode难比开源项目简单1-2周能做完

有明确输入输出能给别人演示能自动化测试

标准一:综合四座地基。筑基期学了四座地基(组成原理,操作系统,数据结构,网络),毕业项目至少要涉及三座。做一个纯算法题只考数据结构,做一个纯前端页面什么地基都不考。好的项目应该让你在做的过程中用上多块知识。

标准二:难度适中。太简单没有压力,太难做不完会放弃。合适的项目大约需要1到2周的业余时间完成,代码量在500到2000行之间。比LeetCode的算法题复杂,但比参与一个开源项目简单。

标准三:能跑能用能验证。做完之后不是"我写了代码但没跑通"。程序要能运行,要有明确的输入和输出,能给别人演示。最好能写自动化测试,用数据证明你的程序是对的。

三、推荐项目:用C写一个HTTP服务器

满足上面三个标准的项目,首推:用C语言从零写一个HTTP服务器。

为什么选这个项目?因为它天然综合了四座地基:

#mermaid-svg-VV50fIydXuV7KQNM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VV50fIydXuV7KQNM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VV50fIydXuV7KQNM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VV50fIydXuV7KQNM .error-icon{fill:#552222;}#mermaid-svg-VV50fIydXuV7KQNM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VV50fIydXuV7KQNM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VV50fIydXuV7KQNM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VV50fIydXuV7KQNM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VV50fIydXuV7KQNM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VV50fIydXuV7KQNM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VV50fIydXuV7KQNM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VV50fIydXuV7KQNM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VV50fIydXuV7KQNM .marker.cross{stroke:#333333;}#mermaid-svg-VV50fIydXuV7KQNM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VV50fIydXuV7KQNM p{margin:0;}#mermaid-svg-VV50fIydXuV7KQNM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-VV50fIydXuV7KQNM .cluster-label text{fill:#333;}#mermaid-svg-VV50fIydXuV7KQNM .cluster-label span{color:#333;}#mermaid-svg-VV50fIydXuV7KQNM .cluster-label span p{background-color:transparent;}#mermaid-svg-VV50fIydXuV7KQNM .label text,#mermaid-svg-VV50fIydXuV7KQNM span{fill:#333;color:#333;}#mermaid-svg-VV50fIydXuV7KQNM .node rect,#mermaid-svg-VV50fIydXuV7KQNM .node circle,#mermaid-svg-VV50fIydXuV7KQNM .node ellipse,#mermaid-svg-VV50fIydXuV7KQNM .node polygon,#mermaid-svg-VV50fIydXuV7KQNM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VV50fIydXuV7KQNM .rough-node .label text,#mermaid-svg-VV50fIydXuV7KQNM .node .label text,#mermaid-svg-VV50fIydXuV7KQNM .image-shape .label,#mermaid-svg-VV50fIydXuV7KQNM .icon-shape .label{text-anchor:middle;}#mermaid-svg-VV50fIydXuV7KQNM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VV50fIydXuV7KQNM .rough-node .label,#mermaid-svg-VV50fIydXuV7KQNM .node .label,#mermaid-svg-VV50fIydXuV7KQNM .image-shape .label,#mermaid-svg-VV50fIydXuV7KQNM .icon-shape .label{text-align:center;}#mermaid-svg-VV50fIydXuV7KQNM .node.clickable{cursor:pointer;}#mermaid-svg-VV50fIydXuV7KQNM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VV50fIydXuV7KQNM .arrowheadPath{fill:#333333;}#mermaid-svg-VV50fIydXuV7KQNM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VV50fIydXuV7KQNM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VV50fIydXuV7KQNM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VV50fIydXuV7KQNM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VV50fIydXuV7KQNM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VV50fIydXuV7KQNM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VV50fIydXuV7KQNM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VV50fIydXuV7KQNM .cluster text{fill:#333;}#mermaid-svg-VV50fIydXuV7KQNM .cluster span{color:#333;}#mermaid-svg-VV50fIydXuV7KQNM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VV50fIydXuV7KQNM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VV50fIydXuV7KQNM rect.text{fill:none;stroke-width:0;}#mermaid-svg-VV50fIydXuV7KQNM .icon-shape,#mermaid-svg-VV50fIydXuV7KQNM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VV50fIydXuV7KQNM .icon-shape p,#mermaid-svg-VV50fIydXuV7KQNM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VV50fIydXuV7KQNM .icon-shape .label rect,#mermaid-svg-VV50fIydXuV7KQNM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VV50fIydXuV7KQNM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VV50fIydXuV7KQNM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VV50fIydXuV7KQNM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

HTTP服务器涉及的四座地基

组成原理

字节序转换

内存布局理解

缓冲区和Cache

数据结构

哈希表存HTTP头

链表管理连接

栈解析请求行

操作系统

多进程/多线程并发

文件描述符管理

系统调用read/write

网络

TCP Socket编程

HTTP协议解析

三次握手四次挥手

网络方面,你要用Socket API建立TCP连接,要解析HTTP请求的请求行和头部,要处理三次握手和四次挥手。操作系统方面,你要用多进程或多线程处理并发连接,要管理文件描述符,要用read和write系统调用收发数据。数据结构方面,HTTP请求头是键值对,自然用哈希表存,多个客户端连接用链表管理。组成原理方面,网络字节序和主机字节序的转换涉及字节序问题,缓冲区设计涉及内存布局。

下面拆解这个项目的实现步骤。

四、项目实战拆解

第一步:需求拆解

一个最简HTTP服务器要做的事:

  • 监听一个端口(比如8080)
  • 接受客户端连接
  • 读取HTTP请求
  • 解析请求行(GET /path HTTP/1.1)
  • 根据路径返回对应的文件内容
  • 关闭连接
  • // 最简HTTP服务器的骨架代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>

    #define PORT 8080
    #define BUF_SIZE 4096

    int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP socket

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT); // 主机序转网络序

    bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(server_fd, 5); // 最大等待队列5
    printf("Server listening on port %d…\\n", PORT);

    while (1) {
    int client_fd = accept(server_fd, NULL, NULL); // 等待连接
    char buf[BUF_SIZE];
    int n = read(client_fd, buf, BUF_SIZE 1);
    buf[n] = '\\0';

    // 解析请求行: GET /path HTTP/1.1
    char method[8], path[256], version[16];
    sscanf(buf, "%s %s %s", method, path, version);
    printf("Request: %s %s %s\\n", method, path, version);

    // 返回固定响应(后续可改为读取文件)
    const char *response =
    "HTTP/1.1 200 OK\\r\\n"
    "Content-Type: text/html\\r\\n"
    "Content-Length: 13\\r\\n"
    "\\r\\n"
    "Hello, World!";
    write(client_fd, response, strlen(response));
    close(client_fd);
    }
    return 0;
    }

    这段代码能跑,能处理一个请求。但它只是骨架,离合格项目还差得远。

    第二步:结构设计

    骨架代码的问题:一次只能处理一个连接,客户端要排队。要做一个能用的HTTP服务器,至少需要并发处理。

    #mermaid-svg-ySAe6HW7DeAj4RTu{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ySAe6HW7DeAj4RTu .error-icon{fill:#552222;}#mermaid-svg-ySAe6HW7DeAj4RTu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ySAe6HW7DeAj4RTu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ySAe6HW7DeAj4RTu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ySAe6HW7DeAj4RTu .marker.cross{stroke:#333333;}#mermaid-svg-ySAe6HW7DeAj4RTu svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ySAe6HW7DeAj4RTu p{margin:0;}#mermaid-svg-ySAe6HW7DeAj4RTu .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster-label text{fill:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster-label span{color:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster-label span p{background-color:transparent;}#mermaid-svg-ySAe6HW7DeAj4RTu .label text,#mermaid-svg-ySAe6HW7DeAj4RTu span{fill:#333;color:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu .node rect,#mermaid-svg-ySAe6HW7DeAj4RTu .node circle,#mermaid-svg-ySAe6HW7DeAj4RTu .node ellipse,#mermaid-svg-ySAe6HW7DeAj4RTu .node polygon,#mermaid-svg-ySAe6HW7DeAj4RTu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ySAe6HW7DeAj4RTu .rough-node .label text,#mermaid-svg-ySAe6HW7DeAj4RTu .node .label text,#mermaid-svg-ySAe6HW7DeAj4RTu .image-shape .label,#mermaid-svg-ySAe6HW7DeAj4RTu .icon-shape .label{text-anchor:middle;}#mermaid-svg-ySAe6HW7DeAj4RTu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ySAe6HW7DeAj4RTu .rough-node .label,#mermaid-svg-ySAe6HW7DeAj4RTu .node .label,#mermaid-svg-ySAe6HW7DeAj4RTu .image-shape .label,#mermaid-svg-ySAe6HW7DeAj4RTu .icon-shape .label{text-align:center;}#mermaid-svg-ySAe6HW7DeAj4RTu .node.clickable{cursor:pointer;}#mermaid-svg-ySAe6HW7DeAj4RTu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ySAe6HW7DeAj4RTu .arrowheadPath{fill:#333333;}#mermaid-svg-ySAe6HW7DeAj4RTu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ySAe6HW7DeAj4RTu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ySAe6HW7DeAj4RTu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySAe6HW7DeAj4RTu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ySAe6HW7DeAj4RTu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySAe6HW7DeAj4RTu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster text{fill:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu .cluster span{color:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ySAe6HW7DeAj4RTu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ySAe6HW7DeAj4RTu rect.text{fill:none;stroke-width:0;}#mermaid-svg-ySAe6HW7DeAj4RTu .icon-shape,#mermaid-svg-ySAe6HW7DeAj4RTu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ySAe6HW7DeAj4RTu .icon-shape p,#mermaid-svg-ySAe6HW7DeAj4RTu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ySAe6HW7DeAj4RTu .icon-shape .label rect,#mermaid-svg-ySAe6HW7DeAj4RTu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ySAe6HW7DeAj4RTu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ySAe6HW7DeAj4RTu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ySAe6HW7DeAj4RTu :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    HTTP服务器结构

    工作线程

    主线程

    每次accept新建

    读取请求

    创建socket

    bind+listen

    accept循环

    解析HTTP头

    查找文件

    返回响应

    主线程负责接受连接,每来一个连接就fork一个子进程或创建一个线程去处理。工作线程读取请求,解析HTTP头部,根据路径查找文件,返回响应内容。

    这涉及操作系统的进程管理(fork)或线程管理(pthread_create),涉及文件系统的文件读取(open/read),涉及网络的HTTP协议解析,涉及数据结构的请求头存储(哈希表或数组)。

    第三步:实现HTTP请求解析

    HTTP请求的格式是文本协议,解析过程就是把文本按规则切分:

    // HTTP请求格式:
    // GET /index.html HTTP/1.1\\r\\n
    // Host: localhost:8080\\r\\n
    // User-Agent: curl/7.68.0\\r\\n
    // \\r\\n

    typedef struct {
    char method[8]; // GET/POST/PUT/DELETE
    char path[256]; // /index.html
    char version[16]; // HTTP/1.1
    char headers[32][2][128]; // 头部键值对数组
    int header_count;
    } HttpRequest;

    void parse_request(const char *raw, HttpRequest *req) {
    // 第一步:解析请求行
    sscanf(raw, "%s %s %s", req->method, req->path, req->version);

    // 第二步:逐行解析头部
    const char *p = strstr(raw, "\\r\\n"); // 跳过请求行
    p += 2;
    req->header_count = 0;

    while (*p != '\\r' && *p != '\\0') {
    // 每行格式: Key: Value
    sscanf(p, "%[^:]: %[^\\r\\n]",
    req->headers[req->header_count][0],
    req->headers[req->header_count][1]);
    req->header_count++;
    p = strstr(p, "\\r\\n");
    if (!p) break;
    p += 2;
    }
    }

    这段代码用数组存HTTP头部。更进阶的做法是用哈希表,查找某个头部时从O(n)变成O(1)。这就是数据结构选择的实际意义:同样的功能,不同的数据结构带来不同的性能差异。

    第四步:并发处理

    单进程版的HTTP服务器处理一个请求时,其他客户端必须等待。要支持并发,有两种方式:

    // 方式一:fork子进程(简单但开销大)
    void handle_fork(int server_fd) {
    while (1) {
    int client_fd = accept(server_fd, NULL, NULL);
    pid_t pid = fork();
    if (pid == 0) {
    // 子进程处理请求
    close(server_fd); // 子进程不需要监听socket
    handle_request(client_fd);
    close(client_fd);
    exit(0);
    }
    close(client_fd); // 父进程不需要客户端socket
    // ⚠️ 这里需要waitpid回收子进程,否则产生僵尸进程
    }
    }

    // 方式二:pthread线程(轻量但要注意线程安全)
    #include <pthread.h>

    void *worker_thread(void *arg) {
    int client_fd = *(int*)arg;
    free(arg);
    handle_request(client_fd);
    close(client_fd);
    return NULL;
    }

    void handle_thread(int server_fd) {
    while (1) {
    int client_fd = accept(server_fd, NULL, NULL);
    int *fd_ptr = malloc(sizeof(int));
    *fd_ptr = client_fd;
    pthread_t tid;
    pthread_create(&tid, NULL, worker_thread, fd_ptr);
    pthread_detach(tid); // 自动回收,不需要join
    }
    }

    fork方式简单粗暴,每个子进程有独立的地址空间,不用操心线程安全。但fork的代价是复制父进程的页表结构(写时复制机制下只读标记,真正写时才复制数据),高并发时开销大。pthread方式轻量,线程共享地址空间,但多个线程同时访问全局变量时需要加锁。

    这就是操作系统知识在实际项目里的体现:你知道fork和pthread的区别,知道什么时候用哪种,知道各自的风险。

    第五步:测试验证

    项目做完了,怎么证明它是对的?不能只靠"看起来能跑"。需要系统化测试:

    # 测试1:基本功能,用curl发请求
    curl http://localhost:8080/index.html
    # 期望:返回index.html的内容

    # 测试2:并发压力,用ab(Apache Bench)
    ab -n 1000 -c 10 http://localhost:8080/index.html
    # 期望:1000个请求全部成功,不崩溃

    # 测试3:边界情况,请求不存在的文件
    curl http://localhost:8080/nonexistent
    # 期望:返回404 Not Found

    # 测试4:恶意请求,超长URL
    python3 -c "import socket; s=socket.socket(); s.connect(('localhost',8080)); s.send(b'GET /'+'A'*10000+b' HTTP/1.1\\r\\n\\r\\n'); print(s.recv(4096))"
    # 期望:不崩溃,返回400 Bad Request或正常拒绝

    这五个步骤走完,你的HTTP服务器就算一个能跑能用的项目了。做的过程中你用到了网络(Socket和HTTP),操作系统里的fork和pthread,数据结构里的数组和哈希表,组成原理里的字节序和内存布局。四座地基全部覆盖。

    五、其他推荐项目

    HTTP服务器不是唯一选择。以下是几个同样满足三标准的项目,按你的兴趣和技术方向选一个:

    recommended_projects = [
    {
    "name": "HTTP服务器",
    "语言": "C",
    "涉及地基": "网络+OS+数据结构+组成",
    "难度": "★★★☆☆",
    "代码量": "800-1500行",
    "适合": "想做后端/系统编程的人",
    },
    {
    "name": "简易Shell",
    "语言": "C",
    "涉及地基": "OS+数据结构+组成",
    "难度": "★★★☆☆",
    "代码量": "500-1000行",
    "适合": "想深入理解进程和信号的人",
    },
    {
    "name": "键值存储(类Redis)",
    "语言": "C/Python",
    "涉及地基": "数据结构+OS+网络",
    "难度": "★★★★☆",
    "代码量": "1000-2000行",
    "适合": "对数据库感兴趣的人",
    },
    {
    "name": "定时器模块",
    "语言": "C",
    "涉及地基": "数据结构+OS",
    "难度": "★★☆☆☆",
    "代码量": "300-600行",
    "适合": "想做嵌入式/网络框架的人",
    },
    ]

    # 选项目的原则:选你感兴趣的,选你能做完的
    # 不要选最难的,选最适合你的

    简易Shell涉及操作系统的进程创建和信号处理,还有管道和重定向。键值存储需要数据结构(哈希表或跳表),需要网络(TCP协议),需要操作系统(持久化用文件系统)。定时器模块需要数据结构(最小堆或红黑树管理定时任务),需要操作系统(信号或timerfd)。

    六、做项目时常见的坑

    坑一:一上来就追求完美。想支持HTTP/2,想支持HTTPS,想做异步IO。结果一周过去了连基本的HTTP/1.1请求都没解析对。做项目要分阶段:先让它跑起来,再让它跑得对,最后让它跑得好。先做一个只能返回固定字符串的服务器,再加上文件读取,再加上并发,再加上错误处理。每一步都是可运行的状态。

    坑二:不写测试。写完代码手动curl一下觉得"能跑"就算完了。但手动测试覆盖不了边界情况:空请求怎么处理?超大请求怎么处理?并发100个连接会不会崩?每加一个功能,同时加对应的测试用例。

    坑三:照着教程抄。网上有大量"用C写HTTP服务器"的教程。看一遍理解思路没问题,但写代码的时候关掉教程自己写。你能抄出来的代码不证明你会写,只有关掉教程能写出来才算会。

    坑四:不读经典。做完项目觉得"能跑就行了",不回去对照经典教材看自己的实现差在哪。HTTP服务器做完后,去看看Nginx的设计思路(不需要读源码,看设计文章就行),对比自己的设计,找到差距。这一步是让你从"会做"变成"做得好"。


    修仙术语对照表

    修仙术语技术现实本篇位置
    下山办事 独立完成一个完整项目 修仙引入
    宗门给任务 拿到一个需求自己拆解实现 修仙引入
    陪练完成 跟着教程抄代码,不算独立 什么叫独立完成
    四座地基 组成原理/OS/数据结构/网络 项目选择标准
    灵力浑厚也是半成品 知识学了一堆但没有项目验证 修仙引入
    实修 动手做项目 项目选择
    走火入魔 追求完美导致做不完 常见坑
    闭门造车 不写测试不验证 常见坑
    师门传承 对照经典教材找差距 常见坑
    分阶段修炼 先跑起来再改进 常见坑
    传音符 Socket API驱动网络通信 HTTP服务器
    心法 设计思路和模块拆解 结构设计
    经脉 项目中知识之间的依赖链路 结构设计
    修炼成果 能跑能用能验证的项目 项目选择标准

    进阶条件

    • 做了一个至少500行的非教程项目,涉及四座地基中的至少三座
    • 项目能编译能运行,不是"写了一半跑不起来"
    • 全程没有照着教程逐行抄代码,可以查文档但不能抄实现
    • 写了至少3个测试用例,覆盖正常请求和边界情况
    • 遇到bug时能自己用gdb或printf定位到具体行,不是靠搜报错信息碰运气
    • 做完后能向别人用5分钟讲清楚项目的结构和数据流
    • 对照过经典教材或开源项目的设计文章,知道自己的实现差在哪

    勾掉5条以上,你的筑基期算是真正毕业了。接下来进入筑基期第二组:数据结构与算法。四座地基里,数据结构是最先该打牢的那一座,下一篇带你正式入门。


    下期预告 + 互动

    下一篇:数据结构就是功法。今天讲了筑基毕业怎么做项目,接下来进入筑基期的第二组内容,数据结构与算法。数组,链表,栈,队列,哈希表,这些容器到底是什么,什么情况用什么,一篇讲清楚。

    互动问题:你做过的项目里,有没有一个是你觉得"真正独立完成"的?它用了哪些CS基础知识?评论区聊聊,看看大家的项目都涉及哪些地基。

    我是玄芯散人,带你从炼气修到大乘。


    本文是「码农修仙传」系列第36篇。系列导航见 xren.ren

    赞(0)
    未经允许不得转载:171主机测评 » 【筑基·036】筑基毕业:能独立做一个项目
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址