如何正确使用 SSL_read() 和 select()?

how to work CORRECTLY with SSL_read() and select()?

我尝试使用 OpenSSL 创建一个 C++ TLS 客户端,它在 Windows 上使用非阻塞套接字。

我想使用 SSL_read()/SSL_write() 和 select() 函数,但我没有找到运行良好的算法并且网络不提供好的和简单的例子。在接收到最后一个数据块后,select() 已经超时 return。

我不明白 OpenSSL api,SSL_pending() return 已经 0 并且 select 超时了??

Select 导致最后一个数据块出现临界延迟。

我的 recv_buffer() 算法是这样的:

我有检查套接字是否可读或可写的功能(运行良好):

int CSocket::socket_RWable(int rw_flag, const int time_out)
{
    fd_set rwfs;
    int error = 0;
    struct timeval timeout;

    try
    {
        memset(&timeout, 0, sizeof(struct timeval));
        timeout.tv_sec = time_out;

        while( 1 ) // boucle de surveillance
        {
            FD_ZERO(&rwfs);
            FD_SET(m_socket, &rwfs);

            // surveiller la socket en lecture ou ecriture
            if(rw_flag == R_MODE)   
                error = select(m_socket+1, &rwfs, NULL, NULL, &timeout);
            else if(rw_flag == W_MODE) 
                error = select(m_socket+1, NULL, &rwfs, NULL, &timeout);

            if(error < 0) // echec de select
                throw 1;
            else if(error == 0) // fin du time out
                throw 2;

            // Une opération d' entree/sortie sur la socket est disponible
            if(FD_ISSET(m_socket, &rwfs) != 0)  
            {
                FD_CLR(m_socket, &rwfs );
                return 0;
            }
        }
    }
    catch(int ret)
    {
        FD_CLR(m_socket, &rwfs );
        if(ret == 1) throw CErreur("[-] CSocket : select : ", CWinUtil::Win_sys_error(NET_ERROR));
        else if(ret == 2)   return -1;
    }

    return -1;
}

更新:

并且此函数将数据接收到缓冲区并在 las 数据块之后导致超时:

int CTLSClient::recv_buffer(char *buffer, const int buffer_size, const int  time_out)
{
    int selectErr = 0;
    int sslErr = 0;
    int retRead = 0;    
    int recvData = 0;   

    selectErr = m_socket->socket_RWable(R_MODE, time_out);

    while(selectErr == 0)
    {
        retRead = SSL_read(m_ssl, buffer+recvData, buffer_size-recvData);

        sslErr = SSL_get_error(m_ssl, retRead);

        if(sslErr == SSL_ERROR_NONE)
        {
            cout<<"DEBUG 2  SSL_ERROR_NONE recv data="<<retRead<<endl;
            recvData += retRead;
        }
        else if(sslErr == SSL_ERROR_WANT_READ)
        {
            cout<<"DEBUG 3  SSL_ERROR_WANT_READ select()"<<endl;
            selectErr = m_socket->socket_RWable(R_MODE, time_out);
        }
        else if(sslErr == SSL_ERROR_WANT_WRITE)
        {
            cout<<"DEBUG 4  SSL_ERROR_WANT_WRITE select()"<<endl;
            selectErr = m_socket->socket_RWable(W_MODE, time_out);
        }
        else if(sslErr == SSL_ERROR_ZERO_RETURN)
        {
            return -1;
        }
        else
            return -1;
    }

    return recvData;
}

这是连接到 POP3 服务器的输出:

DEBUG 2  SSL_ERROR_NONE recv data=35
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]+OK BLU0-POP617 POP3 server ready
total data -> 35
DEBUG 2  SSL_ERROR_NONE recv data=23
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]+OK password required
total data -> 23
DEBUG 2  SSL_ERROR_NONE recv data=30
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]+OK mailbox has 180 messages
total data -> 30
DEBUG 2  SSL_ERROR_NONE recv data=18
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]+OK 180 12374432
total data -> 18
DEBUG 2  SSL_ERROR_NONE recv data=13
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]+OK 1 23899
total data -> 13
DEBUG 2  SSL_ERROR_NONE recv data=5
DEBUG 3  SSL_ERROR_WANT_READ select()
DEBUG 2  SSL_ERROR_NONE recv data=8192
DEBUG 2  SSL_ERROR_NONE recv data=8192
DEBUG 3  SSL_ERROR_WANT_READ select()
DEBUG 3  SSL_ERROR_WANT_READ select()
DEBUG 2  SSL_ERROR_NONE recv data=7521
DEBUG 3  SSL_ERROR_WANT_READ select()
[S]total data -> 23910

假设您已经阅读了 headers,由于某种原因 SSL_read() 在阅读电子邮件后挂起并且 returns SSL_WANT_READ。我通过一次一行循环消息 body 直到找到结束句点来解决这个问题。当我到达这一行时,我调用 SSL_pending()。虽然没有待处理的数据,但它防止了 SSL_read() returns SSL_WANT_READ 的无限循环。但是,我正在寻找更好的解决方案。

for(;;)
{
    char *line = ReadLine(ssl, buf, sizeof(buf));
    if(line != NULL)
    {
        if(*line == '.')
        {
            int pending = SSL_pending(ssl);
            if(pending > 0)
            {
                int read = SSL_read(ssl,buf,pending);
            }
        }
    }
}

此函数一次读取一个字符,直到到达行尾字符和 returns 行。

char *ReadLine(SSL *ssl, char *buf, int size)
{
    int i = 0;
    char *ptr = NULL;
    for (ptr = str; size > 1; size--, ptr++)
    {
        i = SSL_read(out, ptr, 1);
        switch (SSL_get_error(out, i)){
        case SSL_ERROR_NONE:
            break;
        case SSL_ERROR_ZERO_RETURN:
            break;
        case SSL_ERROR_WANT_READ:
            break;
        case SSL_ERROR_WANT_WRITE:
            break;
        default:
            TRACE("SSL problem\r\n");
        }

        if (*ptr == '\n')
            break;   
        if (*ptr == '\r'){
            ptr--;
        }
    }


    *ptr = '[=11=]';

    return(str);
}