C语言如何实现HTTPS监听?

99ANYc3cd6
预计阅读时长 23 分钟
位置: 首页 C语言 正文

使用一个支持 SSL/TLS 的网络库来处理底层的 TCP 通信、握手和加密/解密,而你的业务逻辑则专注于处理 HTTP 请求和生成 HTTP 响应。

c语言 https监听
(图片来源网络,侵删)

目前最常用和最推荐的库是 OpenSSL,下面我将详细介绍如何使用 OpenSSL 来构建一个简单的 HTTPS 监听器。


核心概念

  1. HTTPS = HTTP + SSL/TLS:HTTPS 本质上是在 HTTP 协议(应用层)和 TCP 协议(传输层)之间加入了一个 SSL/TLS 加密层。
  2. SSL/TLS 握手:当客户端(如浏览器)连接到 HTTPS 服务器时,它们会进行一个“握手”过程,这个过程包括:
    • 协商一个加密算法(如 AES-256, RSA)。
    • 验证服务器的身份(通过数字证书)。
    • 生成并交换对称密钥,用于后续的加密通信。
  3. 数字证书:这是服务器身份的“身份证”,它由受信任的证书颁发机构(CA)签发,包含服务器的公钥和一些身份信息,你需要为你的服务器准备一个证书文件(通常是 .crt.pem)和对应的私钥文件(通常是 .key)。
    • 自签名证书:用于开发和测试,它不受公共 CA 信任,所以浏览器会显示“不安全”的警告。
    • 权威机构签名的证书:用于生产环境,需要购买或申请(如 Let's Encrypt 提供免费证书)。

准备工作:安装 OpenSSL 和生成证书

安装 OpenSSL

在 Linux (Debian/Ubuntu) 上:

sudo apt-get update
sudo apt-get install libssl-dev

在 macOS 上 (使用 Homebrew):

brew install openssl

在 Windows 上,可以从 OpenSSL 官网 下载安装包。

c语言 https监听
(图片来源网络,侵删)

生成自签名证书和私钥

我们将创建一个名为 server.pem 的文件,它同时包含了证书和私钥(OpenSSL 可以方便地处理这种格式)。

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes

命令解释:

  • req -x509: 生成一个 X.509 格式的自签名证书。
  • -newkey rsa:4096: 同时生成一个新的 4096 位的 RSA 密钥对。
  • -keyout key.pem: 将私钥保存到 key.pem 文件。
  • -out cert.pem: 将证书保存到 cert.pem 文件。
  • -sha256: 使用 SHA-256 算法签名。
  • -days 365: 证书有效期 365 天。
  • -nodes: "No DES" 的缩写,表示私钥不使用密码加密,这样程序读取时更方便(但安全性较低,生产环境应使用密码保护)。

为了方便,我们将这两个文件合并成一个 server.pem

cat cert.pem key.pem > server.pem

你有了 server.pem 文件,可以开始写代码了。


使用 OpenSSL 实现 HTTPS 监听器(C 语言)

下面的代码实现了一个简单的 HTTPS 服务器,它会监听 4433 端口,当有客户端连接时,它会发送一个 "Hello, HTTPS World!" 的响应。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define PORT 4433
#define CERT_FILE "server.pem"
// 初始化 OpenSSL
void init_openssl() {
    SSL_load_error_strings();
    OpenSSL_add_ssl_algorithms();
}
// 清理 OpenSSL
void cleanup_openssl() {
    EVP_cleanup();
}
// 创建 SSL 上下文
SSL_CTX *create_context() {
    const SSL_METHOD *method;
    SSL_CTX *ctx;
    // 使用 TLS 方法
    method = TLS_server_method();
    // 创建 SSL 上下文
    ctx = SSL_CTX_new(method);
    if (!ctx) {
        perror("Unable to create SSL context");
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    return ctx;
}
// 配置 SSL 上下文,加载证书和私钥
void configure_context(SSL_CTX *ctx) {
    // 加载证书
    if (SSL_CTX_use_certificate_file(ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
    // 加载私钥
    if (SSL_CTX_use_PrivateKey_file(ctx, CERT_FILE, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(EXIT_FAILURE);
    }
}
int main() {
    int sock;
    struct sockaddr_in addr;
    SSL_CTX *ctx;
    SSL *ssl;
    int client;
    // --- 1. 初始化 ---
    init_openssl();
    ctx = create_context();
    configure_context(ctx);
    // --- 2. 创建 TCP 监听套接字 ---
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("Unable to create socket");
        exit(EXIT_FAILURE);
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    // 绑定套接字
    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("Unable to bind");
        exit(EXIT_FAILURE);
    }
    // 开始监听
    if (listen(sock, 1) < 0) {
        perror("Unable to listen");
        exit(EXIT_FAILURE);
    }
    printf("HTTPS Server listening on port %d...\n", PORT);
    // --- 3. 接受客户端连接 ---
    client = accept(sock, (struct sockaddr*)&addr, (socklen_t*)&addr);
    if (client < 0) {
        perror("Unable to accept");
        exit(EXIT_FAILURE);
    }
    // --- 4. 创建 SSL 连接 ---
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, client);
    // 执行 SSL 握手
    if (SSL_accept(ssl) <= 0) {
        ERR_print_errors_fp(stderr);
    } else {
        char response[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, HTTPS World!";
        // 通过 SSL 连接发送数据
        SSL_write(ssl, response, strlen(response));
        printf("HTTPS message sent.\n");
    }
    // --- 5. 清理 ---
    SSL_free(ssl);
    close(client);
    SSL_CTX_free(ctx);
    close(sock);
    cleanup_openssl();
    return 0;
}

如何编译和运行

  1. 保存代码:将上面的代码保存为 https_server.c

  2. 确保证书存在:确保 server.pem 文件和 https_server.c 在同一个目录下。

  3. 编译:你需要链接 OpenSSL 的库 (sslcrypto)。

    gcc https_server.c -o https_server -lssl -lcrypto
  4. 运行

    ./https_server

    你会看到输出:HTTPS Server listening on port 4433...

  5. 测试

    • 打开你的浏览器。
    • 在地址栏输入 https://localhost:4433
    • 由于我们使用的是自签名证书,浏览器会显示一个“不安全”的警告,点击“高级”,然后选择“继续访问”。
    • 如果一切正常,你将看到页面内容:Hello, HTTPS World!

代码分步解析

  1. init_openssl()cleanup_openssl()

    这是 OpenSSL 的标准初始化和清理函数,用于注册必要的算法和错误处理机制。

  2. create_context()

    • 创建一个 SSL_CTX (SSL Context) 对象,这个对象可以看作是一个“工厂”或“配置模板”,用于后续创建多个 SSL 连接,我们指定了 TLS_server_method 来告诉 OpenSSL 我们要创建一个 TLS 服务器。
  3. configure_context()

    • 这是 HTTPS 的核心,它加载了之前生成的 server.pem 文件,其中包含了服务器的证书和私钥,服务器在握手时会使用这些信息来证明自己的身份。
  4. TCP 监听部分

    • 这部分和普通的 C 语言 TCP 服务器完全一样:创建套接字 (socket),绑定地址 (bind),开始监听 (listen),它只负责建立底层的 TCP 连接。
  5. SSL 连接部分

    • accept():接受来自客户端的 TCP 连接,返回一个新的套接字描述符 client
    • SSL_new():基于之前创建的 ctx,创建一个新的 SSL 对象,代表一个具体的 SSL 连接。
    • SSL_set_fd():将底层的 TCP 套接字 (client) 与这个 SSL 对象关联起来。
    • SSL_accept():这是关键的一步!它会执行 SSL/TLS 握手过程,它会读取客户端的 "ClientHello" 消息,验证客户端,并协商加密参数,如果成功,后续的通信就是加密的了。
    • SSL_write()SSL_read()
      • 一旦握手成功,你就不能再使用 write()read() 了。
      • 必须使用 OpenSSL 提供的 SSL_write()SSL_read() 函数,这些函数会在数据发送/接收前自动进行加密/解密,对上层应用是透明的。
    • SSL_free()SSL_CTX_free():释放 SSL 连接和上下文对象所占用的资源。

进阶和注意事项

  • 多线程/多进程:上面的例子一次只能处理一个客户端,在生产环境中,你需要使用多线程(每个连接一个线程)、I/O 多路复用(如 select, poll, epoll)或线程池来同时处理成千上万个并发连接。
  • 错误处理:示例中的错误处理比较简单,一个健壮的服务器应该对 SSL_accept, SSL_read, SSL_write 等函数的返回值进行更细致的检查,并根据不同的错误类型采取不同的恢复或关闭连接的措施。
  • HTTP 协议解析:这个例子只是简单地发送了一个固定的字符串,一个真正的 Web 服务器需要解析 HTTP 请求头(如 GET /index.html HTTP/1.1),然后根据请求的路径和内容动态生成响应,你可以自己写解析逻辑,也可以集成像 libhttpdmongoose 这样的轻量级 HTTP 库。
  • 性能:SSL/TLS 握手是比较耗时的,为了优化性能,可以使用 会话恢复 机制,避免为每个连接都重新进行完整的握手,OpenSSL 支持会话缓存和 TLS 会话票证来实现这一点。
-- 展开阅读全文 --
头像
织梦文章标题无法显示
« 上一篇 03-31
织梦首页如何调用指定文章?
下一篇 » 03-31

相关文章

取消
微信二维码
支付宝二维码

目录[+]