【问题标题】:SSL_write reports success but fails in SafariSSL_write 报告成功但在 Safari 中失败
【发布时间】:2016-07-12 11:13:56
【问题描述】:

我有自己的使用 openssl 的网络服务器实现。

我正在使用 SSL_write 的阻塞版本来传输数据和 OSX 上最新的 Safari。

我的逻辑如下:

num_bytes = 0;
while( num_bytes < bytes_to_send) {
  int n = SSL_write(...);
  if (n <= 0) {
     ... // Handle error, break loop
  }
  num_bytes += n;
}
// Close the socket

我遇到的一个不常见的问题是 Safari 报告 Failed to load resource: The network connection was lost.

当我在 Safari 下的开发者面板中检查资源时,它显示接收到的字节数少于预期。

我已经调试了我的代码并确定SSL_write 报告在我关闭连接之前所有字节都已传输。

当我不关闭连接时,此错误不再发生。

这使我得出以下结论:

SSL_write 错误地报告了在实际发生之前已成功传输的字节数,并且在传输后立即关闭连接会缩短传输时间。

我是正确的,还是我缺少关于 SSL_write 的详细信息?

更新:

在检查了我处理 SSL_shutdown 的方式后,我看到报告了几个 SSL_ERROR_SYSCALL 错误。这可能与我的问题有关,也可能无关,因为这些错误不会在受影响的 http 传输后直接返回。奇怪的是,在受影响的 http 传输期间,除了阻塞调用的重试错误之外,SSL_shutdown 没有报告任何错误。

关机代码

// First shutdown SSL
SSL_CTX_free(ctx);
if (SSL_shutdown(ssl) == 0) {
  // Call SSL Shutdown again
  SSL_shutdown(ssl);
}
SSL_free(ssl);

// Now shutdown port
struct linger linger;
linger.l_onoff = 1;
linger.l_linger = 10;
setsockopt(socket, SOL_SOCKET, SO_LINGER, (char *)&linger,
           sizeof(linger));

shutdown(socket, SHUT_WR);
while(recv(...) > 0) { }
closesocket(socket);

更新 #2

我设置了带有 ssl 解密的wireshark 来捕获错误。这是它的样子:

注意第一条绿线,它显示了来自 Safari 对 PatientSearch.js 的 HTTP 请求。现在第二条绿线显示了来自我的网络服务器的 HTTP 响应。此响应包含整个文件,然后是断开协议。

更新 #3

这件事变得越来越神秘。在检查了成功传输的 Wireshark 跟踪之后,成功传输与失败传输的协议模式没有区别。现在完全糊涂了。

【问题讨论】:

  • 鉴于它在您保持连接打开时有效,我的猜测是该错误实际上在其他地方,即您在关闭套接字之前没有正确读取来自客户端的请求。见stackoverflow.com/a/33947985/3081018
  • @SteffenUllrich 在阅读了您的评论后,我回去仔细检查了我的标头处理代码,并确认整个请求实际上是从套接字中检索的。我读出/打印了整个 HTTP 标头,使用 SSL_write 将数据返回给客户端,然后关闭了套接字。这样做之后,Safari 仍然会时断时续。
  • 你能发布你的关机代码吗?对于 SSL 和底层套接字?
  • @hofan41:我知道移动设备上的 Safari 默认使用 HTTP pipelining,这导致我曾经实现的 HTTP 服务器出现问题。在发送响应后关闭套接字会导致仍在管道中的后续请求失败,Safari 对此并不满意。也许 Safari for OSX 也有类似的问题?
  • @hofan41:您的服务器使用的是 HTTP 1.0 还是 1.1?您的服务器是否在处理 HTTP keepalive 请求?在关闭套接字之前,您是否在响应中发送了 Connection: close 标头?

标签: c++ sockets safari openssl


【解决方案1】:

您的发送循环没有正确跟踪发送的字节数。它应该看起来更像这样:

unsigned char *data = ...;
while (bytes_to_send > 0) {
  int num_sent = SSL_write(ssl, data, bytes_to_send);
  if (num_sent <= 0) {
    // handle error as needed...
    break;
  }
  data += num_sent;
  bytes_to_send -= num_sent;
}
// Close the socket

或者这个:

unsigned char *data = ...;
int num_total_sent = 0;
while (num_total_sent < bytes_to_send) {
  int num_sent = SSL_write(ssl, &data[num_total_sent], bytes_to_send - num_total_sent);
  if (num_sent <= 0) {
    // handle error as needed...
    break;
  }
  num_total_sent += num_sent;
}
// Close the socket

更新:

SSL_write 错误地报告了在实际发生之前已成功传输的字节数

返回值报告它从您那里接受的字节数。这并不能保证这些字节实际上已经被传输了。它们被加密并用于创建传出 SSL 帧,该帧位于底层套接字的出站缓冲区中,等待内核在后台实际传输它。该传输可能需要一些时间,尤其是在套接字上启用了 Nagle 算法的情况下(通常默认情况下)。

在切断传输后立即关闭连接。

这是可能的,具体取决于您如何关闭连接、如何配置套接字的 linger 选项等。默认情况下,优雅地关闭套接字应该允许内核在后台花费一些额外的时间来写出任何在它实际关闭连接之前挂起的数据。您通常需要采取额外措施来改变这种行为。

【讨论】:

  • 抱歉没有正确发布我的伪代码。事实上,我使用的循环与您发布的循环非常相似。我更新了我的问题以更准确地反映我的循环。
猜你喜欢
  • 2016-08-24
  • 2020-02-13
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
相关资源
最近更新 更多