I would like to create client/server communication programs pair using Boost ASIO + SSL. So I started off with the examples provided by boost, and I learned how that works, and I'm almost ready to develop my communication protocol, except that there's one problem.
So starting from this example, I'm modifying the handle_read() callback function after the handshake. The following is my code. My only modification is: Add another callback function called startComm(), which will start the communication.
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
std::cout << "Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
boost::asio::async_write(socket_,
boost::asio::buffer(std::string("Now?")),
boost::bind(&SSLClient::startComm, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
}
}
void startComm(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
std::cout << "Reply: ";
std::cout.write(reply_, bytes_transferred); //problem here, bytes transferred should contain the number of received chars not number of written chars
std::cout << "\n";
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
}
}
In the async_write() above, there's an argument boost::asio::placeholders::bytes_transferred which parametrizes my callback function to provide the number of bytes that were sent to the server. Now I would like to know the number of bytes the server responded with. How can I do that in my simple example?
Thanks. If you require any additional details, please ask.
The write call sends data.
Since it doesn't, at all, receive data the number of bytes received is by definition 0.
If you want to receive data, use (async_)read and it will tell you the number of bytes received.
These call backs use the same placeholder (bytes_transferred) but it carries different meaning depending on the direction of the transfer that has been completed.
Here's a solution that technically does what you want: define an extra parameter of startComm and bind it (not with a placeholder).
void handle_read(const boost::system::error_code &error, size_t bytes_received) {
if (!error) {
std::cout << "Reply: ";
std::cout.write(reply_, bytes_received);
std::cout << "\n";
boost::asio::async_write(socket_, boost::asio::buffer(std::string("Now?")),
boost::bind(&SSLClient::startComm,
this,
boost::asio::placeholders::error,
bytes_received,
boost::asio::placeholders::bytes_transferred));
} else {
std::cout << "Read failed: " << error.message() << "\n";
}
}
void startComm(const boost::system::error_code &error, size_t had_received, size_t bytes_sent) {
if (!error) {
std::cout << "Reply: ";
std::cout.write(reply_, had_received);
std::cout << "\n";
} else {
std::cout << "Write failed: " << error.message() << "\n";
}
}
Note that I still think you might mistakenly expect async_write to receive a reply, which (obviously?) isn't the case
Related
I am writing simple server using boost::asio.
Currently I have ready part of the server that receives data.
But I have problem. Boost::asio receives only one message (my client program sends multiple number of messages).
After receiving message I must do something to be able to receive another message from the same client?
TCPConnection::TCPConnection(boost::asio::io_service& io_service)
: socket_(io_service),
socketActive(true)
{
Traces() << "\n" << "LOG: TCPConnection::TCPConnection(boost::asio::io_service& io_service) : socket_(io_service)";
}
void TCPConnection::Start()
{
Traces() << "\n" << "LOG: void TCPConnection::Start()";
socket_.async_read_some(boost::asio::buffer(buffer_),
boost::bind(&TCPConnection::HandleRead, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void TCPConnection::HandleRead(const boost::system::error_code& e,
std::size_t bytes_transferred)
{
Traces() << "\n" << "LOG: void TCPConnection::HandleRead(const boost::system::error_code& e, std::size_t bytes_transferred)";
if (!e)
{
Traces() << "\n" << "LOG: New message";
Message tempMessage;
tempMessage.CopyData((boost::shared_ptr<TCPConnection>)this, buffer_.data());
messageQueue->PushBack(tempMessage);
}
else if (e != boost::asio::error::operation_aborted)
{
Traces() << "\n" << "LOG: Close connection";
this->Stop();
}
}
Every time you successfully read data, you need to request the io_service/socket to once again handle a data read. My usual solution looks like this:
void handle_read(asio::error_code ec, size_t bytes_read) {
if(!ec) {
/*Do Stuff*/
socket.async_read(/*buffer*/, handle_read);
} else {
/*Error handling*/
}
}
I haven't syntax checked the code I'm providing here, but you should get the right idea.
I'm developing a client-server app, both sides of which use boost::asio.
I'm trying to send a large package of data over TCP (356 kb)
On server side, I write like:
boost::asio::async_write(Msocket,
boost::asio::buffer(sendBuffer,dataLen),
boost::bind(&ServerSession::onDataWrite,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_trasferred));
The onDataWrite is simple:
void ServerSession::onDataWrite(const boost::system::error_code& error, const std::size_t bytesSent) {
if (error) {
std::cout << "Error " << error << " while sending data" << std::endl;
}
}
On client side:
int readSize = ...; // defined from msg header, in this case equals 300 kbytes.
boost::asio::async_read(*Msocket,
boost::asio::buffer(recvBuffer, 50*1024*1024),
boost::asio::transfer_exactly(readSize),
boost::bind(&ClientSession::onDataRead,
this, boost::asio::placeholders::error,
boost::asio::placeholders::bytes_trasferred,
readSize));
And onDataRead is:
void ClientSession::onDataRead(const boost::system::error_code& error, const std::size_t bytesRecvd, const int readSize) {
if (error || bytesRecvd != readSize) {
std::cout << "Error " << error << " while getting data, expect " << readSize <<", got " << bytesRecvd << std::endl;
}
}
During write, server side prints
Error system:10014 while sending data
And client prints
Error system:0 while getting data, expect 393216, got 131064
While 131064 = 128kb - 8 bytes of header.
It looks like this 128-kb issue is caused by send/receive buffer overflow. But I though Boost will take care about those buffers itself, transparently for me.
What do I misunderstand?
I have studied the existing examples:
Sending Protobuf Messages with boost::asio
Reading Protobuf objects using boost::asio::read_async
Google Protocol Buffers: parseDelimitedFrom and writeDelimitedTo for C++
Are there C++ equivalents for the Protocol Buffers delimited I/O functions in Java?
Sending Protobuf Messages with boost::asio
but I still can not figure out how to pass Google Protobuf messages using the Boost::asio API. In particular I have no clear understanding of the following problems:
Interaction between boost::asio::streambuf and google::protobuf::io objects (and the necessity of the applying of the last ones)
Correct implementation of the message streaming (due to the lack of writeDelimitedTo and parseDelimitedFrom methods in C++ API)
Here is my implementation based on boost::asio v. 1.39 ssl_client from examples.
class client
{
public:
client(boost::asio::io_service& io_service, boost::asio::ssl::context& context,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context),
request_stream(&b),
raw_output(&request_stream),
coded_output(&raw_output)
{
...
}
void handle_connect(const boost::system::error_code& error,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
...
}
//Debugging function
void print_buffers_condition(const char *step)
{
std::cout << "\nBuffer conditions after " << step << std::endl;
std::cout << "boost::asio::streambuf\t\tb: " << b.size() << std::endl;
std::cout << "google::protobuf::io::OstreamOutputStream raw_output: " << raw_output.ByteCount() << std::endl;
std::cout << "google::protobuf::io::CodedOutputStream coded_output: " << coded_output.ByteCount() << std::endl;
std::cout << std::endl;
}
//Sending test message after SSL Handshake
void handle_handshake(const boost::system::error_code& error)
{
std::cout << "-----------------------------SENDING-----------------------------" << std::endl;
print_buffers_condition("handle handshake");
if (!error)
{
SearchRequest msg;
msg.set_query("qwerty");
msg.set_code(12345);
std::cout << "Debugged" << std::endl;
msg.PrintDebugString();
//Writing the length of the message before and serializing
print_buffers_condition("before serialising");
coded_output.WriteVarint32(msg.ByteSize());
if (!msg.SerializeToCodedStream(&coded_output))
{
std::cout << "serailizing error" << std::endl;
}
else
{
std::cout << "serializing success" << std::endl;
}
//Sending
buffers_condition("before async write");
boost::asio::async_write(socket_,
b,
boost::bind(&client::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
buffers_condition("after async write");
}
else
{
std::cout << "Handshake failed: " << error << "\n";
}
}
void handle_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
std::cout << " bytes_trransferred: " << bytes_transferred << std::endl;
if (!error)
{
std::cout << "No error" << std::endl;
...
}
else
{
std::cout << "Write failed: " << error << "\n";
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
...
}
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::streambuf b;
std::ostream request_stream;
google::protobuf::io::OstreamOutputStream raw_output;
google::protobuf::io::CodedOutputStream coded_output;
};
This code is operational, so after creating the message we fall into the void handle_write(const boost::system::error_code& error, size_t bytes_transferred) function. Printing the bytes_transferred_ value returns 0: server (implemented on the base of examples too) recieves nothing.
The usage of the debugging function void print_buffers_condition(const char *step) hints at loss of message during its transmission through a stack of different buffering objects:
$ ./client 127.0.0.1 5000
-----------------------------SENDING-----------------------------
Buffer conditions after handle handshake
boost::asio::streambuf b: 0
google::protobuf::io::OstreamOutputStream raw_output: 8192
google::protobuf::io::CodedOutputStream coded_output: 0
Debugged:
query: "qwerty"
code: 12345
Buffer conditions after before serialization
boost::asio::streambuf b: 0
google::protobuf::io::OstreamOutputStream raw_output: 8192
google::protobuf::io::CodedOutputStream coded_output: 0
serializing success
Buffer conditions after before async write
boost::asio::streambuf b: 0
google::protobuf::io::OstreamOutputStream raw_output: 8192
google::protobuf::io::CodedOutputStream coded_output: 13
Buffer conditions after after async write
boost::asio::streambuf b: 0
google::protobuf::io::OstreamOutputStream raw_output: 8192
google::protobuf::io::CodedOutputStream coded_output: 13
bytes_trransferred: 0
I have no idea how to do it in a proper way.
OS is RHEL 6.4.
Thank you.
I'm not familiar with asio, but it looks to me like the problem is that you aren't flushing your buffers. The data is stuck in CodedOutputStream and never finds its way into asio.
CodedOutputStream should be allocated on the stack, such that it is destroyed as soon as you're done writing the message. The destructor will flush the buffer. Note that CodedOutputStream is cheap to allocate so there's no performance problem with putting it on the stack (in fact, it's probably better that way).
OstreamOutputStream can similarly be allocated on the stack, but it heap-allocates a buffer which you might want to reuse. If you choose to reuse the same object, make sure to call Flush() to flush the buffer after the CodedOutputStream is destroyed.
Incidentally, OstreamOutputStream is not particularly efficient, because it has to do its own layer of buffering on top of what ostream is already doing. You may want to serialize to a string (str = message.SerializeAsString() or message.SerializeToString(&str)) and then write that directly to the socket (if asio allows this), as it will probably avoid a redundant copy.
I have searched other posts, but didn't found anything relevant.
Now, I have a protocol consisting of header and body.
Protocol is like:
Z24,91009802,123456789ABCDEF
Where Z24, is the header. Z is message type, 24 is remaining bytes to read. Remaining bytes is variable, so I read until first ',' is found.
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
boost::asio::async_read_until(
socket_,
inputStreamBuffer_,
',',
boost::bind(
&session::doReadHeader, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)
);
}
else
{
delete this;
}
}
void doReadHeader(
const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
istream is(&inputStreamBuffer_);
vector<char> v(bytes_transferred);
is.read(&(v[0]),bytes_transferred);
request_.append(v.begin(),v.end());
cout << "request_=#" << request_ << "#" << endl;
int nBytes=string_to_llint(request_.substr(1,request_.size()-2));
cout << "nBytes=" << nBytes << endl;
cout << "size=" << inputStreamBuffer_.size() << endl;
boost::asio::async_read(
socket_,
inputStreamBuffer_,
boost::asio::transfer_at_least(nBytes),
boost::bind(
&session::doReadBody, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)
);
}
else
{
delete this;
}
}
void doReadBody(
const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
istream is(&inputStreamBuffer_);
vector<char> v(bytes_transferred);
is.read(&(v[0]),bytes_transferred);
request_.append(v.begin(),v.end());
string response=cardIssueProcessor_.process(request_);
cout << "request=#" << request_ << "#" << endl;
cout << "response=#" << response << "#" << endl;
request_.clear();
boost::asio::async_write(
socket_,
boost::asio::buffer(response, response.size()),
boost::bind(
&session::doWriteResponse, this,
boost::asio::placeholders::error)
);
}
else
{
delete this;
}
}
Now, the header is read. But reading the footer blocks. Apparently the entire message is read in the header call. When I do the second async_read() with boost::asio::transfer_at_least(nBytes), nBytes are already in inputStreamBuffer_, but I think the call doesn't check this?
This is dump from the output:
request_=#Z24,#
nBytes=24
size=24
What is the problem, or how can I workaround it. I am a boost newbie, so all help appreciated. Thank you.
EDIT:
I tried to check the buffer fullness, and don't make async_read() call for the body if it happens to be already read by previous call.
It kind of works, but is it the right solution?
void doReadHeader(
const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
istream is(&inputStreamBuffer_);
vector<char> v(bytes_transferred);
is.read(&(v[0]),bytes_transferred);
request_.assign(v.begin(),v.end());
cout << "request_=#" << request_ << "#" << endl;
int nBytes=string_to_llint(request_.substr(1,request_.size()-2));
cout << "nBytes=" << nBytes << endl;
cout << "size=" << inputStreamBuffer_.size() << endl;
size_t toReadBytes=nBytes-inputStreamBuffer_.size();
if (toReadBytes>0)
{
boost::asio::async_read(
socket_,
inputStreamBuffer_,
boost::asio::transfer_at_least(toReadBytes),
boost::bind(
&session::doReadBody, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)
);
}
else
{
doReadBody(error,nBytes);
}
}
else
{
delete this;
}
}
The Boost ASIO documentation indicates that the async_read_until call may read data into the buffer that is beyond the delimiter (see the Remarks section). That being said, your solution for checking whether the buffer has more data is a good solution given your input.
As I mentioned in my comment above, if your requirements will allow you to do so, using an integral value for the remaining bytes instead of a string is probably going to make your life easier and the code a bit cleaner and less error prone.
async_read_until can read bytes past the delimiter
Remarks
After a successful async_read_until
operation, the streambuf may contain
additional data beyond the delimiter.
An application will typically leave
that data in the streambuf for a
subsequent async_read_until operation
to examine.
I'm writing a simple ProxyServer that analyzes packages and sends them to another server instance, e.g. something like this:
client -> MyProxy -> SQLServer ->
client <- MyProxy <- SQLServer <-
It should run in an infinite loop.
My problem now is that the proxy seems to loose packages, sometimes it even hangs.
When I add a lot of debug information (which is written to the console), the ProxyServer is
much more stable. It seems like the ProxyServer is too fast.. :-)
I'm pretty sure I'm doing something wrong, here is code of my session class (the code is derived from the Boost::Asio examples).
#include "session.h"
#include <iostream>
using namespace std;
session::session(boost::asio::io_service& io_service)
: socket_(io_service)
, sqlsocket_(io_service)
, io_service_(io_service)
, resolver(io_service)
{
cout << "session::session()" << endl;
}
session::~session()
{
cout << "session::~session()" << endl;
cout << "closing session ..." << endl;
}
tcp::socket& session::socket()
{
return socket_;
}
void session::start()
{
cout << "session::start()" << endl;
cout << "starting session ..." << endl;
// connect to the sqlserver database
tcp::resolver::query query("192.168.1.50", "1317");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::endpoint endpoint = *endpoint_iterator;
sqlsocket_.async_connect(endpoint,
boost::bind(&session::handle_sqlserver_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
// TODO: connect to the connector
}
void session::handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
cout << "session::handle_read()" << endl;
if (!error)
{
cout << "session::handle_read() (read: "
<< bytes_transferred << ")"
<< endl;
boost::asio::async_write(sqlsocket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_sqlserver_write, this,
boost::asio::placeholders::error, bytes_transferred));
}
else
{
delete this;
}
}
void session::handle_sqlserver_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
cout << "session::handle_sqlserver_read()" << endl;
if (!error)
{
cout << "session::handle_sqlserver_read() (read: "
<< bytes_transferred << ")"
<< endl;
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error, bytes_transferred));
}
else
{
delete this;
}
}
void session::handle_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
static int count = 0;
cout << ++count << ". session::handle_write()" << endl;
if (!error)
{
cout << "session::handle_write() (read: "
<< bytes_transferred << ")"
<< endl;
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
void session::handle_sqlserver_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
cout << "session::handle_sqlserver_write()" << endl;
if (!error)
{
cout << "session::handle_sqlserver_write() (read: "
<< bytes_transferred << ")"
<< endl;
sqlsocket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_sqlserver_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
void session::handle_sqlserver_connect(const boost::system::error_code& error,
tcp::resolver::iterator endpoint_iterator)
{
cout << "session::handle_sqlserver_connect()" << endl;
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (endpoint_iterator != tcp::resolver::iterator())
{
sqlsocket_.close();
tcp::endpoint endpoint = *endpoint_iterator;
sqlsocket_.async_connect(endpoint,
boost::bind(&session::handle_sqlserver_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
}
Do I need to use other methods instead of async_* for my type of proxy?
I'm porting the code from some old project that my company wants to restart again, but with boost instead of the Winsock stuff that was used before.
Any idea what could be the problem?
The old code did something like this:
The main method with the accept method call created two threads
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)listenatclient, (LPVOID)cs, 0, 0);
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)listenatserver, (LPVOID)cs, 0, 0);
and the threads called the following functions:
void listenatclient(LPVOID connection)
{
connection_s* cs = (connection_s*)connection;
char inMessagecli[MSG_SIZE];
int rcount = 0;
...
do
{
memset(inMessagecli, 0, MSG_SIZE);
rcount = recv((SOCKET)cs->client, inMessagecli, MSG_SIZE, 0);
if (rcount != SOCKET_ERROR)
{
// analyze package
...
send((SOCKET)cs->server, inMessagecli, rcount, 0);
}
} while (rcount > 0);
}
void listenatserver(LPVOID connection)
{
connection_s* cs = (connection_s*)connection;
char inMessageserv[MSG_SIZE];
int rcount = 0;
do
{
memset(inMessageserv, 0, MSG_SIZE);
rcount = recv((SOCKET)cs->server, inMessageserv, MSG_SIZE, 0);
if (rcount != SOCKET_ERROR)
{
send((SOCKET)cs->client, inMessageserv, rcount, 0);
}
} while (rcount > 0);
}
[EDIT]:
I tried to run the async_read commands for the client and the sqlserver simultaneously, but now I get crashes all the time, sometimes in boost::bind, sometimes in other parts of the boost library.
What seems to happen is that 2 or three conections are created ( 3 sessions). While closing the first session, the crash seems to happen in the second session.
Is boost asio not treadsafe or am I doing something terribly wrong here :-) ?
I posted the code for the little ProxyServer here:
session.h : link
session.cpp : link
server.h: link
server.cpp: link
ProxyServer.cpp: link
I suspect what is happening is that one of your async_read_some calls is returning "some" data, but not enough for the SQL server to be satisfied that it has received a complete request. You code always follows the path of read_from_client -> send_to_server -> read_from_server -> send_to_client. It does not handle the case where you need read_from_client -> send_to_server - >read_from_client -> send_to_server -> read_from_server -> send_to_client.
The code you have written so far does not perform the same operations as the original. Specifically, the old code was simultaneously listening for reads on both sockets. Fortunately, since you're using ASIO, you don't need to mess with threads. Just issue issue simultaneous async_read_some requests on both sockets and deal with them asynchronously.