You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言OpenSSL单进程接受SSL连接后跨无关联进程复用通信方案咨询

OpenSSL TLS上下文跨execv进程共享实现方案

核心原理

  • 进程调用execv时已打开的socket文件描述符默认会被新进程继承,不需要额外传递
  • OpenSSL原生提供SSL_SESSION序列化/反序列化接口,可将已完成握手的TLS会话状态完整导出、重建,不需要共享内存或文件缓存
  • 重建后的SSL对象可直接关联继承的socket FD,不需要二次握手即可正常收发加密数据,性能和原生单进程TLS连接一致

可运行代码示例

主进程代码(ssl_server.c)

负责监听端口、完成TLS握手、序列化会话后启动子进程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PORT 4433
#define CERT_PATH "cert.pem"
#define KEY_PATH "key.pem"

int main() {
    // 初始化OpenSSL环境
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 初始化SSL上下文
    const SSL_METHOD *method = TLS_server_method();
    SSL_CTX *ctx = SSL_CTX_new(method);
    if (!ctx) { ERR_print_errors_fp(stderr); exit(1); }

    // 加载证书和私钥
    if (SSL_CTX_use_certificate_file(ctx, CERT_PATH, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr); exit(1);
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, KEY_PATH, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr); exit(1);
    }
    if (!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "私钥校验失败\n"); exit(1);
    }

    // 启动监听socket
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = INADDR_ANY};
    bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(listen_fd, 10);

    while (1) {
        int client_fd = accept(listen_fd, NULL, NULL);
        if (fork() == 0) {
            close(listen_fd);
            // 完成TLS握手
            SSL *ssl = SSL_new(ctx);
            SSL_set_fd(ssl, client_fd);
            if (SSL_accept(ssl) <= 0) { ERR_print_errors_fp(stderr); close(client_fd); exit(1); }

            // 序列化TLS会话
            SSL_SESSION *sess = SSL_get1_session(ssl);
            int sess_len = i2d_SSL_SESSION(sess, NULL);
            unsigned char *sess_buf = malloc(sess_len);
            unsigned char *p = sess_buf;
            i2d_SSL_SESSION(sess, &p);
            SSL_SESSION_free(sess);
            SSL_free(ssl);

            // 传递参数给子进程:socket FD、会话长度、会话数据
            char len_str[16], fd_str[16];
            snprintf(len_str, sizeof(len_str), "%d", sess_len);
            snprintf(fd_str, sizeof(fd_str), "%d", client_fd);
            execv("./ssl_worker", (char*[]){"ssl_worker", fd_str, len_str, sess_buf, NULL});
            
            perror("execv调用失败");
            free(sess_buf);
            close(client_fd);
            exit(1);
        }
        close(client_fd);
    }

    SSL_CTX_free(ctx);
    close(listen_fd);
    return 0;
}

子进程代码(ssl_worker.c)

被execv启动,重建TLS上下文后直接处理客户端请求

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define CERT_PATH "cert.pem"
#define KEY_PATH "key.pem"

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "参数错误\n");
        return 1;
    }

    int client_fd = atoi(argv[1]);
    int sess_len = atoi(argv[2]);
    unsigned char *sess_buf = (unsigned char*)argv[3];

    // 初始化OpenSSL环境
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    // 初始化和主进程配置完全一致的SSL上下文
    const SSL_METHOD *method = TLS_server_method();
    SSL_CTX *ctx = SSL_CTX_new(method);
    if (!ctx) { ERR_print_errors_fp(stderr); return 1; }
    if (SSL_CTX_use_certificate_file(ctx, CERT_PATH, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr); return 1;
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, KEY_PATH, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr); return 1;
    }

    // 反序列化TLS会话
    SSL_SESSION *sess = d2i_SSL_SESSION(NULL, (const unsigned char**)&sess_buf, sess_len);
    if (!sess) { ERR_print_errors_fp(stderr); return 1; }

    // 重建TLS连接
    SSL *ssl = SSL_new(ctx);
    SSL_set_fd(ssl, client_fd);
    SSL_set_session(ssl, sess);
    SSL_SESSION_free(sess);

    // 直接收发加密数据,不需要二次握手
    char buf[1024] = {0};
    int recv_len = SSL_read(ssl, buf, sizeof(buf)-1);
    if (recv_len > 0) {
        printf("收到客户端数据: %s\n", buf);
        const char *resp = "TLS连接上下文共享成功,来自子进程的响应";
        SSL_write(ssl, resp, strlen(resp));
    }

    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    close(client_fd);
    return 0;
}

编译运行步骤

  • 生成测试证书:openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
  • 编译主进程:gcc ssl_server.c -o ssl_server -lssl -lcrypto
  • 编译子进程:gcc ssl_worker.c -o ssl_worker -lssl -lcrypto
  • 运行主进程:./ssl_server
  • 测试连接:openssl s_client -connect 127.0.0.1:4433,输入任意内容即可收到子进程返回的响应

生产环境优化建议

  • 示例中将会话数据作为启动参数传递仅作演示,实际使用时建议通过匿名管道传递序列化后的会话数据,避免超出操作系统进程启动参数长度限制
  • 序列化后的会话数据包含敏感密钥信息,不要写入持久化存储,传递完成后立即清空内存
  • 如果需要支持会话复用,可在主进程中统一维护会话缓存,子进程重建上下文时按需读取即可

内容的提问来源于stack exchange,提问作者ekhanad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 01:54:04