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

news2024/12/28 20:59:42

文章目录

  • 当客户端关闭后,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 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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聚铭网络入选“2024年南京市工程研究中心”认定名单

为深入实施创新驱动发展战略&#xff0c;因地制宜发展新质生产力充分发挥工程研究中心对推进产业强市的重要支撑作用&#xff0c;根据《南京市工程研究中心管理办法》&#xff0c;南京市发展和改革委员会于2024年5月组织开展了本年度南京市工程研究中心遴选工作。经企业申报、各…

编译安装调试 scaLapack 和 openmpi 以及 lapack

编译安装调试 scaLapack /home/hipper/ex_scalapack/ mkdir ./lapack mkdir -p ./lapack/local/lib mkdir ./openmpi mkdir ./scalapack 1&#xff0c;编译安装 Lapack 下载代码&#xff1a; cd lapack wget https://github.com/Reference-LAPACK/lapack/archive/refs/tags/…

[全网首发]2024国赛数学建模ABCE题完整思路+py(matlab)代码+成品论文参考+持续更新

AB题详细思路(含问题一问题二模型) CE题问题一代码思路已经写好[pythonmatlab两种都会更新 需要完整版的看这里&#xff1a; 点击链接加入群聊【2024数学建模国赛资料汇总】&#xff1a;http://qm.qq.com/cgi-bin/qm/qr?_wv1027&klZncBILk30DuPRI1Bd8X-3Djv7ZVZyAv&…

【非零段划分 / 2】

题目 思路 第一种思路&#xff1a;按照表面题意&#xff0c;枚举p&#xff0c;处理数组后进行计数&#xff1a; 复杂度 ∈ O ( n ⋅ m ) 复杂度 \in O(n \cdot m) 复杂度∈O(n⋅m) 第二种思路&#xff1a;把数组看成一个二维的山形图&#xff0c;先将相邻的水平线段转化成点…

Java核心知识体系-并发与多线程:线程基础

1 先导 Java线程基础主要包含如下知识点&#xff0c;相信我们再面试的过程中&#xff0c;经常会遇到类似的提问。 1、线程有哪几种状态? 线程之间如何转变&#xff1f; 2、线程有哪几种实现方式? 各优缺点&#xff1f; 3、线程的基本操作&#xff08;线程管理机制&#xff…

数据资产入表元年,企业如何抓住数据资产增值的机遇?

近年来&#xff0c;政府将数据要素纳入了经济发展的重要指示性文件当中&#xff0c;希望利用数据驱动。《全国数据资源调查报告&#xff08;2023年&#xff09;》显示&#xff0c;2024年以来不少地方纷纷成立“数据集团”&#xff0c;加快盘活数据资产。作为数字经济时代的首要…

Java | Leetcode Java题解之第389题找不同

题目&#xff1a; 题解&#xff1a; class Solution {public char findTheDifference(String s, String t) {int ret 0;for (int i 0; i < s.length(); i) {ret ^ s.charAt(i);}for (int i 0; i < t.length(); i) {ret ^ t.charAt(i);}return (char) ret;} }

编写Dockerfile第二版

目标 更快的构建速度 更小的Docker镜像大小 更少的Docker镜像层 充分利用镜像缓存 增加Dockerfile可读性 让Docker容器使用起来更简单 总结 编写.dockerignore文件 容器只运行单个应用 将多个RUN指令合并为一个 基础镜像的标签不要用latest 每个RUN指令后删除多余文…

leetcode172. 阶乘后的零,遍历每个因数中5的个数

leetcode172. 阶乘后的零 给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * … * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0 示例 2&#xff1a; 输…

第八届控制工程与先进算法国际论坛(IWCEAA 2024)

重要信息 会议大会官网&#xff1a;www.iwaace.cn&#xff08;点击进一步了解&#xff09; 二轮截稿时间&#xff1a;2024年09月27日 录用通知时间&#xff1a;投稿后7个工作日 收录检索类型&#xff1a;IEEE Xplore, EI, Scopus 大会介绍 第八届控制工程与先进算法国际论…

python 交互模式怎么切换目录

假如要用交互界面调用一个.py文件&#xff1a; &#xff08;1&#xff09;用cmd界面定位到文件位置&#xff0c;如cd Desktop/data/ #进入desktop下data目录。 &#xff08;2&#xff09;接着打开python&#xff08;输入python&#xff09; 调用os &#xff08;1&#xff0…

3.js - modelPosition.z = sin((modelPosition.x+uTime)*10.0)*0.05;

哈 1、完整的动态波纹效果吧 main.js import * as THREE from three import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 顶点着色器 import basicVertexShader from ./shader/11-01/raw/vertex.glsl?raw // 片元着色器 import basicFragmentShad…

python OpenGL绘制正八面体代码

学习OpenGL过程中&#xff0c;先从绘制三角形开始逐渐对Opengl有一些了解&#xff0c;到现在进阶到绘制立方体。通过借鉴网友绘制六面体得到灵感&#xff0c;改了一下他的代码成功绘制出正八面体。不管是绘制三角形还是绘制立方体&#xff0c;对于空间坐标系要相当熟悉&#xf…

苍穹外卖项目前端DAY03

前端DAY03 1、套餐管理 1.1、套餐分页查询 1.1.1、需求分析和接口设计 产品原型&#xff1a; 业务规则&#xff1a; 根据页码展示套餐信息每页展示10条数据分页查询时可以根据需要&#xff0c;输入套餐名称、套餐分类、售卖状态进行查询 接口设计&#xff1a; 套餐分页查…

如何使用电商API接口?(淘宝|京东商品详情数据接口)

一、了解电商API接口&#xff1a; 如今&#xff0c;在电商市场中&#xff0c;电商API接口的广泛应用极大地提高了电商行业的工作效率&#xff0c;使得商家能够灵活集成多种服务&#xff0c;高效优化业务流程。 当前&#xff0c;电商平台中的多种业务都可以通过使用API接口来做…

Tomato靶场渗透测试

1.扫描靶机地址 可以使用nmap进行扫描 由于我这已经知道靶机地址 这里就不扫描了 2.打开网站 3.进行目录扫描 dirb http&#xff1a;//172.16.1.113 发现有一个antibot_image目录 4.访问这个目录 可以看到有一个info.php 5.查看页面源代码 可以发现可以进行get传参 6.…

脉脉高聘:大模型算法岗平均月薪近7万元,位居高薪榜第一

9月5日&#xff0c;脉脉高聘人才智库数据显示&#xff0c;大模型领域整体供需比1.76&#xff0c;整体供大于求。同时&#xff0c;高技术岗位人才稀缺&#xff0c;云计算人才供需比仅为0.33&#xff0c;相当于3个岗位争夺1个人才。大模型算法岗位平均月薪最高&#xff0c;超过6.…

【STM32+HAL库】---- 驱动MAX30102心率血氧传感器

硬件开发板&#xff1a;STM32F407VET6 软件平台&#xff1a;cubemaxkeilVScode1 MAX30102心率血氧传感器工作原理 MAX30102传感器是一种集成了红外光源、光电检测器和信号处理电路的高度集成传感器&#xff0c;主要用于心率和血氧饱和度的测量。以下是MAX30102传感器的主要特点…

草料二维码功能上新!可以跨分区移动或复制内容了!

支持将 分区 下的内容移动或复制到其他分区。适用于将原初始分区下的内容按业务划分&#xff0c;移动到其他分区。或者当物品流转或业务变更时&#xff0c;可以及时将码及数据移动到对应分区&#xff0c;移动后&#xff0c;二维码图案不会发生变化。 目前仅表单、活码、批量模…

在修改文件 /ect/hosts时无法保存 can‘t open file for writing

输入&#xff1a;q! 即可 情境&#xff1a; 在Master节点中执行如下命令打开并修改Master节点中的“/etc/hosts”文件&#xff1a; sudo vim /etc/hosts 可以在hosts文件中增加如下两条IP和主机名映射关系&#xff1a; 192.168.1.121 Master 192.168.1.122 Slave1