I've been following numerous tutorials online on learning Asynchronous Networking in Asio, so if I've made a really obvious mistake, there's your explanation.
Nonetheless, I've written a program that sets up both a client and server simultaneously and tries to communicate between the two. Simply connecting and making requests to send/receive data seem to be working fine, but the data itself isn't being sent.
#define ASIO_STANDALONE
#include<asio.hpp>
#include<thread>
#include<iostream>
#include<vector>
#include<array>
#include<mutex>
#include<memory>
#include<functional>
#define IPADDRESS "127.0.0.1"
#define PORT "6118"
enum side_type {
t_server, t_client
};
std::mutex m_lock;
std::array<char, 32> clientBuffer;
std::array<char, 32> serverBuffer;
bool stop(false);
void read_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void write_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void read_function(const asio::error_code& ec, size_t bytes_read, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
if (ec) return;
using namespace std;
using namespace std::placeholders;
char value = buffer[0];
{
lock_guard<mutex> guard(m_lock);
string type_str = type == t_server ? "Server" : "Client";
cout << "Value of " << int(value) << " read by " << type_str << "." << endl;
}
if (value >= 100) stop = true;
else {
if(type == t_server)
buffer[0] = value + 1;
socket->async_write_some(asio::buffer(&buffer[0], buffer.max_size()), bind(write_function, _1, _2, socket, buffer, type));
}
}
void write_function(const asio::error_code& ec, size_t bytes_written, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
if (ec) return;
using namespace std;
using namespace std::placeholders;
socket->async_read_some(asio::buffer(&buffer[0], buffer.max_size()), bind(read_function, _1, _2, socket, buffer, type));
}
void work_function(std::shared_ptr<asio::io_service> io_service) {
using namespace std;
asio::error_code ec;
while (!ec) {
try {
io_service->run(ec);
break;
}
catch (exception & e) {
lock_guard<mutex> guard(m_lock);
cout << "Exception thrown: \"" << e.what() << "\"." << endl;
}
}
}
void connect_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
using namespace std;
using namespace std::placeholders;
lock_guard<mutex> guard(m_lock);
if (ec) {
cout << "Error Connecting: " << ec << endl;
}
else {
cout << "Successful Connection!" << endl;
socket->async_read_some(asio::buffer(&clientBuffer[0], clientBuffer.max_size()), bind(read_function, _1, _2, socket, clientBuffer, t_client));
}
}
void accept_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
using namespace std;
using namespace std::placeholders;
lock_guard<mutex> guard(m_lock);
if (ec) {
cout << "Error Accepting: " << ec << endl;
}
else {
cout << "Successful Acception!" << endl;
serverBuffer[0] = 0;
socket->async_write_some(asio::buffer(&serverBuffer[0], serverBuffer.max_size()), bind(write_function, _1, _2, socket, serverBuffer, t_server));
}
}
int main(int argc, char** argv) {
using namespace std;
using namespace std::placeholders;
shared_ptr<asio::io_service> io_service(new asio::io_service());
shared_ptr<asio::io_service::work> work(new asio::io_service::work(*io_service));
vector<shared_ptr<thread>> threads;
int num_of_threads = thread::hardware_concurrency();
for (auto i = 0; i < thread::hardware_concurrency(); i++) {
threads.push_back(shared_ptr<thread>(new thread(work_function, io_service)));
}
using namespace asio::ip;
tcp::resolver resolver(*io_service);
tcp::resolver::query query(IPADDRESS, PORT);
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::endpoint endpoint = *iterator;
cout << "Connecting to " << endpoint << endl;
shared_ptr<tcp::acceptor> acceptor(new tcp::acceptor(*io_service));
shared_ptr<tcp::socket> acc_socket(new tcp::socket(*io_service));
shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));
acceptor->open(endpoint.protocol());
acceptor->set_option(tcp::acceptor::reuse_address(false));
acceptor->bind(endpoint);
acceptor->listen(asio::socket_base::max_connections);
acceptor->async_accept(*acc_socket, bind(accept_function, _1, acc_socket));
asio::error_code ec;
socket->async_connect(endpoint, bind(connect_function, _1, socket));
//while (!stop);
cout << "Press Any Key to Continue..." << endl;
cin.get();
socket->shutdown(tcp::socket::shutdown_both, ec);
socket->close(ec);
work.reset();
while (!io_service->stopped());
for (shared_ptr<thread> & t : threads) {
t->join();
}
return 0;
}
As output, I've been getting the following:
Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 0 read by Client.
Value of 1 read by Server.
Value of 0 read by Client.
Value of 2 read by Server.
Value of 0 read by Client.
Value of 3 read by Server.
......
Value of 0 read by Client.
Value of 98 read by Server.
Value of 0 read by Client.
Value of 99 read by Server.
Value of 0 read by Client.
Value of 100 read by Server.
However, what I'm expecting is:
Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 1 read by Client.
Value of 1 read by Server.
Value of 2 read by Client.
Value of 2 read by Server.
Value of 3 read by Client.
Value of 3 read by Server.
......
Value of 98 read by Client.
Value of 98 read by Server.
Value of 99 read by Client.
Value of 99 read by Server.
Value of 100 read by Client.
Value of 100 read by Server.
Clearly what's happening is that the Server buffer is getting updated (when I manually increment the value), while the Client Buffer never gets updated by the async_read_some function. Additionally, because the client buffer never gets updated, the server is just reading in old values (also without getting updated) and thus technically has incorrect output as well. However, I don't know what's wrong. I'm passing in all my buffers the way I think I'm supposed to, and all the functions seem to be bound correctly, but the data isn't being passed. So what did I do wrong?
The problem is that a copy of the buffer is being bound to the completion handler, which is a different buffer than that which is provided to the asynchronous operations:
socket->async_read_some(asio::buffer(buffer), std::bind(..., buffer, ...));
// ^~~~~~ = reference ^~~~~~ = copy
In the above snippet, the async_read_some() operation will operate on buffer, and the completion handler will be provided a copy of buffer before the operation has made any modifications. To resolve this, use std::ref() to pass a reference to std::bind().
socket->async_read_some(asio::buffer(buffer), std::bind(..., std::ref(buffer), ...));
// ^~~~~~ = reference ^~~~~~ = reference
In this case, passing a reference will also fix a potential case where undefined behavior could have been invoked. The async_write_some() and async_read_some() operations require that ownership of the underlying buffer memory is retained by the caller, who must guarantee that it remains valid until the completion handler is called. When std::bind() was being provided a copy of the buffer, the buffer's lifetime was bound to the functor object returned from std::bind(), which may have ended before the completion handler was invoked.
void read_function(
...,
std::shared_ptr<asio::ip::tcp::socket> socket,
std::array<char, 32>& buffer,
...)
{
...
socket->async_write_some(asio::buffer(buffer), handler);
} // buffer's lifetime ends shortly after returning from this function
socket->async_read_some(
asio::buffer(buffer),
std::bind(&read_function, ..., socket, buffer, ...));
Here is an example demonstrating the fundamental problem and behavior:
#include <array>
#include <cassert>
#include <functional>
int get_data(std::array<char, 32>& data)
{
return data[0];
}
int main()
{
std::array<char, 32> data;
data[0] = 0;
auto fn_copy = std::bind(&get_data, data);
auto fn_ref = std::bind(&get_data, std::ref(data));
data[0] = 1;
assert(0 == fn_copy());
assert(1 == fn_ref());
}
Your Readhandler and WriteHander:
void read_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void write_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
don't conform to the asio Read handler and Write handler requirements. I.e. just:
void read_function(const asio::error_code&, size_t);
void write_function(const asio::error_code&, size_t);
Your application needs to "own" the read and write buffers and not expect their locations to be sent back to you by the handlers. If you use clientBuffer and serverBuffer where appropriate, it should work correctly.
I am made a Server & Client Asynchronous Application. All works perfectly except the message I receive. I am sending image pieces into strings. But when I receive them back, the string is corrupted, I that it's not the same as I send. The length it's the same, and almost all characters. If I compare what I send with what I received I have like 300 characters different from what I sent. I am sending strings of 50.000 characters. Any idea what may be the problem? The most of the code are comments, so you will understand it in seconds. Also, I shrinked it and made it easier for you to read.
I am sending with this.
// Send a message
void StartSendMessage ( MessagePtr msg )
{
// As long as the queue is not empty, the 'sending agent' is still alive
bool writeInProgress =! m_messageQueue.empty() ;
// Queue the message
m_messageQueue.push ( msg ) ;
if ( msg -> BodyLength() != 0 )
{
std:: cout << "Sending :" << msg -> BodyLength() << std:: endl ;
}
// If the 'sending agent' is inactive, start it
if ( !writeInProgress )
{
// Send message asynchronously. We leave the message on the queue
// since it needs to be available during the async read
async_write ( m_socket , boost::asio::buffer ( msg -> HeaderData() , msg -> SendLength () ) ,
boost::bind ( &ASyncConnectionMT::HandleSentMessage , this , boost::asio::placeholders::error , boost::asio::placeholders::bytes_transferred ) ) ;
}
}
// Message was sent
void HandleSentMessage ( const boost::system::error_code& ec , size_t size )
{
// Check the error code
if ( ec )
{
// Transfer error
std:: cout << "Error sending message: " << ec.message() << std:: endl ;
DoStop() ;
return ;
}
// Remove the sent message from queue
m_messageQueue.pop() ;
// If the que is not empty, send next message asynchronously.
// We leave the message on the que since it needs to be available during the async send
if ( !m_messageQueue.empty() )
{
MessagePtr msg = m_messageQueue.front() ;
std:: cout << "Message send lenght "<< msg->SendLength() ;
async_write ( m_socket , boost::asio::buffer ( msg -> HeaderData() , msg -> SendLength () ) ,
boost::bind ( &ASyncConnectionMT:: HandleSentMessage , this , boost::asio::placeholders::error , boost::asio::placeholders::bytes_transferred ) ) ;
}
}
I am reading with this.
void StartReceiving()
{
// Create receive buffer
BufferPtr receiveBuffer ( new Buffer ) ;
// Start async read, must pass 'this' as shared_ptr, else the
// 'this' object will be destroyed after leaving this function
m_socket.async_read_some ( boost::asio::buffer ( *receiveBuffer ) , boost::bind ( &ASyncConnectionMT::HandleReceivedd , shared_from_this() , receiveBuffer ,
boost::asio::placeholders::error , boost::asio::placeholders::bytes_transferred ) );
}
// Handle received data
void HandleReceivedd ( BufferPtr receiveBuffer , const boost::system::error_code& ec , size_t size)
{
if ( !ec )
{
BufferPtr sendBuffer ( new Buffer ) ;
std:: cout << m_socket.remote_endpoint() << ": Message received: " << std:: string (receiveBuffer -> data() , size ) << std:: endl << std:: endl;
std:: cout << "Message lenght received " << size << std:: endl;
// Start receiving next bit
StartReceiving() ;
}
else if ( ec == boost::asio::error::eof)
{
// Client disconnected. Close the socket.
std:: cout << m_socket.remote_endpoint() << ": Connection closed ( handle received )" << std:: endl;
m_socket.close();
}
}
I see several problems in this chunk of code:
1) When you send, you are putting copy of msg into m_messageQueue. But, when you call async_write, your buffer is constructed from pointer taken from msg, not m_messageQueue. So eventually you can send from incorrent buffer.
2) On receive you creating receiveBuffer on stack. When async_read_some immediatelly returns (almost always), your receiveBuffer will be destroyed because you exit from StartReceiving call.
3) Same with sendBuffer
I am trying to read specific number of bytes from the socket. My server is sending:
1) byte[0] - length of the message
2) byte[1:N] - the actual message
How do I read the first byte and then read the remaining bytes using boost::asio::ip::tcp::socket::read ? Here is the code snippet:
// receive data through the socket
void TCPTestClient::ReceiveData( )
{
try
{
boost::system::error_code error;
boost::asio::streambuf receivedStreamBuffer;
// reserve 512 bytes in output sequence
boost::asio::streambuf::mutable_buffers_type bufs =receivedStreamBuffer.prepare( 512 );
boost::asio::read( m_socket,
bufs,
error );
// transfer the buffer contents to string
std::istream is( &receivedStreamBuffer );
is >> m_receivedMessageStr;
// throw exception if error occurred
if ( error )
{
throw NetworkTestFailedException( error.message() );
}
}
catch(...)
{
}
}
You'll want to prepare a buffer for the one byte header, then prepare another buffer for the message. A simplified example might be
boost::asio::read(
m_socket,
receivedStreamBuffer.prepare(1),
error
);
if ( error ) {
std::cerr << "Read header failed: " << boost::system::system_error(error).what() << std::endl;
return;
}
receivedStreamBuffer.commit(1);
std::istream header( &receivedStreamBuffer );
uint8_t size;
header >> size;
// reserve message size in output sequence
boost::asio::read(
m_socket,
receivedStreamBuffer.prepare( size ),
bufs,
error
);
if ( error ) {
std::cerr << "Read message failed: " << boost::system::system_error(error).what() << std::endl;
return;
}
receivedStreamBuffer.commit( size );
// transfer the buffer contents to string
std::istream is( &receivedStreamBuffer );
is >> m_receivedMessageStr;
I'm using what looks to be a real nice API for streaming sockets found here:
http://www.pcs.cnu.edu/~dgame/sockets/socketsC++/sockets.html.
I'm having trouble accessing the IP of the connected user because its a private member of a class "Socket" that is used within another class "ServerSocket". My program looks exactly like the demo only it it forks processes.
// libraries
#include <signal.h>
#include <string>
#include <iostream>
// headers
#include "serversocket.hpp"
#include "socketexception.hpp"
#include "config.hpp"
using namespace std;
void sessionHandler( ServerSocket );
int main ( int argc, char** argv )
{
configClass config; // this object handles command line args
config.init( argc, argv ); // initialize config with args
pid_t childpid; // this will hold the child pid
signal(SIGCHLD, SIG_IGN); // this prevents zombie processes on *nix
try
{
ServerSocket server ( config.port ); // create the socket
cout << "server alive" << "\n";
cout << "listening on port: " << config.port << "\n";
while ( true )
{
ServerSocket new_client; // create socket stream
server.accept ( new_client ); // accept a connection to the server
switch ( childpid = fork() ) // fork the child process
{
case -1://error
cerr << "error spawning child" << "\n";
break;
case 0://in the child
sessionHandler( new_client ); // handle the new client
exit(0); // session ended normally
break;
default://in the server
cout << "child process spawned: " << childpid << "\n";
break;
}
}
}
catch ( SocketException& e ) // catch problem creating server socket
{
cerr << "error: " << e.description() << "\n";
}
return 0;
}
// function declarations
void sessionHandler( ServerSocket client )
{
try
{
while ( true )
{
string data;
client >> data;
client << data;
}
}
catch ( SocketException& e )
{
cerr << "error: " << e.description() << "\n";
}
}
So my question is, can I not access the IP of the client currently connected to the socket? If it has to be modified for that functionality, what would the cleanest way to do it be?
Thanks for suggestions
I was able to add these 2 functions that allowed me to get the IP only from the scope of main like this:
server.get_ip( new_client );
but what I'd really like is to get it like this new_client.ip();
here's my 2 functions, maybe you can help me further:
std::string Socket::get_ip( Socket& new_socket )
{
char cstr[INET_ADDRSTRLEN];
std::string str;
inet_ntop(AF_INET, &(m_addr.sin_addr), cstr, INET_ADDRSTRLEN);
str = cstr;
return str;
}
std::string ServerSocket::get_ip( ServerSocket& sock )
{
return Socket::get_ip( sock );
}
The Socket class you are using has a private data member:
sockaddr_in m_addr;
This contains the info of the client connected to the socket. You can get the human-readable address with:
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(m_addr.sin_addr), str, INET_ADDRSTRLEN);
As for the changes you need to make, either make m_addr public (not recommended) or add a member function that can return a string based on the above code sample.
I have this piece of code here:
These are functions used to create and stop a pthread:
void WatchdogController::conscious_process_handler_start() {
if ( debug ) cout << "WatchdogController: starting conscious process thread" << endl;
cn_pr_thread_active = true;
if ( pthread_create( &cn_pr_thread, NULL, conscious_process_handler, this ) < 0 ) {
cn_pr_thread_active = false;
throw WatchdogException( "Unable to start new thread" );
}
}
void WatchdogController::conscious_process_handler_stop() {
if ( debug ) cout << "WatchdogController: stopping conscious process thread" << endl;
cn_pr_thread_active = false;
int *retval;
pthread_join( cn_pr_thread, ( void ** )&retval );
if ( *retval < 0 ) {
delete retval;
string err = string( "Error returned by conscious_process_handler(): " ) + string( pthread_err );
throw WatchdogException( err.c_str() );
}
delete retval;
}
I use select() in function passed to pthread, and when stopped it returns an error resulting in return value from pthread being negative, but that's not the issue, I'll fix it later - problem is, that when the exception is thrown here:
throw WatchdogException( err.c_str() );
and caught here:
try {
watchdog_controller->hardware_watchdog_stop();
watchdog_controller->unconscious_process_handler_stop();
watchdog_controller->conscious_process_handler_stop();
}
catch ( HardwareWatchdogException &e ) {
cerr << "Error stopping hardware watchdog!" << endl;
cerr << e.get_reason() << endl;
string err = string( "Exception thrown by hardware watchdog controller" ) + string( e.get_reason() );
if ( log ) write_log( err.c_str() );
delete watchdog_controller;
return -1;
}
catch ( WatchdogException &e ) {
cerr << "Exception cought when exiting!" << endl;
cerr << e.get_reason() << endl;
string err = string( "Exception cought when exiting" ) + string( e.get_reason() );
if ( log ) write_log( err.c_str() );
delete watchdog_controller;
return -1;
}
I get segmentation fault then trying to access the object at this point:
cerr << e.get_reason() << endl;
What could be the reason?
Reference &e points to something, but it seems as if the address was invalid.
Here's the exception class:
class WatchdogException {
public:
/**
#brief Default constructor
*/
WatchdogException() : reason() {
}
/**
#brief Overloaded constructor - setting the error message
#param why Error message
*/
WatchdogException( const char *why ) : reason( why ) {
}
/**
#brief The destructor
*/
virtual ~WatchdogException() {
}
/**
#brief A getter for the error message
#return Returns a string containing error description
*/
virtual std::string get_reason() const {
return reason;
}
protected:
/**
#var reason String containing the error message
*/
std::string reason;
};
I am guessing that you are not properly allocating memory for retval, or that somehow you are returning an invalid pointer from cn_pr_thread, and that is why you get a segmentation fault when you call pthread_join.
In WatchDogException's constructor, are you remembering the pointer to the c-string passed in or are you making a copy of it.
If you're simply storing the pointer then when "err" goes out of scope when the exception is thrown the pointer returned by c_str() will be bad, hence your seg fault when you try and use it.