Boost::Asio TCP server: sending a message to client as a response to an event

Hi. I have a TCP server. When the server receives a hand shake message from a client, it sends a message back. My code is along the following lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
  class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
    
    typedef boost::shared_ptr<tcp_connection> pointer;
    
    static pointer create(boost::asio::io_service& io_service)
    {
        return pointer(new tcp_connection(io_service));
    }
    
    tcp::socket& socket()
    {
        return socket_;
    }
    
    
    void startConnection()
    {
        start_read();
    }
    
    
    void stopConnection()
    {
        socket_.close();
    }
    
    
private:
    
    tcp_connection(boost::asio::io_service& io_service) : socket_(io_service)
    {
    }
    

    void start_read()
    {
        boost::asio::async_read_until(socket_, input_buffer_, '\n', boost::bind(&tcp_connection::handle_read, shared_from_this(), boost::asio::placeholders::error));
    }
    
    
    void handle_read(const boost::system::error_code& error)
    {
        if (!error)
        {
            boost::asio::streambuf::const_buffers_type bufs = input_buffer_.data();
            
            std::string msgstr(boost::asio::buffers_begin(bufs),
                               boost::asio::buffers_begin(bufs) +
                               input_buffer_.size());
            
            // Remove the first part of the buffer to prevent from accumulating
            input_buffer_.consume(input_buffer_.size());
            
            std::vector<std::string> msgVector;
            boost::split(msgVector, msgstr, boost::is_any_of(":"));
            
            if(msgVector.size() >= 2)
            {
                messageFromClient_ = msgVector[0];
                valueFromClient_ = msgVector[1];
                
                // HERE I HAVE CODE TO HANDLE MESSAGES FROM CLIENT
                
                boost::asio::async_write(socket_, 
boost::asio::buffer(messageToClient_), 
boost::bind(&tcp_connection::handle_write, shared_from_this(), 
boost::asio::placeholders::error, 
boost::asio::placeholders::bytes_transferred));
                
                start_read();
            }
        }
        else
        {   
            stopConnection();
        }
    }
    

    void handle_write(const boost::system::error_code& /*error*/,
                      size_t /*bytes_transferred*/)
    {
    }
    
    tcp::socket socket_;
    boost::asio::streambuf input_buffer_;
};


class tcp_server
{
public:
    
    tcp_server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), tcpPort))
    {
        start_accept();
    }
    
    void start_accept()
    {
        
        tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
        
        acceptor_.async_accept(new_connection->socket(),
                               boost::bind(&tcp_server::handle_accept, this, new_connection, boost::asio::placeholders::error));
    }
    
    void handle_user_read(const boost::system::error_code& err, std::size_t bytes_transferred)
    {
    }
    
    void handle_accept(tcp_connection::pointer new_connection, const boost::system::error_code& error)
    {
        if (!error)
        {
            new_connection->startConnection();
            
            start_accept();
        }
    }
    
    tcp::acceptor acceptor_;
};



In my Main class, I initiate the server:

 
myServer.reset(new tcp_server(io_service));


What I want to do now is to have the server listen to a specific event (the Main class implements a loop callback every 1/24 second) and send a message to the client once the event has happened.

I understand that I need to call something like this in the tcp_connection class:

1
2
3
4
5
6
7
void start_listen()
    {
        boost::asio::async_write(socket_, boost::asio::buffer(messageToClient_),
                                 boost::bind(&tcp_connection::handle_write, shared_from_this(),
                                             boost::asio::placeholders::error,
                                             boost::asio::placeholders::bytes_transferred));
    }


from the Main class every time the event takes place, but I do not understand what would be the proper way to do it.

I obviously cannot do this:

 
tcp_connection::pointer.start_listen()


Also, I tried to make new_connection as a class variable instead of local, however this does not allow a new connection with the same client while the first one is being run (the client should be able to establish multiple connections in different threads).

If someone has a solution, I would greatly appreciate your help!
Last edited on
Actually due to the asynchronous nature of your server you shouldn't need additional threads.

In order to send all accepted connection a [periodic] message you should store new_connection into a vector (which is actually a member variable) within handle_accept(...).
Without threading there is the benefit of no need to protect the member variables.

By the way: you can realize the timed loop with the asynchronous timers. See:

https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/steady_timer.html
Last edited on
Thank you, coder777.

This is a step forward in the right direction for me.

I have created a vector of connections as a member variable in the .h file:

1
2
typedef boost::shared_ptr<tcp_connection> pointer;
std::vector<pointer> connections;


and add connections to it:

1
2
3
4
5
6
7
8
9
10
void handle_accept(pointer new_connection,
                       const boost::system::error_code& error)
    {
        if (!error)
        {
            new_connection->startConnection();
            connections.push_back(new_connection);
            start_accept();
        }
    }


Now, I could implement the following in the loop callback method:

connections[i]->start_listen();

where:

1
2
3
4
5
6
7
8
9
void start_listen()
    {
        std::string listen_msg = "TEST";
        
        boost::asio::async_write(socket_, boost::asio::buffer(listen_msg),
                                 boost::bind(&tcp_connection::handle_write, shared_from_this(),
                                             boost::asio::placeholders::error,
                                             boost::asio::placeholders::bytes_transferred));
    }


However, if I have one connection that requires the loop callback and another parallel one that does not, how can I identify i? There are no identifiers associated with every new_connection...

Cheers.
Last edited on
Actually you can have additional data with you connection. Just use struct/class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef boost::shared_ptr<tcp_connection> pointer;
struct conn_data
{
  pointer p;
  bool is_loop_callback = false;
};
std::vector<conn_data> connections;

...
	
void handle_accept(pointer new_connection,
                       const boost::system::error_code& error)
    {
        if (!error)
        {
            new_connection->startConnection();
            bool is_loop_callback = ...;
            connections.emplace_back(new_connection, is_loop_callback);
            start_accept();
        }
    }
Topic archived. No new replies allowed.