要将上述HTTPS服务器代码修改为基于事件驱动的实现,我们可以使用select()
或epoll()
等I/O多路复用技术。以下是一个使用select()
的简单示例:
1. 修改后的代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define PORT 443
#define BACKLOG 10
#define MAX_CLIENTS 100
void handle_client(SSL *ssl) {
const char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from HTTPS Server!";
if (SSL_write(ssl, response, strlen(response)) <= 0) {
ERR_print_errors_fp(stderr);
}
}
int main() {
SSL_CTX *ctx;
// 初始化OpenSSL库
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
// 创建并初始化上下文
ctx = SSL_CTX_new(TLS_server_method());
// 加载证书和私钥
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0 ||
SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
// 创建socket并绑定到指定端口
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
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);
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
listen(server_fd, BACKLOG);
printf("Server started on port %d...\n", PORT);
fd_set read_fds;
int max_sd;
int client_sockets[MAX_CLIENTS] = {0}; // 存储客户端套接字
while (1) {
FD_ZERO(&read_fds);
FD_SET(server_fd, &read_fds); // 将服务器套接字添加到集合
max_sd = server_fd;
for (int i = 0; i < MAX_CLIENTS; i++) {
int sd = client_sockets[i];
if (sd > 0)
FD_SET(sd, &read_fds); // 添加活动的客户端套接字
if (sd > max_sd)
max_sd = sd; // 更新最大套接字值
}
// 等待活动连接
if (select(max_sd + 1, &read_fds, NULL, NULL, NULL) < 0) {
perror("select error");
break;
}
// 检查是否有新的客户端连接请求
if (FD_ISSET(server_fd, &read_fds)) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd >= 0) {
printf("New connection: socket fd is %d\n", client_fd);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_sockets[i] == 0) {
client_sockets[i] = client_fd;
break;
}
}
// 为每个客户端创建SSL连接
SSL *ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_fd);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
close(client_fd);
continue;
} else {
handle_client(ssl); // Handle the client request
}
// 清理和关闭连接
SSL_shutdown(ssl);
SSL_free(ssl);
close(client_fd);
}
}
for(int i=0; i<MAX_CLIENTS; i++){
int sd=client_sockets[i];
if(FD_ISSET(sd,&read_fds)){
char buffer[1024];
memset(buffer,'\0',sizeof(buffer));
int val_read=recv(sd , buffer , sizeof(buffer)-1 , MSG_DONTWAIT );
if(val_read==0){
getpeername(sd , (struct sockaddr*)&client_addr , \
&addr_len );
printf("Host disconnected , ip %s , port %d \n" ,
inet_ntoa(client_addr.sin_addr) , ntohs(client_addr.sin_port));
close(sd);
client_sockets[i]=0;
}
}
}
}
close(server_fd);
SSL_CTX_free(ctx);
return EXIT_SUCCESS;
}
修改说明
事件驱动:我们使用了
select()
函数来处理多个客户端连接。主循环中会监控所有活跃的客户端套接字,并在有新的数据可读时进行处理。FD_SET 和 FD_ISSET:这些宏用于将文件描述符添加到集合以及检查集合中的文件描述符。
动态管理客户端:通过
client_sockets
数组动态管理多个客户端的套接字,以便能够同时处理多个连接。
编译与运行
确保在同一目录下存在自签名证书和私钥,然后编译并运行服务器:
gcc https_server.c -o https_server -lssl -lcrypto
sudo ./https_server
测试服务器
使用浏览器或命令行工具(如curl)访问你的服务器,记得加上 -k
参数以忽略自签名证书警告:
curl -k https://localhost/
这样,你就完成了一个基于C语言实现的事件驱动HTTPS服务器。这种设计允许高效地处理多个并发连接。根据需要,可以进一步扩展以增加更多功能,如更复杂的请求处理、日志记录等。
内容由零声教学AI助手提供,问题来源于学员提问