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
相关产品推荐
相关产品推荐

