Client¶
In this example we’re going to create a pars application by deriving from an app::single
class that allows us to create an application that uses just one component, namely comp::client
in this case.
If you’re interested in building the library and examples as well as running the examples, take a look at the building page.
Main¶
The main function is straightforward. We just instantiate ::pars_example::apps::client
, and then execute it passing argc and argv. Should any exception arise, we will print the exception message to standard output:
1int main(int argc, char** argv)
2{
3 auto app = ::pars_example::apps::client{};
4
5 try
6 {
7 return app.exec(argc, argv);
8 }
9 catch (std::exception& e)
10 {
11 std::cout << fmt::format("Error: {}", e.what()) << "\n";
12
13 return EXIT_FAILURE;
14 }
15}
Application¶
We start by declaring such application:
1class client : public app::single<comp::client>
Then we override the only functions that we are required to implement in order to have a runnable single application (ie: the startup function):
1 void startup(int argc, char** argv) override
Startup¶
We are going to leverage the pars::app::state_machine
to handle the application state, which can be in one of the cases defined in resource::client_state
.
1 app::state_machine<client_state> state = {client_state::creating};
As a first thing hence, we’re going to ask for a transition from the actual expected state to the next state. This will check that the current application state is resource::client_state::creating
(and will throws if it’s not) and creates a pars::app::state_tx
object that initiate the transition on the pars::app::state_machine
.
Once we’re done, we’re going to commit this state transition in order for the state to change to resource::client_state::initializing
. Should any exception be raised after the creation of this pars::app::state_tx
, the initiated transition will be rolled back and the app state machine will remain in the initial state:
1 auto ts = state.tx(client_state::creating, client_state::initializing);
Next we’re going to read the inputs:
1 switch (argc)
2 {
3 case 6:
4 connect_p.service_cmode = net::cmode_from_string(argv[1]);
5
6 connect_p.service_addr = argv[2];
7
8 work_id = std::stoull(argv[3]);
9
10 if (std::string_view(argv[4]).compare("fast") == 0)
11 fast_fib = true;
12 else if (std::string_view(argv[4]).compare("slow") == 0)
13 fast_fib = false;
14 else
15 usage();
16
17 n = std::stoull(argv[5]);
18
19 break;
20
21 default:
22 usage();
23 }
Now we register the handler functions (ie: the ones that react to various kinds of events) for the events that we’re intrested in:
1 hfs().on<fired, init>(&self::initialize, this);
2
3 hfs().on<fired, exception>(&self::terminate, this);
4
5 comp().req().on<fired, pipe_created>(&self::send_work, this);
6
7 comp().req().on<sent, fib_requested>(&self::recv_answer, this);
8
9 comp().req().on<received, fib_computed>(&self::terminate, this);
10
11 comp().req().on<fired, network_error>(&self::terminate, this);
12
13 comp().req().on<fired, pipe_removed>(&self::terminate, this);
Finally as anticipated, we just commit the state transition:
1 ts.commit();
2
3 pars::info(SL, "Application Started!");
Handler Functions¶
We’re going now to briefly comment all the handler functions that we registered during startup (see previous section).
This application is made out of a client component, which has a single nng_req socket. These handler functions are going to connect to the server, send the request and print the output or any error that could arise.
On fired<init>
we’re going to initialize the client component and connect to the specified address using the given mode:
1 void initialize(hf_arg<fired, init> fired)
2 {
3 auto ts = state.tx(client_state::initializing, client_state::started);
4
5 comp().init({.req_opts = {.recv_timeout = req_recv_timeout.count(),
6 .send_timeout = req_send_timeout.count()}});
7
8 comp().connect(connect_p);
9
10 ts.commit();
11
12 pars::info(SL, "Fired {}, Application Initialized!", fired.event());
13 }
On fired<pipe_created>
(ie: connection established), we’re going to start a send operation on the req socket:
1 void send_work(hf_arg<fired, pipe_created> fired)
2 {
3 auto ts = state.tx(client_state::started, client_state::sending_work);
4
5 auto [ev, md] = fired.as_tuple();
6
7 auto out_ev = fib_requested{work_id, n, fast_fib};
8
9 // use the default context on the sock to send the event
10 comp().req().sock().send(out_ev, md.pipe());
11
12 ts.commit();
13
14 pars::info(SL, "Fired {}, Sent {}!", ev, out_ev);
15 }
On sent<event::fib_requested>
we start a receive operation:
1 void recv_answer(hf_arg<sent, fib_requested> sent)
2 {
3 auto ts =
4 state.tx(client_state::sending_work, client_state::waiting_work_done);
5
6 // recv on the default context of the sock
7 comp().req().sock().recv();
8
9 ts.commit();
10
11 pars::info(SL, "Sent {}, Receiving!", sent.event());
12 }
On received<event::fib_computed>
we terminate the component and print the result:
1 void terminate(hf_arg<received, fib_computed> recv)
2 {
3 auto ts =
4 state.tx(client_state::waiting_work_done, client_state::terminating);
5
6 auto& ev = recv.event();
7
8 graceful_terminate();
9
10 ts.commit();
11
12 pars::info(SL, "Received {}, Application Terminated!", ev);
13
14 std::cout << "WORK(" << ev.work_id << ") FIB(" << n << ") = " << ev.fib_n
15 << "\n";
16 }
On fired<exception>
(ie: an exception was thrown during an execution of some handler function), we just terminate and print the exception message:
1 void terminate(hf_arg<fired, exception> fired)
2 {
3 auto ts = state.tx(client_state::terminated);
4
5 auto& ev = fired.event();
6
7 graceful_terminate();
8
9 ts.commit();
10
11 pars::info(SL, "Fired {} while \"{}\", Application Terminated!", ev,
12 state.current());
13
14 std::cout << fmt::format("ERROR: {}", ev) << std::endl;
15 }
On fired<network_error>
(ie: we got some error while executing a send or recv operation), we just terminate and print the error message:
1 void terminate(hf_arg<fired, network_error> fired)
2 {
3 auto ts = state.tx(client_state::terminated);
4
5 auto& ev = fired.event();
6
7 graceful_terminate();
8
9 ts.commit();
10
11 pars::info(SL, "Fired {} while \"{}\", Application Terminated!", ev,
12 state.current());
13
14 std::cout << fmt::format("ERROR: {}", ev.error) << std::endl;
15 }
On fired<pipe_removed>
(ie: connection gone), we just terminate and inform the client was disconnected:
1 void terminate(hf_arg<fired, pipe_removed> fired)
2 {
3 auto ts = state.tx(client_state::terminated);
4
5 graceful_terminate();
6
7 ts.commit();
8
9 pars::info(SL, "Fired {} while \"{}\", Application Terminated!",
10 fired.event(), state.current());
11
12 std::cout << fmt::format("Client Disconnected!") << std::endl;
13 }