I've written a program which uses boost:asio library, which transfers data between 2 tcp servers.
One server uses the following code to send data:
std::shared_ptr<std::string> s =
std::make_shared<std::string>(message);
boost::asio::async_write(socket_, boost::asio::buffer(*s),
std::bind(&TcpServer::HandleWrite, shared_from_this(), s, _1, _2));
In another TcpServer, when I use async_read_until to get data, everything works fine, but if I replace async_read_until to async_read, it gives an End Of File error:
boost::asio::streambuf input_buffer;
boost::asio::async_read_until(socket_, input_buffer, match_condition(),
std::bind(&TcpServer::HandleRead, shared_from_this(), _1));
//boost::asio::async_read(socket_, input_buffer, boost::asio::transfer_all(),
// std::bind(&TcpServer::HandleRead, shared_from_this(), _1));
If I use boost::asio::transfer_at_least(1) in async_read, I can get the expected result.
Why did this happen?
An eof error indicates that the writer side closed the connection. Any data sent before that should still be available in the TcpServer::HandleRead callback. Check the bytes_transferred parameter to find out how much data was received by the reader.
Related
My goal is to register to a websocket service to get real-time company quotations.
So I based my code on the following example, by mostly calling (again) async_read, once we receive a quotation to accept futures quotation:
https://www.boost.org/doc/libs/master/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp
The problem is when I am waiting for a new quotation (who could take sometimes minutes or hours for small companies), the program is blocked waiting for a message and I do not have the opportunity to ask for another company.
I tried to use the "post" function to call again async_write in the good context thread but the program crashed.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message?
Here is the function I modified (simplified without mutexes):
void
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail2(ec, "read");
std::string mycontent = beast::buffers_to_string(buffer_.data());
cout << mycontent << endl;
buffer_.clear();
ws_.async_read(
buffer_,
beast::bind_front_handler(
&session::on_read,
shared_from_this()));
}
void subscribe(const std::string &symbol)
{
// We save the message in the queue
std::string text = "{\"action\": \"subscribe\", \"symbols\": \"" + symbol + "\"}";
msgqueue_.push_back(text);
boost::asio::post(ioc_, beast::bind_front_handler(&session::_subscription_to_post, shared_from_this()));
}
void _subscription_to_post()
{
if (msgqueue_.empty())
return;
// We send the message
ws_.async_write(
net::buffer(msgqueue_.front()),
beast::bind_front_handler(
&session::on_write,
shared_from_this()));
msgqueue_.pop_front();
}
And the program crashes immediately when trying to async_write.
The problem is when I am waiting for a new quotation [...] the program is blocked waiting for a message
It isn't technically blocked because you are using async_read
I tried to use the "post" function to call again async_write in the good context thread, but the program crashed.
That means you're doing something wrong. You can post a question with your self-contained minimal code, and we can tell you what is wrong.
In general, you can use a single read operation and a single write operation concurrently (as in: in flight, asynchronously, you still need to synchronize threads accessing all related resources).
Typically, you have a single async-read-chain active at all times, and a outbound message-queue that is drained by a single async-write chain (that obviously ends when the queue is empty, so needs to be initiated when the first outbound message is queued).
I have many answers on this site (literally dozens) that you may be able to find by search for outbox or outbox_ e.g. Keep in mind that the majority of them will deal with plain (ssl) sockets intead of websockets, but the pattern practically the same.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message ?
You can technically cancel() it, which completes it with operation_aborted. But that is not what you need. You want full-duplex, so writing cancel() is the opposite of what you want.
I'm implementing a client which accesses a REST endpoint and then begins processing an SSE stream and monitoring events as they occur. To this end, I'm using Boost::Beast version 124 with Boost 1.63 and attempting to use async_read_some to incrementally read the body of the response.
Here's my code thus far:
namespace http = boost::beast::http;
http::response_parser<http::string_body> sse_client::m_parser;
http::response<http::string_body> sse_client::m_response;
boost::beast::flat_buffer m_buffer;
void sse_client::monitor_sse()
{
http::request<http::empty_body> req{http::verb::get, m_target, 11};
req.set(http::field::host, m_host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
req.set(http::field::accept, "text/event-stream");
http::async_write(m_socket, req,
std::bind(
&sse_client::process_sse,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void sse_client::process_sse(boost::system::error_code ec, std::size_t byte_count)
{
http::read_header(m_socket, m_buffer, m_parser);
http::async_read_some(m_socket, m_buffer, m_parser,
std::bind(
&sse_client::read_event,
shared_from_this(),
std::placeholders::_1));
}
void sse_client::read_event(boost::system::error_code ec)
{
// TODO: process event
http::async_read_some(m_socket, m_buffer, m_parser,
std::bind(
&sse_client::read_event,
shared_from_this(),
std::placeholders::_1));
}
My questions are:
Is this the right approach for this particular use case?
Is there a more appropriate type to use with response_parser and response than http::string_body?
When the read_event handler is invoked, how does it access the content retrieved by async_read_some? Should it be pulled from the buffer?
I'll answer your questions first and then provide explanation.
Yes, you want to read the header and then call read_some (or read, see below) until the parser returns true from is_complete(). However, in your code I notice you are mixing synchronous and asynchronous calls (read_header followed by async_read_some). It would be best to stick to just one model instead of mixing them.
For your purposes you probably want buffer_body instead of string_body. There is an example in the documentation which shows how to do this (http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/parser_stream_operations/incremental_read.html)
The "buffer" you refer to is the dynamic buffer argument passed to the HTTP stream operation. While this buffer will hold the message data, it is not for the application to inspect. This buffer is used to hold additional data past the end of the current message that the stream algorithm can read (this is explained in http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/message_stream_operations.html#beast.using_http.message_stream_operations.reading). You will access the content by inspecting the body of the message when using buffer_body
http::response_parser::get() will provide you with access to the message being read in.
The best solution for you is to use buffer_body as in the example, provide an area of memory to point it to and then call read or async_read in a loop. Every time the buffer is full, the read will return with the error beast::http::error::need_buffer, indicating that further calls are required.
Hope this helps!
We have boost asio ssl server that reads data from a client. We have a requirement to perform actual read of the data in our own code as oppose to read directly by boost async_read_some() routines by passing a buffer to it. Hence we pass null_buffers() to async_read_some() and later do the actual data read using socket->read_some() API. Our sockets are all always non-blocking for read and write. http://www.boost.org/doc/libs/1_40_0/doc/html/boost_asio/overview/core/reactor.html
This works fine with normal stream (tcp) socket. However, with ssl socket, it is not working correctly.
read callback constantly gets called even when there is no data available. This churns the cpu for ever.. When ssl_socket->read_some() is called, we get 0 bytes. error code is set to 11 (Resource unavailable)
In the read callback, we always trigger future reads again, by calling socket->async_read_some(null_buffers(), ...)
If we don't do (2), then not all data is received, when large amounts of data needs to be read. If we do (2), all data is correctly received (over many read callbacks), but the read callback keeps getting called even after all data has been read and when there is no more data to read...
To verify the same, I used the example boost ssl server and client code and notice a similar behavior with following change to the server code
http://www.boost.org/doc/libs/1_40_0/doc/html/boost_asio/example/ssl/server.cpp
handle_read() call back constantly gets called for ever... If we use normal boost stream (tcp) socket (not boost ssl), then we do not see such callbacks when no data is available to read.
We see the same behavior even with the latest boost and ssl code as well. Is null_buffers() read/write mode with ssl socket well tested and supported ? I could not find much documentation for this any where..
Can some one please help. Thank you so much!
diff --git a/server.cc b/server.cc
index 3f2b028..bfd65c7 100644
--- a/server.cc
+++ b/server.cc
## -40,7 +40,10 ## public:
{
if (!error)
{
- socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ socket().non_blocking(true);
+
+ // socket_.async_read_some(boost::asio::buffer(data_, max_length),
+ socket_.async_read_some(boost::asio::null_buffers(),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
## -56,6 +59,8 ## public:
{
if (!error)
{
+ boost::system::error_code err;
+ bytes_transferred = socket_.read_some(boost::asio::mutable_buffers_1(boost::asio::buffer(data_, max_length)), err);
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
+
+ // Post for read again..
+ socket_.async_read_some(boost::asio::null_buffers(),
+ boost::bind(&session::handle_read, this,
+ boost::asio::placeholders::error,
+ boost::asio::placeholders::bytes_transferred));
I am trying to use boost asio library to implement network programming.
Here is the code which is called when some data(which ends with "##") arrives at the endpoint.
{
boost::asio::async_read_until(m_socket, m_response,
std::string("##"),
boost::bind(&CTcpClient::HandleReceive,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void CTcpClient::HandleReceive(const ErrorCodeType p_errorCode, size_t p_length)
{
IN_FUNCTION
if ( !p_errorCode )
{
logInfo(STR("Data received ..."));
boost::asio::async_read_until(m_socket, m_response,
std::string("##"),
boost::bind(&CTcpClient::HandleReceive,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
m_onReceiveSignal(sbuf2s(m_response));
}
else
{
Shutdown(p_errorCode);
}
OUT_FUNCTION
}
Let's say, the data that is sent to the end point is "KINGS##". Hence, the Handlereceive should be called once. But in my code, this is being called twice, once with "KINGS##" and another with a null string.
Can somebody tell me what is the reason and how to fix it ?
You need to be clearing out your m_response buffer, up to the token, after a successful read.
Because you aren't doing this before re-issuing an async read, your response buffer still has the ## characters in it, so the read is going to complete immediately.
As a side note, the async_read_until call may read data beyond the ##, so you have to be careful to only clear up to and including the ##, but not past it.
I think you just need to call async_read_until(...) after m_onReceiveSignal(...).
Change the order of both inside Handlereceive.
That is under the assumption the first time you call the Handlereceive is from another method (connect maybe) through async_read_untill(...)
I am having difficulties in implementing a simple TCP server. The following code is taken from boost::asio examples, "Http Server 1" to be precise.
void connection::start() {
socket_.async_read_some(
boost::asio::buffer(buffer_),
boost::bind(
&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void connection::handle_read(const boost::system::error_code& e, std::size_t bytes_transferred) {
if (!e && bytes_transferred) {
std::cout << " " << bytes_transferred <<"b" << std::endl;
data_.append(buffer_.data(), buffer_.data()+bytes_transferred);
//(1) what here?
socket_.async_read_some(
boost::asio::buffer(buffer_),
boost::bind(
&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
else// if (e != boost::asio::error::operation_aborted)
{
std::cout << data_ << std::endl;
connection_manager_.stop(shared_from_this());
}
}
In the original code the buffer_ is big enough to keep the entire request. It's not what I need. I've changed the size to 32bytes.
The server compiles and listens at port 80 of localhost, so I try to connect to it via my web browser.
Now if the statement (1) is commented-out, then only the first 32bytes of the request are read and the connection hangs. Web browser keeps waiting for the response, the server does.. I dont know what.
If (1) is uncommented, then the entire request is read (and appeded to data_), but it never stops - I have to cancel the request in my browser and only then does the else { } part run - I see my request on stdout.
Question 1: How should I handle a large request?
Question 2: How should I cache the request (currently I append the buffer to a string)?
Question 3: How can I tell that the request is over? In HTTP there always is a response, so my web-browser keeps waiting for it and doesnt close the connection, but how can my server know that the request is over (and perhaps close it or reply some "200 OK")?
Suppose browser send you 1360 bytes of data, you say asio to read some data into your buffer that you say it only have 32 bytes.
then first time that you call it your handler will be called with 32 bytes start of data. here if you comment (1) then browser try to send rest of its data(actually browser already sent it and it is in the OS buffer that wait for you to peek it from there) and you are possibly blocked behind io_service::run for some miracle!!
if you uncomment (1) as you say your loop started, you read first block, then next and another and ... until the data that the browser sent finished, but after that when you say asio to read some more data it will wait for some more data that never come from the browser( since browser already sent its information and is waiting for your answer ) and when you cancel the request from the browser, it will close its socket and then your handler will be called whith an error that say I can't read more data, since the connection is closed.!!
but what you should do here to make it work is: you should learn HTTP format and thus know what is the data that your browser sent to you and provide a good answer for it and then your communication with the client will be proceeded. in this case end of buffer is \r\n\r\n and when you see it you shouldn't read any more data, you should process what you read till now and then send a response to the browser.