记录ssl epoll的tcp socket服务端在客户端断开时崩溃的问题

2024-09-05 18:04

本文主要是介绍记录ssl epoll的tcp socket服务端在客户端断开时崩溃的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 当客户端关闭后,Epoll 的 TCP socket 服务端会收到两次断开事件可能有以下原因及解决方法:
    • 原因分析
    • 解决方法
  • 问题ssl socket服务端代码
  • 出错现象
  • 第一次尝试修改
  • 正确改法
  • 附上客户端代码

记录ssl epoll的tcp socket服务端在客户端断开时接收到多次disconnect事件导致崩溃的问题.
流程:在linux服务器上跑socke服务, 客户端连接后发送数据,服务端回复, 客户端接收到回复后关闭.

当客户端关闭后,Epoll 的 TCP socket 服务端会收到两次断开事件可能有以下原因及解决方法:

原因分析

  1. 网络延迟或数据包重传
    网络环境不稳定可能导致数据包延迟到达或重传。当客户端关闭连接时,发送的断开信号可能由于网络延迟在服务端被接收多次。
    这种情况下,服务端可能会误认为有多个断开事件发生。
  2. 服务端处理逻辑问题
    服务端在处理断开事件时,如果没有正确地标记已处理的事件或者没有及时清理相关资源,可能会导致后续再次触发相同的断开事件。
    例如,在处理第一个断开事件后,服务端可能没有将对应的文件描述符从 Epoll 实例中移除,导致下一次轮询时再次触发该事件。
  3. SSL 层的影响
    如果使用了 SSL 加密,SSL 层的关闭过程可能会与底层的 TCP 连接关闭产生交互问题,导致服务端收到多次断开事件。
    SSL 的关闭握手过程可能会产生额外的数据包,被服务端误认为是新的断开事件。

解决方法

  1. 处理网络问题
    可以通过优化网络环境、增加超时时间等方式来减少网络延迟和数据包重传的影响。
    对于关键的应用,可以考虑使用更稳定的网络连接或者采用网络冗余技术。
  2. 优化服务端处理逻辑
    在服务端处理断开事件时,确保正确地标记已处理的事件,并及时清理相关资源。
    例如,在处理断开事件后,立即将对应的文件描述符从 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 1024void 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.fdSSL* 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 1024void 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;
}

作者:帅得不敢出门 原创文章谢绝转载

这篇关于记录ssl epoll的tcp socket服务端在客户端断开时崩溃的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1139676

相关文章

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

解决Nginx启动报错Job for nginx.service failed because the control process exited with error code问题

《解决Nginx启动报错Jobfornginx.servicefailedbecausethecontrolprocessexitedwitherrorcode问题》Nginx启... 目录一、报错如下二、解决原因三、解决方式总结一、报错如下Job for nginx.service failed bec

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

MySQ中出现幻读问题的解决过程

《MySQ中出现幻读问题的解决过程》文章解析MySQLInnoDB通过MVCC与间隙锁机制在可重复读隔离级别下解决幻读,确保事务一致性,同时指出性能影响及乐观锁等替代方案,帮助开发者优化数据库应用... 目录一、幻读的准确定义与核心特征幻读 vs 不可重复读二、mysql隔离级别深度解析各隔离级别的实现差异

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

Linux部署中的文件大小写问题的解决方案

《Linux部署中的文件大小写问题的解决方案》在本地开发环境(Windows/macOS)一切正常,但部署到Linux服务器后出现模块加载错误,核心原因是Linux文件系统严格区分大小写,所以本文给大... 目录问题背景解决方案配置要求问题背景在本地开发环境(Windows/MACOS)一切正常,但部署到

MySQL磁盘空间不足问题解决

《MySQL磁盘空间不足问题解决》本文介绍查看空间使用情况的方式,以及各种空间问题的原因和解决方案,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录查看空间使用情况Binlog日志文件占用过多表上的索引太多导致空间不足大字段导致空间不足表空间碎片太多导致空间不足临时表空间