2016-08-17 31 views
1

我很高兴看到有一个针对C++增强的TCP/IP套接字的跨平台标准。到目前为止,我已经能够为我遇到的所有主题找到帮助。但现在我陷入了一个奇怪的行为。我正在开发在2013年末的iMac上使用Xcode 7.3.1。boost :: asio :: read()在自定义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中都点击了刷新。他们两个都挂了。我在Chrome中点击刷新,所有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”)。一个字符。不。作为一个便笺,我知道我正在做同步,但我真的想要一个简单的线性演示来展示这种不良行为。我假设这不是什么导致这个问题,但我知道这是可能的。

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

+0

无法在我的mac上重现它 – Arunmu

+0

学习更多,我发现Chrome正在发出两个连接请求,其他浏览器只有一个。第一个带有一个HTTP头,但第二个没有。看起来这是Chrome中的一个错误。以下帖子提到: http://stackoverflow.com/questions/4761913/server-socket-receives-2-http-requests-when-i-send-from-chrome-and-receives-one –

+0

我注意到了来自Chrome的两个请求,一个用于'/',另一个用于'favicon.ico',这两个请求看起来都很好。也许你正在使用旧版本的Chrome? – Arunmu

回答

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)之后封装了所有的东西。在新的线程中,循环可以继续等待下一个请求。

+1

ASIO的整个想法是避免创建新线程的必要性。你不应该无条件地阅读。你也不应该每次都创建一个套接字。不管怎么说,它都只是一个局部变量。 –

+1

静态在这里也不需要。 –

+0

“静态”在这里是一个oopsie。谢谢。 –