I am designing a client-server app and I need to use three different channels between them. Below on the schema, the client opens a connection to the server (1), and then the server opens two connections to the client on other ports (2).
|-----------| |-----------|
| | (1) port: 8000 | |
| |<-----------------| |
| | | |
| | | |
| Server | (2) port: 8001 | Client |
| |----------------->| |
| | | |
| | (2) port: 8002 | |
| |----------------->| |
|-----------| |-----------|
A simple channel server example : http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/example/cpp11/echo/async_tcp_echo_server.cpp
My questions is how to implement this, based on boost.asio framework ? Or, how to open the new connections (2) from the server ?
PS Maybe a better protocol could be to open the two other connections from client when the first is established ? But what could be the structure used in asio in this case ?
Thanks a lot in advance !
As has been pointed out, any system that's required to be robust should not assume that routing/firewalling is such that connections can be made back to the client from the server.
How would we go around this?
This is fairly easy, but requires a wire protocol to coordinate the sessions and channel "roles" for connections. I did initially elect not to implement this as a demo. Instead I thought it would be a nice exercise to learn Boost Asio, and implemented the back-channels (initiated from the server side) as in your original drawing.
The full code is on github: https://gist.github.com/sehe/9946161
Notes:
shared_ptr<>
, at least in my view. The reason is that it's very beneficial if the coroutine (which is also the completion functor) is copyable without any issues. I could probably clean this up by making the class itself enable_shared_from_this
and bind to shared_from_this
instead. The benefit now is that there are (almost) no bind
-expressions. Creating the 'back channels' is done in the server class which overrides on_accept
:
virtual bool on_accept(tcp::socket& socket) override { auto host = socket.remote_endpoint().address().to_string(); // for now setting up the back-connections is all synchronous - // that might not work well in practice (scaling, latency) but... try { tcp::resolver resolver(socket.get_io_service()); auto ep1 = resolver.resolve(tcp::resolver::query(host, "8001")); auto ep2 = resolver.resolve(tcp::resolver::query(host, "8002")); backsock1 = make_shared<tcp::socket>(socket.get_io_service()); backsock2 = make_shared<tcp::socket>(socket.get_io_service()); backsock1->connect(*ep1); backsock2->connect(*ep2); std::cerr << "on_accept: back channels connected for " << host << "\\n"; } catch(std::exception const& e) { std::cerr << "on_accept: '" << e.what() << "' for " << host << "\\n"; return false; } return base_type::on_accept(socket); }
If on_accept
fails (eg for our server cannot connect the back-channels) an error is returned on the "main" socket (initial connection) and the session is aborted
There are three programs:
run_server
(which listens on port 8000) run_client
(which connects to port 8000 and listens on 8001,8002), and sends 1 message. You can observe how the the server responds by connecting on the back-channels and sending different messages on all three sockets.
test
(which combines the two):
#include <boost/asio.hpp> #include <boost/thread.hpp> #include "server.hpp" #include "client.hpp" int main() { boost::asio::io_service svc; // start service on a separate thread boost::thread th([&svc] { svc.post(demo::server(svc)); svc.run(); }); boost::this_thread::sleep_for(boost::chrono::milliseconds(500)); // allow server to start accepting // post client traffic to the service std::cerr << "Starting a test client that sends a message...\\n"; demo::client client(svc, "localhost", "8000"); // await interrupt (or new connections) th.join(); }
The output of the last program looks like:
Starting a test client that sends a message...
on_accept: back channels connected for 127.0.0.1
listener 127.0.0.1:8000: accepting connection from 127.0.0.1:40999
listener 127.0.0.1:8000: 'hello world from demo client' received from 127.0.0.1:40999
listener 127.0.0.1:8001: accepting connection from 127.0.0.1:40132
listener 127.0.0.1:8002: accepting connection from 127.0.0.1:37970
listener 127.0.0.1:8001: 'We've received a request of length 29' received from 127.0.0.1:40132
listener 127.0.0.1:8002: 'We're handling it in void demo::server::do_back_chatter(const string&)' received from 127.0.0.1:37970
listener 127.0.0.1:40999: 'ECHO hello world from demo client' received from 127.0.0.1:8000
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.