文章目录
- 当客户端关闭后,Epoll 的 TCP socket 服务端会收到两次断开事件可能有以下原因及解决方法:
- 原因分析
- 解决方法
- 问题ssl socket服务端代码
- 出错现象
- 第一次尝试修改
- 正确改法
- 附上客户端代码
记录ssl epoll的tcp socket服务端在客户端断开时接收到多次disconnect事件导致崩溃的问题.
流程:在linux服务器上跑socke服务, 客户端连接后发送数据,服务端回复, 客户端接收到回复后关闭.
当客户端关闭后,Epoll 的 TCP socket 服务端会收到两次断开事件可能有以下原因及解决方法:
原因分析
- 网络延迟或数据包重传
网络环境不稳定可能导致数据包延迟到达或重传。当客户端关闭连接时,发送的断开信号可能由于网络延迟在服务端被接收多次。
这种情况下,服务端可能会误认为有多个断开事件发生。 - 服务端处理逻辑问题
服务端在处理断开事件时,如果没有正确地标记已处理的事件或者没有及时清理相关资源,可能会导致后续再次触发相同的断开事件。
例如,在处理第一个断开事件后,服务端可能没有将对应的文件描述符从 Epoll 实例中移除,导致下一次轮询时再次触发该事件。 - SSL 层的影响
如果使用了 SSL 加密,SSL 层的关闭过程可能会与底层的 TCP 连接关闭产生交互问题,导致服务端收到多次断开事件。
SSL 的关闭握手过程可能会产生额外的数据包,被服务端误认为是新的断开事件。
解决方法
- 处理网络问题
可以通过优化网络环境、增加超时时间等方式来减少网络延迟和数据包重传的影响。
对于关键的应用,可以考虑使用更稳定的网络连接或者采用网络冗余技术。 - 优化服务端处理逻辑
在服务端处理断开事件时,确保正确地标记已处理的事件,并及时清理相关资源。
例如,在处理断开事件后,立即将对应的文件描述符从 Epoll 实例中移除,避免再次触发相同的事件。
问题ssl socket服务端代码
这段代码是用AI工具自动生成的, 拿来调试后发现有问题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAX_EVENTS 10
#define BUFFER_SIZE 1024
void handle_error(const char* msg) {
perror(msg);
exit(EXIT_FAILURE);
}
SSL_CTX* init_ssl_context() {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_CTX* ctx = SSL_CTX_new(TLS_server_method());
if (!ctx) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
void handle_client(SSL* ssl, int epollfd, int client_socket) {
char buffer[BUFFER_SIZE];
int bytes_read = SSL_read(ssl, buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received: %s", buffer);
SSL_write(ssl, buffer, bytes_read);
} else {
if (bytes_read == 0) {
printf("Connection closed by client\n");
} else {
ERR_print_errors_fp(stderr);
}
close(client_socket);
SSL_shutdown(ssl);
SSL_free(ssl);
epoll_ctl(epollfd, EPOLL_CTL_DEL, client_socket, NULL);
}
}
int main() {
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
SSL_CTX* ssl_ctx = init_ssl_context();
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
handle_error("socket");
}
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(12345);
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
handle_error("bind");
}
if (listen(server_socket, 10) == -1) {
handle_error("listen");
}
int epollfd = epoll_create1(0);
if (epollfd == -1) {
handle_error("epoll_create1");
}
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_socket;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
handle_error("epoll_ctl");
}
printf("EPOLL_CTL_ADD:%d\n", server_socket);
struct epoll_event events[MAX_EVENTS];
while (1) {
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
handle_error("epoll_wait");
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_socket) {
client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
handle_error("accept");
}
SSL* ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, client_socket);
if (SSL_accept(ssl) <= 0) {
ERR_print_errors_fp(stderr);
close(client_socket);
continue;
}
event.events = EPOLLIN;
event.data.ptr = ssl; // 重点是这两行, 后面会分析
//event.data.fd = client_socket;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_socket, &event) == -1) {
handle_error("epoll_ctl");
}
printf("New client connected, EPOLL_CTL_ADD:%d, ssl:%p\n", client_socket, ssl);
} else {
// 这里用了data.ptr 与data.fd
SSL* ssl = (SSL*)events[i].data.ptr;
printf("data fd:%d ssl:%p\n", events[i].data.fd, ssl);
handle_client(ssl, epollfd, events[i].data.fd);
}
}
}
close(server_socket);
SSL_CTX_free(ssl_ctx);
return 0;
}
出错现象
EPOLL_CTL_ADD:3
New client connected, EPOLL_CTL_ADD:5
Received: 1
Connection closed by client
Connection closed by client
段错误 (核心已转储)
经过调试发现,每次客户端close后,服务端会接收到两次的disconnect事件, 导致重复关闭从而崩溃.
第一次尝试修改
handle_client(SSL* ssl, int epollfd, int client_socket)中会调用close(client_socket), 发现client_socket值没传.
event.data.ptr = ssl; // 重点是这两行, 后面会分析
event.data.fd = client_socket;// 把这行注释打开,把client_socket传递到event中
这么改后,还是会崩溃.
通过man查看event的结构体epoll_data原型, 发现data是union类型, ptr与fd不能同时赋值, 所以第一次改法失败.
epoll_data结构如下
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
正确改法
client_socket从ssl中获取,不需要额外传递, 如图, 左边是问题代码, 右边是改正过的代码
附上客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
void handle_error(const char* msg) {
perror(msg);
exit(EXIT_FAILURE);
}
SSL_CTX* init_ssl_context() {
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
ERR_print_errors_fp(stderr);
exit(EXIT_FAILURE);
}
return ctx;
}
int main() {
int client_socket;
struct sockaddr_in server_addr;
SSL_CTX* ssl_ctx = init_ssl_context();
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == -1) {
handle_error("socket");
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(12345);
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
handle_error("connect");
}
SSL* ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, client_socket);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
close(client_socket);
exit(EXIT_FAILURE);
}
char buffer[BUFFER_SIZE];
printf("Enter a message to send to the server: ");
fgets(buffer, BUFFER_SIZE, stdin);
SSL_write(ssl, buffer, strlen(buffer));
int bytes_read = SSL_read(ssl, buffer, BUFFER_SIZE - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received from server: %s", buffer);
} else {
ERR_print_errors_fp(stderr);
}
SSL_shutdown(ssl);
SSL_free(ssl);
close(client_socket);
SSL_CTX_free(ssl_ctx);
return 0;
}
作者:帅得不敢出门 原创文章谢绝转载