我尝试使用Windows上的非阻塞套接字来制作OpenSSL的C ++ TLS客户端。
我想使用SSL_read()/ SSL_write()和select()函数,但是我没有find运行正常的algorithm,networking也没有提供好的和简单的例子。 在recv的最后一个数据块之后,select()会返回一个超时值。
我不明白OpenSSL api,SSL_pending()已经返回0,并select一个超时?
select导致在最后的数据块批评延迟。
我的recv_buffer()的algorithm是这样的:
我有检查一个套接字是否可读或可写的function(工作正常):
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
假设您已经阅读了标题,出于某种原因,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); }