自定义Web服务

时间:2016-08-17 16:08:25

标签: c++ http boost-asio

我喜欢看到有一个针对C ++的TCP / IP套接字的跨平台标准。到目前为止,我已经能够找到我遇到的所有主题的帮助。但是现在我陷入了一种奇怪的行为。我正在使用Xcode 7.3.1开发2013年末的iMac。

我正在为特殊目的开发一个简单的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 OK\r\n\r\nYup!"));

        socket->shutdown(asio::ip::tcp::socket::shutdown_both);
        socket->close();
        delete socket;

    }

    return 0;
}

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

让我澄清一下,因为在我的机器上重现这一点我需要做的很奇怪。启动程序后(用于调试),我从Chrome打开“页面”(作为http://localhost:2080/)。我可以多次点击刷新,效果很好。但后来我使用Firefox(或Safari),它可能会持续20秒,因此页面会按预期显示。现在得到这个。如果在Firefox的延迟期间,我点击Chrome中的“刷新”,Firefox页面也会立即出现。在另一个实验中,我在Chrome中点击了Refresh(工作正常),然后在Firefox和Safari中点击了Refresh。他们俩都挂了。我在Chrome中点击了Refresh,所有3个都立即出现。

在对此实验的更改中,一旦我启动此程序,我就会在Firefox或Safari中点击“刷新”,它们的工作正常。无论我刷新多少次。并在他们之间来回走动。我实际上是按住CMD-R来快速刷新这些浏览器。但是,只要我在同一页面上刷新Chrome,然后尝试刷新其他两个浏览器,它们就会再次挂起。

自1993年左右完成网络编程以来,我非常了解HTTP标准。最基本的工作流程是浏览器启动TCP连接。一旦Web服务器接受连接,客户端就会发送HTTP标头。对于根页(“/”),例如“GET / \ r \ n \ r \ n”。服务器通常读取所有标题行并停止,直到它到达第一个空白行,这表示标题的结束和上载内容的开始(例如,POST表单内容),Web应用程序可以自由使用或忽略。服务器在准备好自己的HTTP标头时会响应,通常以“HTTP / 1.1 200 OK \ r \ n”开头,然后是实际页面内容(或二进制文件内容等)。

在我的应用程序中,我实际上使用asio :: read_until(* socket,inBuffer,“\ r \ n \ r \ n”)来读取整个HTTP标头。由于这是悬挂,我想也许那些其他浏览器发送损坏的标题或东西。因此,我将样本调整为仅读取单个字符(应该是“GET /”中的“G”)。一个字符。不。

作为旁注,我知道我正在同步这样做,但我真的想要一个简单的线性演示来展示这种不良行为。我假设这不是造成这个问题的原因,但我知道这是可能的。

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

1 个答案:

答案 0 :(得分:0)

这似乎源于Chrome中的设计怪癖。看这篇文章:

server socket receives 2 http requests when I send from chrome and receives one when I send from firefox

我看到现在发生了什么。 Chrome会发出2个连接请求。第一个是针对所需页面并包含正确的请求HTTP标头。第二个连接一旦被接受,甚至不包含单个字节的输入数据。所以我尝试读取第一个字节没有得到回报。幸运的是,读取尝试超时。这很容易通过try / catch恢复。

这似乎是一种贪婪的优化,可以加快Chrome的性能。也就是说,它保持下一个连接打开,直到浏览器需要来自站点的内容,从而在该开放套接字上发送HTTP请求。然后,它会立即打开一个新连接,再次预期未来的请求。虽然我知道这会如何加速Chrome的体验,但这似乎是一个可疑的设计,因为它给服务器带来了额外的负担。

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