7
votes

I'm writing proxy server based on boost asio. In the part of my code responsible for accepting incoming connections from browser to proxy server, I'm facing the behaviour i'm not fully understand.

So - I'm creating acceptor object with next constructor:

_acceptor(_io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port ), true)

start listening here (start_accept):

_new_connection.reset(new connection(*_io_services.front(), _connection_id));
_acceptor.async_accept(_new_connection->get_socket(),
                            boost::bind(&server::handle_accept, this,
                                        boost::asio::placeholders::error));

and handle_accept

if (!error) {
    _new_connection->start();
}

// continue waiting for incoming connections
start_accept();

In general my code for accepting incoming connections is the same as in the HTTP Server 2 example

The problem appears only when first incoming connection was not closed, then second incoming will be queued and pending, till first one will be closed.

According to this two answers: boost::asio acceptor reopen and async read after EOF

How to launch an "event" when my Boost::asio tcp server just start running ( AKA io_service.run() )?

The acceptor object will add all incoming connections into the queue and will not accept them till pending connection will not be closed.

I want to achieve immediate processing for all incoming connections - so they are not pending in the acceptor's queue, and I did not find any solution so far.

Could you please help me, what is right way to implement this?

connection->start() function

void
connection::start() {
    _bsocket.async_read_some(boost::asio::buffer(_bbuffer),
            boost::bind(&connection::handle_browser_read_headers,
                        shared_from_this(),
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred
        ));
}

Graphical representation UPDATE: boost asio logs

@asio|1368460995.389629|0*1|[email protected]_accept
@asio|1368461003.855113|>1|ec=system:0
@asio|1368461003.855113|1*2|[email protected]_receive
@asio|1368461003.855113|>2|ec=system:0,bytes_transferred=318
@asio|1368461003.856113|1*3|[email protected]_accept
@asio|1368461003.856113|<1|
@asio|1368461003.856113|2*4|[email protected]_resolve
@asio|1368461003.856113|<2|
@asio|1368461003.866114|>4|ec=system:0,...
@asio|1368461003.866114|4*5|[email protected]_connect
@asio|1368461003.868114|<4|
@asio|1368461004.204133|>5|ec=system:0
@asio|1368461004.204133|5*6|[email protected]_send
@asio|1368461004.204133|<5|
@asio|1368461004.204133|>6|ec=system:0,bytes_transferred=302
@asio|1368461004.204133|6*7|[email protected]_receive
@asio|1368461004.204133|<6|
@asio|1368461004.613156|>7|ec=system:0,bytes_transferred=16384
@asio|1368461004.613156|7*8|[email protected]_send
@asio|1368461004.614157|<7|
@asio|1368461004.614157|>8|ec=system:0,bytes_transferred=16384
@asio|1368461004.614157|8*9|[email protected]_receive
@asio|1368461004.614157|<8|
@asio|1368461004.614157|>9|ec=system:0,bytes_transferred=1946
@asio|1368461004.614157|9*10|[email protected]_send
@asio|1368461004.614157|<9|
@asio|1368461004.614157|>10|ec=system:0,bytes_transferred=1946
@asio|1368461004.614157|10*11|[email protected]_receive
@asio|1368461004.614157|<10|
@asio|1368461004.618157|>11|ec=system:0,bytes_transferred=14080
@asio|1368461004.618157|11*12|[email protected]_send
@asio|1368461004.619157|<11|
@asio|1368461004.619157|>12|ec=system:0,bytes_transferred=14080
@asio|1368461004.619157|12*13|[email protected]_receive
@asio|1368461004.619157|<12|
@asio|1368461019.248994|>13|ec=asio.misc:2,bytes_transferred=0
@asio|1368461019.248994|13|[email protected]
@asio|1368461019.248994|13|[email protected]
@asio|1368461019.248994|<13|
@asio|1368461019.253994|0|[email protected]
@asio|1368461019.253994|>3|ec=system:0
@asio|1368461019.253994|3*14|[email protected]_receive
@asio|1368461019.254994|3*15|[email protected]_accept
@asio|1368461019.254994|<3|
@asio|1368461019.254994|>14|ec=system:0,bytes_transferred=489
@asio|1368461019.254994|14*16|[email protected]_resolve
@asio|1368461019.254994|<14|
@asio|1368461019.281995|>16|ec=system:0,...
@asio|1368461019.281995|16*17|[email protected]_connect
@asio|1368461019.282996|<16|
@asio|1368461019.293996|>17|ec=system:0
@asio|1368461019.293996|17*18|[email protected]_send
@asio|1368461019.293996|<17|
@asio|1368461019.293996|>18|ec=system:0,bytes_transferred=470
@asio|1368461019.293996|18*19|[email protected]_receive
@asio|1368461019.293996|<18|
@asio|1368461019.315997|>19|ec=system:0,bytes_transferred=11001
@asio|1368461019.315997|19*20|[email protected]_send
@asio|1368461019.349999|<19|
@asio|1368461019.349999|>20|ec=system:0,bytes_transferred=11001
@asio|1368461019.349999|20|[email protected]
@asio|1368461019.349999|20|[email protected]
@asio|1368461019.349999|<20|
@asio|1368461019.349999|0|[email protected]

I found that acceptor's behaviour depends on functions I'm using for read data from server socket. connection class reads data from browser, modifies request url, connects to host and sends request, then reading response from server and writing it back to browser. So at the moment when I need to read server body - I use this function

    _ssocket.async_read_some(boost::asio::buffer(_sbuffer),
            boost::bind(&connection::handle_server_read_body,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
        ));

If content-length was not specified in service response headers, I'm reading till EOF. If async_read_some function was called and there is no more data to read on socket it's waiting ~15 sec before EOF will be raised. All new incoming connections during this 15 sec will not be accepted by acceptor.

But if I'm using another variant of async_read -

        boost::asio::async_read(_ssocket, boost::asio::buffer(_sbuffer),
            boost::bind(&connection::handle_server_read_body,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
        ));

Incoming connections are accepted just fine. But it boost::asio::async_read works a bit slow, it is waiting for bunch of data to be read from socket and does not call handler till that data will be read, so - I thought I will specify transfer_at_least

        boost::asio::async_read(_ssocket, boost::asio::buffer(_sbuffer), boost::asio::transfer_at_least(1),
            boost::bind(&connection::handle_server_read_body,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
        ));

Yep, it became better - but problem with accepting new connections returns :/

What is real differences between - async_read_some and boost::asio::async_read - it feels like something is blocked.

1
Can you show code inside your start() function? Maybe it is blocking your code, so it does not keep accept()ing. - Bogolt
I updated question. In general I checked this, I don't use any blocking operations in the connection class. And from the debug logs I see that server::start_accept function was called immediately after handle_accept. But new connection somehow is not accepted by acceptor. It will be accepted only when first one get closed. - miks131
A brief look at the code looks fine. As long as a thread running the io_service is available, then a connection should be accepted if there is an outstanding async_accept operation; otherwise the connection is queued within the acceptor until an accept operation is initiated. If you are using Boost.Asio 1.47+, then enabling handler tracking may provide much better insight into what is occurring. - Tanner Sansbury
The asynchronous chains look correct. With the code also looking fine, I am inclined to think the problem resides outside of the server code. Consider using lower-level tools, such as netstat to get connection information, and netcat to make the connections. I would start with using netcat on the same host as the server code, then using it from the machines on which the browser(s) are running. - Tanner Sansbury
Interesting. boost::async_read is implemented in terms of stream.async_read_some, and the CompletionCondition defaults to boost::asio::transfer_all(). - Tanner Sansbury

1 Answers

0
votes

I do not know if this will help, but in my server, I'm using the following for my session's read request:

boost::asio::async_read( socket(), boost::asio::buffer( _incoming ),
    boost::asio::transfer_at_least( 1 ),
    boost::bind( &server_class::handle_read, shared_from_this(),
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred ) );

I then dump anything I receive into a parser to ensure that it's sane (handling state, etc).

Otherwise, I believe I am doing everything you're doing, based on what I see here.

If this works, then it would seem asio has behavior that isn't intuitive.