自定义web服务中的Boost::asio::read()阻塞



我很高兴看到c++在boost中出现了TCP/IP套接字的跨平台标准。到目前为止,我已经能够找到我遇到的所有主题的帮助。但现在我被一种奇怪的行为困住了。我在2013年底的iMac上使用Xcode 7.3.1进行开发。

我正在开发一个简单的web服务器用于特殊目的。下面的代码是演示不良行为的简化版本:

#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace std;
using namespace boost;
using namespace boost::asio;
using namespace boost::asio::ip;
int main(int argc, const char * argv[]) {
    static asio::io_service ioService;
    static tcp::acceptor tcpAcceptor(ioService, tcp::endpoint(tcp::v4(), 2080));
    while (true) {
        // creates a socket
        tcp::socket* socket = new tcp::socket(ioService);
        // wait and listen
        tcpAcceptor.accept(*socket);
        asio::streambuf inBuffer;
        istream headerLineStream(&inBuffer);
        char buffer[1];
        asio::read(*socket, asio::buffer(buffer, 1));  // <--- Yuck!
        asio::write(*socket, asio::buffer((string) "HTTP/1.1 200 OKrnrnYup!"));
        socket->shutdown(asio::ip::tcp::socket::shutdown_both);
        socket->close();
        delete socket;
    }
    return 0;
}

当我访问此服务时,在某些条件下,浏览器将阻塞20秒以上。如果我暂停在调试模式下运行的程序,我可以看到asio::read()调用阻塞了。它实际上是在等待一个字符从浏览器中出现。为什么会这样?

让我澄清一下,因为我要在我的机器上复制这个是很奇怪的。一旦我启动程序(调试),我打开"页面"从Chrome(如http://localhost:2080/)。我可以点击刷新很多次,它工作得很好。但当我使用Firefox(或Safari)时,它可能会挂起20秒,然后页面就会像预期的那样显示出来。现在听听这个。如果在这段时间内,我在Chrome中点击刷新,Firefox页面也会立即显示出来。在另一个实验中,我在Chrome上点击刷新(效果不错),然后在Firefox和Safari上都点击刷新。他们俩都被绞死了。我在Chrome中点击刷新,所有3个都立即显示出来。

在这个实验中,我一启动这个程序,我就在Firefox或Safari中点击刷新,它们工作得很好。不管我刷新多少次。在它们之间来回切换。我实际上是按住CMD-R键来快速刷新这些浏览器。但当我在同一页面上刷新Chrome浏览器,然后尝试刷新其他两个浏览器时,它们又挂起了。

从1993年开始做web编程,我很了解HTTP标准。最基本的工作流程是浏览器发起一个TCP连接。一旦web服务器接受连接,客户端就发送一个HTTP报头。类似于"GET/rnrn"的根页面("/")。服务器通常读取所有的标题行并停止,直到它到达第一个空白行,这标志着标题的结束和上传内容的开始(例如,post表单内容),web应用程序可以自由地消费或忽略。当服务器准备好使用自己的HTTP标头时,服务器会响应,通常以"HTTP/1.1 200 OKrn"开头,然后是实际的页面内容(或二进制文件内容等)。

在我的应用程序中,我实际上使用asio::read_until(*socket, inBuffer, "rnrn")来读取整个HTTP标头。由于挂起,我想可能是其他浏览器发送了损坏的头或其他东西。因此,我对示例进行了精简,只读取一个字符(应该是"GET/"中的"G")。一个字符。不。

作为旁注,我知道我正在同步执行此操作,但我真的想要一个简单的线性演示来显示此不良行为。我认为这不是导致这个问题的原因,但我知道这是可能的。

有什么想法吗?在我的用例中,这是可以忍受的,因为服务器最终会响应,但我真的更愿意理解消除这种不良行为。

这似乎是Chrome设计怪癖的结果。请看这篇文章:

当我从chrome发送时,服务器套接字接收2个http请求,当我从firefox发送时接收1个

我知道发生了什么。Chrome使2连接请求。第一个用于所需页面,并包含适当的请求HTTP标头。第二个连接一旦被接受,甚至不包含单个字节的输入数据。所以我试图读取第一个字节没有得到回报。幸运的是,读取尝试超时。这很容易通过try/catch来恢复。

这似乎是一个贪婪的优化,以加快Chrome的性能。也就是说,它保持下一个连接打开,直到浏览器需要从站点获取某些东西,然后在打开的套接字上发送HTTP请求。然后,它立即打开一个新的连接,再次为将来的请求做准备。虽然我知道这是如何加快Chrome的体验,但这似乎是一个可疑的设计,因为它给服务器带来了额外的负担。

对于打开一个单独的线程来处理每个接受的套接字,这是一个很好的参数。当其他线程处理其他请求时,一个线程可以耐心地挂起等待永远不会到来的请求。为此,我包装了tcpAcceptor.accept(*socket)之后的所有内容;在一个新的线程中,这样循环可以继续等待下一个请求。

最新更新