我正在编写一个应用程序,该应用程序创建与 https 服务器的多个非阻塞 SSL 连接,在每个连接中我发送一个请求并读取服务器的响应。我的问题是,无论我做什么,我都无法确定响应数据何时完成。这是我负责发送和接收数据的代码的一部分:
....
fd_set connectionfds;
struct timeval timeout2;
FD_ZERO(&connectionfds);
FD_SET(socket_server, &connectionfds);
timeout2.tv_usec = 0;
timeout2.tv_sec = 1;
while(1)
{
r=BIO_read(io,buf,BUFSIZZ-1);
if (r>0){
//gather data
continue;
}
else if (SSL_get_error(ssl, r)==SSL_ERROR_WANT_READ){
int ret = select(socket_server + 1, &connectionfds, NULL, NULL, &timeout2);
if (ret <= 0){
break;
}
continue;
}
else{
break;
}
}
// use whole gathered data
....
我上面代码的问题是,如果我将 select timeout 设置为较小的时间,我无法保证收到所有数据(因为有些服务器真的很慢),并且如果我将 timeout 设置为很长的时间(5-10秒),我的套接字很长一段时间陷入等待状态,并且在此之前我无法使用响应。 我试图通过使用“BIO_should_read()”或“BIO_pending()”函数来完成这项工作,但它们都没有给我我想要的东西。 那么,有没有办法确定 SSL 套接字何时没有其他内容可读取?
正如 Steffen 所说,SSL 和 TCP/IP 流中没有“数据结束”的概念。现代 http 服务器使用“保持活动”连接,并且在回答您的请求后不会断开连接。相反,他们使用额外的信息来推断答案的长度。它要么是“Content-lenght”标头,要么使用“Transfer-Encoding:chunked”后跟数据块。每个块前面都有大小(十六进制数)。大小为零的块意味着答案的结束。
对于您来说,获得可用内容的最简单方法可能是将请求作为 HTTP 1.0 请求(而不是 HTTP 1.1)发送。如果您发出这样的请求:
GET /myrequestdir/myrequestfile HTTP/1.0
服务器在回答您的请求后将立即关闭连接。
如果您需要 HTTP 1.1,这里有两个示例,说明服务器如何用“Hello”进行应答。留言:
HTTP/1.1 200 OK
Content-Length:6
Hello.
或使用分块连接:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
6
Hello.
0
为什么使用BIO_read,而不是SSL_read?在我看来,将 SSL 层与 SSL_get_error 一起使用,然后不将同一层与 SSL_read 一起使用,而是将底层 BIO 层与 BIO_read 一起使用,这是一个坏主意。您也不处理这种情况,其中 SSL_get_error 可能不返回任何错误或 SSL_ERROR_WANT_READ (在(重新)协商的情况下)。
至于你的问题,如何知道数据何时完成:SSL 就像底层 TCP 一样,是一种基于流的协议,例如与基于数据包的协议(如 UDP)相反,没有“数据包结束”。如果 SSL 连接完成,那么您的数据就完成了。如果您想要其他东西,您必须调整应用程序协议以在流层之上创建数据包层。
或者您的意思是,您想知道当前 SSL 帧何时完成?虽然 SSL 应该用作流协议,但它以可变大小的帧发送数据,最大大小为 16k。每个 SSL_write 将产生一个新帧(如果您一次发送更多发送 16k,可能会产生更多帧),但每个 SSL_read 只会读取一个帧,例如如果它没有获取整个帧,它将返回错误(SSL_WANT_*),否则返回帧的解密内容。这就是您知道框架已完全读取的方式。