openSSL SSL_read 总读一个字节

最近公司项目需要把服务器端C++实现的 websocket 改成websocket secure(websocket + ssl)。百度了一下有一个广泛使用的ssl库,openssl。花了两天时间一顿操作之后客户端用js终于连接上服务器上的wss。但是诡异的是,每次客户端来消息后,服务器调用SSL_read函数总是会读完一个字节后返回,第二次调用(epoll控制)才会读完剩下的字节。
比如客户端发了502字节的数据包,服务器这边epoll响应 调用到SSL_read,会先读1个字节,epoll再次响应,再读剩下的501个字节。等于调用了两次read才把一个数据包读完!
百思不得其解啊,在客户端用wrieshark抓包,客户端从来没发过数据长度位1的有效包。说明不是客户端问题。
只好去翻ssl manual ,怀疑是不是什么参数没设置。也没发现有什么参数可设置的。。
最后,怀疑是协议版本的问题。。然后我把把openssl 用的ssl协议从TSL1.0,改成了TSL1.2,就尼玛好了。。可能是js websocket类用的ssl协议版本高于1.0,结果导致这个问题。
好吧,差点怀疑人生。不知道有没有朋友和我遇到过一样的问题。

顺便把我参考网上的代码封装的openssl类贴一下,一个openSSL类,一个SSL管理类。

//
// Created by jamesjiang on 2018/5/15.
//

#ifndef GAME_OPENSSL_H
#define GAME_OPENSSL_H

#include <string>
#include "common_tcpconnection.h"
#include <openssl/ssl.h>
#include <openssl/err.h>

using namespace GameJayo::Server;

class openSSL
{
public:
    openSSL();
    ~openSSL();

public:

    // 初始化 openssl 上下文
    static int32_t  init_ssl_ctx();
    static void     shutdown_ssl();

    int32_t     creat_ssl(int32_t fd);
    int32_t     accept_ssl_fd(int32_t fd);

    // return > 0 recv 字节数, < 0 error , == 0 判断
    int32_t     ssl_recv(char* data, int32_t size);

    // return > 0 send 字节数, < 0 error , == 0 判断
    int32_t     ssl_send(char* data, int32_t size);


private:

    SSL* m_ssl;
    int32_t m_fd;
    static SSL_CTX* m_ctx;

public:
    void ShowCerts();
};


#endif //GAME_OPENSSL_H
//
// Created by jamesjiang on 2018/5/15.
//

#include "openSSL.h"

#define CA_CERT_FILE "ssl/123u.com_combined.crt"
#define SERVER_CERT_FILE "ssl/123u.com_combined.crt"
#define SERVER_KEY_FILE "ssl/123u.com.key"

SSL_CTX* openSSL::m_ctx = NULL;

openSSL::openSSL()
{
}

openSSL::~openSSL()
{
    /* 关闭 SSL 连接 */
    if (m_ssl)
    {
        SSL_shutdown(m_ssl);
        /* 释放 SSL */
        SSL_free(m_ssl);
    }
    m_fd = -1;
    close(m_fd);
}

int32_t openSSL::init_ssl_ctx()
{
    /* SSL 库初始化 */
    SSL_library_init();
    /* 载入所有 SSL 算法 */
    OpenSSL_add_all_algorithms();
    /* 载入所有 SSL 错误消息 */
    SSL_load_error_strings();
    /* 以 TSL1.2 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
    m_ctx = SSL_CTX_new(TLSv1_2_server_method());
    if (m_ctx == NULL)
    {
        ERR_print_errors_fp(stdout);
        return fail;
    }

    // 是否要求校验对方证书 此处不验证客户端身份所以为: SSL_VERIFY_NONE
    SSL_CTX_set_verify(m_ctx, SSL_VERIFY_NONE, NULL);

    // 加载CA的证书
    if(!SSL_CTX_load_verify_locations(m_ctx, CA_CERT_FILE, NULL))
    {
        printf("SSL_CTX_load_verify_locations error!\n");
        ERR_print_errors_fp(stderr);
        return fail;
    }

    // 加载自己的证书 此证书用来发送给客户端。 证书里包含有公钥
    if(SSL_CTX_use_certificate_file(m_ctx, SERVER_CERT_FILE, SSL_FILETYPE_PEM) <= 0)
    {
        printf("SSL_CTX_use_certificate_file error!\n");
        ERR_print_errors_fp(stderr);
        return fail;
    }

    // 加载自己的私钥  私钥的作用是,ssl握手过程中,对客户端发送过来的随机
    //消息进行加密,然后客户端再使用服务器的公钥进行解密,若解密后的原始消息跟
    //客户端发送的消息一致,则认为此服务器是客户端想要链接的服务器
    if(SSL_CTX_use_PrivateKey_file(m_ctx, SERVER_KEY_FILE, SSL_FILETYPE_PEM) <= 0)
    {
        printf("SSL_CTX_use_PrivateKey_file error!\n");
        ERR_print_errors_fp(stderr);
        return fail;
    }

    // 判定私钥是否正确
    if(!SSL_CTX_check_private_key(m_ctx))
    {
        printf("SSL_CTX_check_private_key error!\n");
        ERR_print_errors_fp(stderr);
        return fail;
    }

    return success;
}

int32_t openSSL::creat_ssl(int32_t fd)
{
    /* 基于 ctx 产生一个新的 SSL */
    m_ssl = SSL_new(m_ctx);
    if (m_ssl == NULL)
    {
        ERR_print_errors_fp(stderr);
        return fail;
    }
    m_fd = fd;

    /* 将连接用户的 socket 加入到 SSL */
    SSL_set_fd(m_ssl, m_fd);

    /* 设置成服务器模式 */
    SSL_set_accept_state(m_ssl);
    return success;
}

int32_t openSSL::accept_ssl_fd(int32_t fd)
{
    /* 建立 SSL 连接. 握手在此完成 */
    // 或者 SSL_do_handshake(ssl);
    if (SSL_accept(m_ssl) == -1)
    {
        int icode = -1;
        ERR_print_errors_fp(stderr);
        int iret = SSL_get_error(m_ssl, icode);
        printf("SSL_accept error! code = %d, iret = %d\n", icode, iret);
        return  fail;
    }
    return success;
}

int32_t openSSL::ssl_recv(char *data, int32_t size)
{
    if (NULL == data || size <= 0)
    {
        return -1;
    }

    int32_t recved = 0;
    while(true)
    {
//        int x = recv(m_fd,data,size, 0);
        recved = SSL_read(m_ssl, data, size);
        int left = SSL_pending(m_ssl);
        if (recved > 0)
        {
            int err = SSL_get_error(m_ssl,recved);
            return recved;
        }
        else
        {
            // TODO 小心陷入死循环
            if (recved < 0 &&  SSL_get_error(m_ssl,recved)  == SSL_ERROR_WANT_READ)
            {
                printf("got SSL_ERROR_WANT_READ\n");
                continue;
            }
            // recved=0 || no want read
            return recved;
        }
    }
}

int32_t openSSL::ssl_send(char *data, int32_t size)
{
    if (NULL == data || size <= 0)
    {
        return -1;
    }

    int32_t remainded = size;
    int32_t sended = 0;

    char* pszTmp = data;

    while(remainded > 0)
    {
        //TODO:检查此处的处理逻辑,是否会造成由于单个连接而拖累整个服务
        sended = SSL_write(m_ssl, data, (size_t)remainded);
        if (sended > 0)
        {
            pszTmp += sended;
            remainded -= sended;
        }
        else // sended <= 0
        {
            //TODO
            if (errno != EINTR  || errno != EAGAIN)
            {
                printf(" send data(size:%d) error, msg = %s\n", remainded, strerror(errno));
                break;
            }
        }
    }
    return (size - remainded);
}

void openSSL::ShowCerts()
{
    X509 *cert;
    char *line;

    cert = SSL_get_peer_certificate(m_ssl);
    if (cert != NULL)
    {
        printf("数字证书信息:\n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("证书: %s\n", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("颁发者: %s\n", line);
        free(line);
        X509_free(cert);
    }
    else
        printf("无证书信息!\n");
}


// ---- rsa非对称加解密 ---- //
#define KEY_LENGTH  2048             // 密钥长度
#define PUB_KEY_FILE "pubkey.pem"    // 公钥路径

// 函数方法生成密钥对
int32_t generateRSAKey(std::string strKey[2])
{
    // 公私密钥对
    size_t pri_len;
    size_t pub_len;
    char *pri_key = NULL;
    char *pub_key = NULL;

    // 生成密钥对
    RSA *keypair = RSA_generate_key(KEY_LENGTH, RSA_3, NULL, NULL);

    BIO *pri = BIO_new(BIO_s_mem());
    BIO *pub = BIO_new(BIO_s_mem());

    PEM_write_bio_RSAPrivateKey(pri, keypair, NULL, NULL, 0, NULL, NULL);
    PEM_write_bio_RSAPublicKey(pub, keypair);

    // 获取长度
    pri_len = BIO_pending(pri);
    pub_len = BIO_pending(pub);

    // 密钥对读取到字符串
    pri_key = (char *)malloc(pri_len + 1);
    pub_key = (char *)malloc(pub_len + 1);

    BIO_read(pri, pri_key, pri_len);
    BIO_read(pub, pub_key, pub_len);

    pri_key[pri_len] = '\0';
    pub_key[pub_len] = '\0';

    // 存储密钥对
    strKey[0] = pub_key;
    strKey[1] = pri_key;

    // 存储到磁盘(这种方式存储的是begin rsa public key/ begin rsa private key开头的)
    FILE *pubFile = fopen(PUB_KEY_FILE, "w");
    if (pubFile == NULL)
    {
        return fail;
    }
    fputs(pub_key, pubFile);
    fclose(pubFile);

    FILE *priFile = fopen(SERVER_KEY_FILE, "w");
    if (priFile == NULL)
    {
        return fail;
    }
    fputs(pri_key, priFile);
    fclose(priFile);

    // 内存释放
    RSA_free(keypair);
    BIO_free_all(pub);
    BIO_free_all(pri);

    free(pri_key);
    free(pub_key);
}

void openSSL::shutdown_ssl()
{
    if (m_ctx)
        SSL_CTX_free(m_ctx);
}

我们服务器用的是LT模式,用ET模式的ssl_recv函数和ssl_send可能会有bug。
SSLMgr类:

//
// Created by jamesjiang on 2018/5/16.
//

#ifndef GAME_CONNECTOR_SSLMGR_H
#define GAME_CONNECTOR_SSLMGR_H

#include <stdlib.h>
#include <stdint.h>
#include <map>
#include "Common/openSSL.h"

namespace GameJayo {  namespace Server {

    class SSLMgr
    {
    public:
        SSLMgr()
        {

        }
        ~SSLMgr()
        {
            clear();
        }
        openSSL* get_ssl(int32_t fd)
        {
            if (m_clientSSL.count(fd))
            {
                return m_clientSSL.at(fd);
            }
            return NULL;
        }
        void del_ssl(int32_t fd)
        {
            if (m_clientSSL.count(fd))
            {
                delete m_clientSSL.at(fd);
                m_clientSSL.erase(fd);
            }
        }
        int32_t add_ssl(int32_t fd)
        {
            del_ssl(fd);
            openSSL *ssl = new openSSL();
            if (fail == ssl->creat_ssl(fd))
            {
                return fail;
            }
            m_clientSSL[fd] = ssl;
            return success;
        }
        int32_t accept_fd(int32_t fd)
        {
            openSSL *ssl = get_ssl(fd);
            if (ssl == NULL)
            {
                return fail;
            }
            if (fail == ssl->accept_ssl_fd(fd))
            {
                return fail;
            }
            return success;
        }
        void clear()
        {
            for(auto &it : m_clientSSL)
            {
                delete it.second;
            }
            m_clientSSL.clear();
        }
    private:
        std::map<int32_t, openSSL*> m_clientSSL;
    };
    }
}

#endif //GAME_CONNECTOR_SSLMGR_H


  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054是一个与网络连接相关的错误。这个错误通常出现在使用Git进行提交或克隆操作时。可以根据以下几种方法来解决这个问题: 1. 检查网络连接:首先,确保你的网络连接正常并且稳定。这个错误通常是由于网络过慢或不稳定导致的。可以尝试使用不同的网络连接,者连接到更稳定的网络上再次尝试操作。 2. 更换网络环境:如果你使用的是公共Wi-Fi或者公司的网络,有时候这些网络对Git操作进行了限制。尝试切换到一个不同的网络环境,例如使用个人热点或者使用家庭网络来进行操作。 3. 使用代理:如果你在使用Git时遇到了网络问题,可以尝试配置代理。可以使用`git config`命令来设置代理,具体的设置方法可以参考Git的官方文档。 4. 手动下载:如果以上方法都没有解决问题,你可以尝试手动下载所需的文件。在Git操作中遇到网络问题时,你可以手动从源码管理系统(例如GitHub)下载所需的文件,然后将其放置在正确的位置,再进行后续的操作。 总结起来,OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054错误通常是由于网络连接问题引起的。通过检查网络连接、更换网络环境、使用代理或者手动下载文件等方法,可以解决这个问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [提交Git时报错:OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054](https://blog.csdn.net/qq_42203909/article/details/123882309)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [安装vagrant报错OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 54](https://download.csdn.net/download/weixin_38627213/14043306)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [ERROR: RPC failed; curl 56 OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054解决方法](https://blog.csdn.net/JISOOLUO/article/details/103625488)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值