C++ 纯原生方式实现简单HTTP服务(附完整代码,直接可用)

大家好,我是 同学小张,+v: jasper_8017 一起交流,持续学习C++进阶、OpenGL、WebGL知识AI大模型应用实战案例,持续分享,欢迎大家点赞+关注,共同学习和进步。


如果C++程序中需要接收HTTP消息,但需求不大,不需要复杂的网络模块的处理,不需要考虑高并发等场景。这时候如果用第三方库就有点大材小用了。直接用C++原生方式手搓一个HTTP服务就比较方便且轻便了,不用安装第三方库,不用链接动态链接库,不用解决其它一些依赖问题。

本文我们使用c++的socket接口,从0到1实现一个可用的简单的http server。

前置知识(socket接口的使用)可以参考前面的文章:C++中的socket网络编程,兼看TCP的三次握手与四次挥手过程(附常见面试题整理)

文章目录

  • 1. 主要代码
  • 2. 代码中的一些细节处理
    • 2.1 地址端口重用
      • 2.1.1 端口重用的意义和作用
      • 2.1.2 使用 `SO_REUSEADDR` 的注意事项
    • 2.2 在一些错误的地方可以加一些日志来发现连接失败的原因
  • 3. 处理 HTTP 请求
  • 4. 头文件与主函数
  • 5. 总结

1. 主要代码

直接先上主要代码:

void server(unsigned short port) {
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        return;
    }

    sockaddr_in server_addr;
    std::memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(port);

    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        close(server_socket);
        return;
    }

    if (listen(server_socket, 5) == -1) {
        close(server_socket);
        return;
    }

    while (true) {
        sockaddr_in client_addr;
        socklen_t client_addr_len = sizeof(client_addr);
        int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
        if (client_socket == -1) {
            continue;
        }

        std::thread(handle_client, client_socket).detach();
    }
    close(server_socket);
}

这是 socket 服务建立的主要流程:

(1)首先socket函数创建一个新的socket:socket(AF_INET, SOCK_STREAM, 0)

(2)bind指定端口:if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1)

(3)listen监听起来:if (listen(server_socket, 5) == -1)

(4)accept 接收连接请求:accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);

(5)接收到新的连接之后,通过 std::thread(handle_client, client_socket).detach(); 来新开一个线程处理这个链接

(6)关闭服务socket:close(server_socket);

2. 代码中的一些细节处理

以上主要流程中,实际应用时可能还需要加一点细节:

2.1 地址端口重用

在bind之前,加入以下代码:

int reuse_option = 1;
if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &reuse_option, sizeof(reuse_option)) == -1) {
    // 处理错误
}

使用 SO_REUSEADDR 这个选项时,它允许一个已绑定到特定端口的 socket 可以被快速重用,即使该端口上的 TCP 连接在完成正常的四次挥手后不久,端口仍然处于 TIME_WAIT 状态。SO_REUSEADDR 主要适用于服务器端的 socket,因为它需要频繁地创建和关闭连接。对于客户端 socket,通常不需要设置这个选项。

setsockopt() 函数在 C++ 网络编程中用来设置 socket 的选项。当使用 SO_REUSEADDR 这个选项时,它允许一个已绑定到特定端口的 socket 可以被快速重用,即使该端口上的 TCP 连接在完成正常的四次挥手后不久,端口仍然处于 TIME_WAIT 状态。

2.1.1 端口重用的意义和作用

(1)加快端口重用速度

  • 在 TCP 连接关闭后,端口通常会进入 TIME_WAIT 状态,这段时间内端口不能被立即重用。SO_REUSEADDR 允许端口在 TIME_WAIT 状态下被重用,从而加快了端口的重用速度。

(2)避免端口耗尽

  • 如果应用程序频繁地创建和关闭连接,端口可能会很快耗尽。通过允许端口重用,可以减少端口耗尽的风险。

(3)提高服务器性能

  • 对于需要处理大量并发连接的服务器来说,端口重用可以减少等待端口释放的时间,从而提高服务器的响应速度和性能。

(4)简化编程模型

  • 开发者不需要编写额外的代码来处理端口耗尽的情况,简化了编程模型。

(5)避免端口冲突

  • 在多进程或多线程环境中,不同的进程或线程可能会尝试绑定到同一个端口。使用 SO_REUSEADDR 可以避免这种情况导致的端口冲突。

2.1.2 使用 SO_REUSEADDR 的注意事项

  • 安全性问题
    • 允许端口重用可能会带来一些安全风险,因为端口可以被快速重用,恶意用户可能会利用这一点来发起 SYN Flood 攻击。因此,在使用 SO_REUSEADDR 时,需要权衡性能和安全性。

2.2 在一些错误的地方可以加一些日志来发现连接失败的原因

例如:

if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
    if (errno == EADDRINUSE) {
        // 可以在这里加入重试逻辑,或者选择另一个端口等
    } else {
        // 其他类型的bind错误处理
    }
    close(server_socket);
    return;
}

通过errorno 可以知道bind错误时具体的原因,从而作出相应的处理。

3. 处理 HTTP 请求

上面提到,在建立一个新的连接之后,会通过 std::thread(handle_client, client_socket).detach(); 来新开一个线程处理这个连接。处理这个连接的函数,我们这里叫 handle_client

void handle_client(int client_socket) {
    char buffer[1024];
    std::string response;
    // Read the request
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
    if (bytes_received > 0) {
        buffer[bytes_received] = '\0';
        std::string request(buffer);
        
        // Parse the request to extract parameters
        std::string request_line = request.substr(0, request.find("\r\n"));
        std::istringstream request_stream(request_line);
        std::string method, uri, version;
        request_stream >> method >> uri >> version;

        // Check if the requested URI is "/set_value"
        if (uri.find("/set_value") == 0) {
          // TODO 函数的具体执行逻辑
            response =
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/plain\r\n"
                "Content-Length: " + std::to_string(success_message.size()) + "\r\n"
                "\r\n" + success_message;
            }     
        } else {
            // Handle not found
            std::string error_message = "Error: API Not Found";
            response =
                "HTTP/1.1 404 Not Found\r\n"
                "Content-Type: text/plain\r\n"
                "Content-Length: " + std::to_string(error_message.size()) + "\r\n"
                "\r\n" + error_message;
        }
    } else {
        std::string error_message = "Error: Bad Request";
        response =
            "HTTP/1.1 400 Bad Request\r\n"
            "Content-Type: text/plain\r\n"
            "Content-Length: " + std::to_string(error_message.size()) + "\r\n"
            "\r\n" + error_message;
    }

    // Send the response
    send(client_socket, response.c_str(), response.size(), 0);

    // Close the client socket
    close(client_socket);
}

这个函数里的内容:

(1)通过 recv 函数接收客户端发来的消息内容

(2)处理消息,组织成 HTTP 的回复消息格式

(3)send 函数将回复消息发送给客户端

(4)close(client_socket); 关闭这一次的连接

其中,处理消息时,通过 uri.find("/set_value") 来区分 HTTP 调用的接口,这样就可以实现接口的分发。

4. 头文件与主函数

#include <iostream>
#include <thread>
#include <sstream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    unsigned int port = 8080;
    std::thread server_thread([port]() {
        server(port);
    });
    server_thread.detach();

    // 主线程,其它处理逻辑
}

这里,新开一个线程来启动socket服务,不阻塞主线程其它逻辑。

编译和运行:

g++ -std=c++11 -o simple_server simple_server.cpp -pthread
./simple_server

运行之后,可以使用浏览器或curl命令访问和测试服务器。

5. 总结

本文从0开始利用C++ socket相关函数用原生的方式实现了一个简单的HTTP服务,并提供了一些细节说明。

以上程序大家可以直接粘贴使用。有问题欢迎 +v jasper_8017 来交流讨论。

如果觉得本文对你有帮助,麻烦点个赞和关注呗 ~~~


  • 大家好,我是 同学小张,持续学习C++进阶、OpenGL、WebGL知识AI大模型应用实战案例
  • 欢迎 点赞 + 关注 👏,持续学习持续干货输出
  • +v: jasper_8017 一起交流💬,一起进步💪。
  • 微信公众号搜同学小张 🙏

本站文章一览:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

同学小张

如果觉得有帮助,欢迎给我鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值