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

时间:2015-01-30 15:32:31

标签: windows sockets openssl nonblocking

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

我想使用SSL_read()/ SSL_write()和select()函数但是我没有找到效果很好的算法,并且网络没有提供好的和简单的例子。在最后一个数据块返回后,select()已经完成了超时返回。

我不懂OpenSSL api,SSL_pending()返回0并选择超时??

选择导致最后一组数据的批评延迟。

我对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

1 个答案:

答案 0 :(得分:1)

假设您已经阅读了标题,由于某种原因,SSL_read()在阅读完电子邮件后会挂起并返回SSL_WANT_READ。我通过一次循环消息体一直到找到结束期间来解决这个问题。当我到达这一行时,我调用SSL_pending()。虽然没有挂起的数据,但它可以防止SSL_read()返回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);
            }
        }
    }
}

此函数一次读取一个字符,直到到达行尾字符并返回该行。

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 = '\0';

    return(str);
}