最简单版web server

第一部分:概念与流程解释

1. 什么是"单线程,阻塞式 I/O"?
  • 单线程 :就像餐厅里只有一个服务员。他一次只能接待一桌客人。如果这桌客人点菜慢,或者一直在吃饭,服务员就得干等着,不能去招呼下一桌。
  • 阻塞式 (Blocking):意味着"死等"。
  • 当程序运行到 accept(等待连接)时,如果没有人来连,程序就在那里不动,直到有人来。
  • 当程序运行到 recv(接收数据)时,如果对方没发数据,程序也在那里不动。
2. Socket API 流程详解 (打电话的比喻)

Socket 网络编程的流程,几乎完美对应生活中座机电话的流程:

  1. socket() ------ 买个电话机
  • 含义:向操作系统申请一个"套接字"资源。
  • 代码意图:创建一个文件描述符(fd),告诉系统我要开始网络通信了。
  1. bind() ------ 插上电话线(绑定号码)
  • 含义 :把电话机和你的电话号码(IP地址 + 端口号)绑定在一起。
  • 代码意图 :告诉系统,如果有人打 8080 这个端口,就转给我这个程序。
  1. listen() ------ 接通电源,准备接听
  • 含义:把电话机状态设置为"可接听"。
  • 代码意图:告诉系统,我准备好了,如果有连接进来,先帮我放在"等待队列"里排队(backlog)。
  1. accept() ------ 电话响了,拿起听筒
  • 含义:从等待队列里取出一个连接,正式建立通话。
  • 关键点 :这是一个阻塞 操作。如果没有人打进来,代码就停在这行不动。一旦有人打进来,它会返回一个新的 socket(专门用于和这个人通话),原来的 socket 继续监听别人的电话。
  1. read() / recv() ------ 听对方说话
  • 接收浏览器发过来的 HTTP 请求(一堆字符串)。
  1. write() / send() ------ 回复对方
  • 发送 HTTP 响应(HTML 页面)。
  1. close() ------ 挂断电话
  • 结束这次通话,释放资源。

第二部分:极简乞丐版 WebServer 代码

这是一个在 Linux/Mac 环境下可运行的 C++ 代码(Windows 下 socket API 略有不同,建议在 Linux 虚拟机或 WSL 下运行)。

文件名建议保存为:simple_server.cpp

cpp 复制代码
#include <iostream>
#include <cstring>      // for memset
#include <sys/socket.h> // 核心 socket API
#include <netinet/in.h> // 包含 AF_INET, sockaddr_in 等
#include <unistd.h>     // 包含 close, read, write

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    // 1. 创建 Socket (买电话机)
    // AF_INET: 使用 IPv4
    // SOCK_STREAM: 使用 TCP 协议
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == 0) {
        perror("Socket creation failed");
        return -1;
    }

    // 2. 绑定 IP 和 端口 (插电话线)
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    
    // 初始化结构体
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY; // 监听本机所有 IP
    address.sin_port = htons(PORT);       // 端口号转为网络字节序

    // 执行 bind
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        return -1;
    }

    // 3. 监听 (打开铃声)
    // 3 代表等待队列的最大长度
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        return -1;
    }

    std::cout << "Server is listening on port " << PORT << "..." << std::endl;

    while (true) {
        // 4. 接受连接 (拿起听筒) - 阻塞点
        // accept 会返回一个新的 socket (new_socket) 用于专门和这个客户端通信
        int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
        if (new_socket < 0) {
            perror("Accept failed");
            continue;
        }

        // 5. 读取数据 (听对方说啥)
        char buffer[BUFFER_SIZE] = {0};
        read(new_socket, buffer, BUFFER_SIZE);
        
        // 打印请求内容(调试用)
        // std::cout << "Received Request:\n" << buffer << std::endl;

        // 简单的字符串查找判断 GET
        if (strstr(buffer, "GET") != NULL) {
            
            // 6. 构造响应 (硬编码 HTML)
            // HTTP 响应格式必须严格遵守:状态行 + 响应头 + 空行 + 响应体
            const char *html_content = "<html><body><h1>Hello, World!</h1><p>This is a C++ WebServer.</p></body></html>";
            
            std::string response = "HTTP/1.1 200 OK\r\n";
            response += "Content-Type: text/html\r\n";
            response += "Content-Length: " + std::to_string(strlen(html_content)) + "\r\n";
            response += "\r\n"; // 必须有的空行,分隔头和体
            response += html_content;

            // 7. 发送响应 (说话)
            send(new_socket, response.c_str(), response.length(), 0);
            std::cout << "Response sent to client." << std::endl;
        }

        // 8. 关闭连接 (挂断)
        // 在 HTTP/1.0 中,发完就关。
        close(new_socket);
    }

    // 实际运行中这里不可达,但逻辑上需要关闭监听 socket
    close(server_fd);
    return 0;
}

第三部分:如何运行和测试

请确保你在 Linux 环境(Ubuntu/CentOS)或者 MacOS 下。

1. 编译代码

打开终端,进入代码所在目录,输入:

bash 复制代码
g++ simple_server.cpp -o server

如果没有报错,会生成一个名为 server 的可执行文件。

2. 运行服务器

输入:

bash 复制代码
./server

终端会显示:Server is listening on port 8080...

此时,程序阻塞 在了 accept 函数,正在等待连接。

3. 测试访问

你可以用两种方式测试:

  • 方式 A(浏览器):
    打开浏览器(Chrome/Edge),在地址栏输入:http://localhost:8080
    你将看到页面显示大号的 Hello, World!
  • 方式 B(命令行 curl):
    打开另一个终端窗口,输入:
bash 复制代码
curl -v http://localhost:8080

你会看到详细的 HTTP 握手过程和返回的 HTML。


第四部分:这个版本的"缺陷"在哪?(为了下一阶段学习)

你在运行这个代码时,如果手速够快,或者用脚本同时发 10 个请求,你会发现:

它一次只能服务一个人。

代码逻辑是:
accept -> read -> send -> close -> 回到 accept

如果我在 read 之后加一个 sleep(10)(模拟处理业务很慢),那么在这 10 秒钟内,其他任何人访问这个网页,浏览器都会在那转圈圈(等待连接),因为主线程还在睡觉,没空回到 accept 去接新的电话。

这就是"阻塞式 I/O + 单线程"的致命弱点。

为了解决这个问题,下一阶段(进阶版)通常会引入:

  1. IO 多路复用 (Epoll):不用傻等一个电话,而是雇一个接线员(内核),哪个电话响了通知我处理哪个。
  2. 线程池:来了任务扔给线程池去做,主线程立刻回去接下一个电话。
相关推荐
小迷糊的学习记录几秒前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
梦帮科技41 分钟前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json
VT.馒头1 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
css趣多多1 小时前
一个UI内置组件el-scrollbar
前端·javascript·vue.js
C澒2 小时前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
C澒2 小时前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式
Charlie_lll2 小时前
学习Three.js–雪花
前端·three.js
onebyte8bits2 小时前
前端国际化(i18n)体系设计与工程化落地
前端·国际化·i18n·工程化
C澒2 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
BestSongC2 小时前
行人摔倒检测系统 - 前端文档(1)
前端·人工智能·目标检测