--- /dev/null
+#include <stdlib.h>
+#include <gras.h>
+
+XBT_LOG_NEW_DEFAULT_CATEGORY(test,"My little example");
+
+typedef struct {
+ int done;
+} server_data_t;
+int server_done_cb(gras_msg_cb_ctx_t ctx, void *payload) {
+ server_data_t *globals=(server_data_t*)gras_userdata_get();
+ globals->done = 1;
+ INFO0("Server done");
+
+ return 1;
+} /* end_of_done_callback */
+
+void message_declaration(void) {
+ gras_msgtype_declare_rpc("convert a2i", gras_datadesc_by_name("string"), gras_datadesc_by_name("long"));
+ gras_msgtype_declare_rpc("convert i2a", gras_datadesc_by_name("long"), gras_datadesc_by_name("string"));
+
+ /* the other message allowing the client to stop the server after use */
+ gras_msgtype_declare("done", NULL);
+}
+
+int server_convert_i2a_cb(gras_msg_cb_ctx_t ctx, void *payload) {
+ long data = *(long*)payload;
+ char *result;
+ char *p;
+
+ INFO1("Convert %ld to string",data);
+ result = bprintf("%ld", data);
+ INFO2("%ld converted to string: %s",data,result);
+
+ gras_msg_rpcreturn(60,ctx,&result);
+ free(result);
+ return 1;
+} /* end_of_convert_callback */
+
+int server_convert_a2i_cb(gras_msg_cb_ctx_t ctx, void *payload) {
+ char *string = *(char**)payload;
+ long result;
+ char *p;
+
+ INFO1("Convert %s to long",string);
+ result = strtol(string, &p, 10);
+
+ if (*p != '\0')
+ THROW2(arg_error,0,"Error while converting %s: this does not seem to be a valid number (problem at '%s')",string,p);
+
+ gras_msg_rpcreturn(60,ctx,&result);
+ return 1;
+} /* end_of_convert_callback */
+
+
+int server(int argc, char *argv[]) {
+ gras_socket_t mysock; /* socket on which I listen */
+ server_data_t *globals;
+
+ gras_init(&argc,argv);
+
+ globals=gras_userdata_new(server_data_t*);
+ globals->done=0;
+
+ message_declaration();
+ mysock = gras_socket_server(atoi(argv[1]));
+
+ gras_cb_register(gras_msgtype_by_name("convert a2i"),&server_convert_a2i_cb);
+ gras_cb_register(gras_msgtype_by_name("convert i2a"),&server_convert_i2a_cb);
+ gras_cb_register(gras_msgtype_by_name("done"),&server_done_cb);
+
+ while (!globals->done) {
+ gras_msg_handle(-1); /* blocking */
+ }
+
+ gras_exit();
+ return 0;
+}
+
+int client(int argc, char *argv[]) {
+ gras_socket_t mysock; /* socket on which I listen */
+ gras_socket_t toserver; /* socket used to write to the server */
+
+ gras_init(&argc,argv);
+
+ message_declaration();
+ mysock = gras_socket_server_range(1024, 10000, 0, 0);
+
+ VERB1("Client ready; listening on %d", gras_socket_my_port(mysock));
+
+ gras_os_sleep(1.5); /* sleep 1 second and half */
+ toserver = gras_socket_client(argv[1], atoi(argv[2]));
+
+ long long_to_convert=4321;
+ char *string_result;
+ INFO1("Ask to convert %ld", long_to_convert);
+ gras_msg_rpccall(toserver, 60, gras_msgtype_by_name("convert i2a"), &long_to_convert, &string_result);
+ INFO2("The server says that %ld is equal to \"%s\".", long_to_convert, string_result);
+ free(string_result);
+
+ char *string_to_convert="1234";
+ long long_result;
+ INFO1("Ask to convert %s", string_to_convert);
+ gras_msg_rpccall(toserver, 60, gras_msgtype_by_name("convert a2i"), &string_to_convert, &long_result);
+ INFO2("The server says that \"%s\" is equal to %d.", string_to_convert, long_result);
+
+ xbt_ex_t e;
+ string_to_convert = "azerty";
+ TRY {
+ gras_msg_rpccall(toserver, 60, gras_msgtype_by_name("convert a2i"), &string_to_convert, &long_result);
+ } CATCH(e) {
+ INFO1("The server refuses to convert %s. Here is the received exception:",string_to_convert);
+ xbt_ex_display(&e);
+ INFO0("Again, previous exception was excepted");
+ }
+
+ gras_msg_send(toserver,gras_msgtype_by_name("done"), NULL);
+ INFO0("Stopped the server");
+
+ gras_exit();
+ return 0;
+}
--- /dev/null
+$ ./test_server & ./test_client 127.0.0.1
+[blaise:client:(2223) 0.000005] test.c:95: [test/INFO] Ask to convert 4321
+[blaise:client:(2223) 0.001210] test.c:97: [test/INFO] The server says that 4321 is equal to "4321".
+[blaise:client:(2223) 0.001227] test.c:102: [test/INFO] Ask to convert 1234
+[blaise:client:(2223) 0.001327] test.c:104: [test/INFO] The server says that "1234" is equal to 1234.
+[blaise:client:(2223) 0.040861] test.c:111: [test/INFO] The server refuses to convert azerty. Here is the received exception:
+[blaise:client:(2223) 0.040895] xbt/ex.c:229: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
+** SimGrid: UNCAUGHT EXCEPTION received on blaise(2223): category: invalid_arg; value: 0
+** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
+** Thrown by server() in this process
+
+** In server_convert_a2i_cb() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/test.c:48 (static symbol)
+** In gras_msg_handle() at gras/Msg/msg.c:634 (dynamic symbol)
+** In server() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/test.c:72 (static symbol)
+** In main() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/_test_server.c:21 (static symbol)
+** In __libc_start_main() at ??:0 (dynamic symbol)
+** In _start() at ../sysdeps/i386/elf/start.S:122 (static symbol)
+[blaise:client:(2223) 0.040933] test.c:113: [test/INFO] Again, previous exception was excepted
+[blaise:client:(2223) 0.040957] test.c:117: [test/INFO] Stopped the server
+[blaise:client:(2223) 0.040972] gras/gras.c:85: [gras/INFO] Exiting GRAS
+[blaise:server:(2220) 0.000005] test.c:30: [test/INFO] Convert 4321 to string
+[blaise:server:(2220) 0.000045] test.c:32: [test/INFO] 4321 converted to string: 4321
+[blaise:server:(2220) 0.000197] test.c:44: [test/INFO] Convert 1234 to long
+[blaise:server:(2220) 0.000294] test.c:44: [test/INFO] Convert azerty to long
+[blaise:server:(2220) 0.040123] test.c:12: [test/INFO] Server done
+[blaise:server:(2220) 0.040144] gras/gras.c:85: [gras/INFO] Exiting GRAS
+$
+$ killall test_server
+$
+$ ./test_simulator platform.xml test.xml
+[Boivin:client:(2) 0.000000] test.c:95: [test/INFO] Ask to convert 4321
+[Jacquelin:server:(1) 0.000538] test.c:30: [test/INFO] Convert 4321 to string
+[Jacquelin:server:(1) 0.000538] test.c:32: [test/INFO] 4321 converted to string: 4321
+[Boivin:client:(2) 0.001077] test.c:97: [test/INFO] The server says that 4321 is equal to "4321".
+[Boivin:client:(2) 0.001077] test.c:102: [test/INFO] Ask to convert 1234
+[Jacquelin:server:(1) 0.001615] test.c:44: [test/INFO] Convert 1234 to long
+[Boivin:client:(2) 0.002153] test.c:104: [test/INFO] The server says that "1234" is equal to 1234.
+[Jacquelin:server:(1) 0.002692] test.c:44: [test/INFO] Convert azerty to long
+[Boivin:client:(2) 0.003435] test.c:111: [test/INFO] The server refuses to convert azerty. Here is the received exception:
+[Boivin:client:(2) 0.003435] xbt/ex.c:229: [xbt_ex/CRITICAL] Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
+** SimGrid: UNCAUGHT EXCEPTION received on Boivin(2): category: invalid_arg; value: 0
+** Error while converting azerty: this does not seem to be a valid number (problem at 'azerty')
+** Thrown by server() in this process
+
+** In server_convert_a2i_cb() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/test.c:48 (static symbol)
+** In gras_msg_handle() at gras/Msg/msg.c:634 (dynamic symbol)
+** In server() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/test.c:72 (static symbol)
+** In launch_server() at /home/mquinson/CVSIMPORT/gras/gras/doc/gtut-files/_test_simulator.c:51 (static symbol)
+** In __context_exit() at xbt/context.c:116 (dynamic symbol)
+** In start_thread() at ??:0 (dynamic symbol)
+** In clone() at ??:0 (dynamic symbol)
+[Boivin:client:(2) 0.003435] test.c:113: [test/INFO] Again, previous exception was excepted
+[Boivin:client:(2) 0.003972] test.c:117: [test/INFO] Stopped the server
+[Boivin:client:(2) 0.003972] gras/gras.c:85: [gras/INFO] Exiting GRAS
+[Jacquelin:server:(1) 0.003972] test.c:12: [test/INFO] Server done
+[Jacquelin:server:(1) 0.003972] gras/gras.c:85: [gras/INFO] Exiting GRAS
+[0.003972] msg/global.c:479: [msg_kernel/INFO] Congratulations ! Simulation terminated : all processes are over
+$
/**
-@page GRAS_tut_tour_rpc Lesson 10: Remote Procedure Calling (RPCing) (TODO)
+@page GRAS_tut_tour_rpc Lesson 10: Remote Procedure Calling (RPC)
\section GRAS_tut_tour_rpc_toc Table of Contents
- \ref GRAS_tut_tour_rpc_intro
- - \ref GRAS_tut_tour_rpc_use
+ - \ref GRAS_tut_tour_rpc_use
+ - \ref GRAS_tut_tour_rpc_use_declare
+ - \ref GRAS_tut_tour_rpc_use_i2a_cb
+ - \ref GRAS_tut_tour_rpc_use_a2i_cb
+ - \ref GRAS_tut_tour_rpc_use_rest
- \ref GRAS_tut_tour_rpc_recap
<hr>
\section GRAS_tut_tour_rpc_intro Introduction
+So far, we saw how to send messages from one host to another, but quite
+often, we need a two-way message exchange: a "client" sends a request to a
+"server", and the server returns a result after doing some sort of
+computation. This design is often refered to as "Remote Procedure Call" or
+RPC for short.
+
+It is naturally possible to build RPC exchanges using only one-way messages,
+as the ones we used in GRAS so far, but it's a bit awkward. That is why GRAS
+provide a support for RPC, as we will now detail.
\section GRAS_tut_tour_rpc_use Putting rpc into action
+We will build a simple RPC where clients use a remote server to convert
+strings into numbers and vice-versa (ie, changing between "1234" and 1234).
+To achieve its duty, the server will simply use the strtol function in one
+direction. In the other direction, we will use bprintf(). This is a sprintf()
+version allocating the needed room before doing the conversion. Its
+portability is discutable, but SimGrid declares this function when it cannot
+be found on the host architecture, so you can use it peacefully.
+
+\subsection GRAS_tut_tour_rpc_use_declare Declaring the RPC
+
+To declare a RPC message, we should simply use gras_msgtype_declare_rpc().
+Compared to gras_msgtype_declare() that we use to declare one-way messages,
+this function accepts one extra argument: the datatype of the answer
+message. In our example, we accept one string in input, and a long in
+output for the a2i conversion (a=char 2=to i=integer), and the contrary in
+the other direction.
+
+\dontinclude 10-rpc.c
+\skip gras_msgtype_declare_rpc
+\until long
+\until string
+
+\subsection GRAS_tut_tour_rpc_use_i2a_cb Declaring a simple RPC callback: the integer to string conversion
+
+RPC callbacks are very close to "regular" ones. The only difference is that
+they must call gras_msg_rpcreturn() at some point to return their result to
+the caller. This function accepts 3 arguments: First the timeout to use when
+sending back the result (we must use callbacks when doing network
+communication to avoid deadlocks and such issues). The second argument is
+the callback context that the callback got as first argument. It denotes how
+to reach the caller and such. The last argument is a pointer to a variable
+containing the result to pass to the caller.
+
+Having the callee explicitly returning data to the caller allows to free
+data that were allocated to do the job asked by the client, as in this
+example.
+
+\skip server_convert_i2a_cb
+\until end_of_convert_callback
+
+\subsection GRAS_tut_tour_rpc_use_a2i_cb RPC and exceptions: the string to integer conversion
+
+When converting strings into integer, we must deal with the possibility that
+the provided string is not a number. This is done very easily by raising an
+exception in the RPC callback. This exception will get captured by the
+middleware running the callback on the server side, sent accross the network
+to the client side, and revived here. In short, exceptions raised on callee
+side get passed automagically to the caller.
+
+\skip server_convert_a2i_cb
+\until end_of_convert_callback
+
+\subsection GRAS_tut_tour_rpc_use_rest The rest of the story
+
+The rest of the story is not really exciting. The server and the client are
+very classical compared to what we saw so far. We simply have a specific
+message "done" to stop the server when the client is done using it.
+
+This may also be the first time you see the xbt_ex_display() function, which
+allows to display an exception as if it were not catched without killing the
+process.
\section GRAS_tut_tour_rpc_recap Recapping everything together