Messaging
After establishing connection between client and server there is requirement to exchange some data. Data should be encapsulated into message in the case of NNSP. Message is represented as message_t structure which is internally nothing more than buffer of bytes of specific size. Following functions are used for creating message and serializing/deserializing data.
message_t* message_create(); int message_destroy(message_t* message); int message_write_uchar (message_t* message, const unsigned char data); int message_write_schar (message_t* message, const signed char data); int message_write_ushort(message_t* message, const unsigned short data); int message_write_sshort(message_t* message, const signed short data); int message_write_ulong (message_t* message, const unsigned long data); int message_write_slong (message_t* message, const signed long data); int message_write_float (message_t* message, const float data); int message_write_double(message_t* message, const double data); int message_write_string(message_t* message, const unsigned char* data, const unsigned long size); unsigned char message_read_uchar (message_t* message); signed char message_read_schar (message_t* message); unsigned short message_read_ushort(message_t* message); signed short message_read_sshort(message_t* message); unsigned long message_read_ulong (message_t* message); signed long message_read_slong (message_t* message); float message_read_float (message_t* message); double message_read_double(message_t* message); unsigned long message_read_string(message_t* message, unsigned char** data); int message_free_string(unsigned char* data);
There is function message_create which returns pointer to message_t context. Allocated memory of the message can be freed by message_destroy function.
Write functions are used for serializing data into message buffer. There is one function for each basic data type.
Read functions are used for deserializing data from message buffer. Each function returns specified data. However there is one exception - function message_read_string returns size of string and address to allocated string is stored into data pointer. Allocated string should be freed by message_free_string function.
Following sample is a demonstration of using messaging functions to write and read data. Note that reading functions have to be called in the same order as writing functions.
message_t* msg = message_create(); // writing message_write_uchar(msg, 89); message_write_double(msg, 594.235); unsigned char* foo = "foo"; message_write_string(msg, foo, strlen(foo)); msg->position = 0; // return to beginning of stream, only for the workings of this example // reading unsigned char value1 = message_read_uchar(msg); // value1 should be 89 double value2 = message_read_double(msg); // value2 should be 594.235 unsigned char* str; unsigned long size = message_read_string(msg, &str); // str should be "foo" and size should be 3 message_free_string(str); message_destroy(msg);
If you need to access buffer of bytes in message directly you can do that by following functions. However this is not recommended when writing common applications.
// writing int message_set_stream(message_t* message, unsigned char* data, unsigned long size); int message_append_stream(message_t* message, unsigned char* data, unsigned long size); int message_clear_stream(message_t* message); // reading = accessing to stream and size attributes of message_t structure defined below typedef struct { unsigned long size; // size of stream in message unsigned char* stream; // buffer of bytes (all data in message) unsigned long position; // do not use, position attribute is only for internal purposes } message_t;
Server side
At server side all message-processing have to be done in callback function server_process. That function is called always when server receive any message. This message is passed as parameter message. Then we can read all data from that message and create responding message if needed and send it to client. Let's see following example.
// process incoming message int server_process(server_t* server, connection_t* connection, message_t* message) { // reading of message unsigned char* data; unsigned long size = message_read_string(message, &data); fprintf(stderr,"%d bytes of incoming data: %s\r\n", size, data); // construction of responding message message_t* response = message_create(); message_write_uchar(response,1); // send the response connection_send(connection, response); message_destroy(response); return 0; }
Source code at lines 4-7 is about reading and printing of incoming message. We suppose that client construct that message only with string variable.
Lines 10-14 create response only with unsigned char set to 1.
At lines 16-17 the responding message is sent by function connection_send and destroyed (memory allocated by responding message is freed). If you need to wait immediately for the next incoming message you can do that by calling connection_recv function which returns a message when it comes (however, this is not recommended for common applications). Declaration of receiving and sending functions follows.
message_t* connection_recv(connection_t* connection) int connection_send(connection_t* connection, message_t* message);
Receiving function takes one parameter - pointer to connection_t and it returns message_t when it comes in. Sending function takes two parameters - pointer to connection_t and message which will be send.
Client side
client is an initiator of communication, thus we have to implement as construction of messages as processing of responses from server. Two callback functions have been designed for this purposes - client_control and client_process functions. If we want to send some query to server, we have to create it in client_control function. We put source code for grabbing data from console input into infinite loop.
// controlling of client int client_control(client_t* client) { // infinite loop while(1) { fprintf(stderr,"enter data: "); char buffer[256] = {0}; scanf("%s",&buffer); // query creation message_t* query = message_create(); message_write_string(message,(unsigned char*)query,strlen(query)); // send message and destroy it connection_send(client->connection, query); message_destroy(query); } return 0; }
Lines 7-9 grab data from console input and store it into buffer.
Lines 11-13 construct query message with specified format. Only string variable is put into query message in this case.
Finally, lines 16-17 send message to server and destroy it. Connection info needed for connection_send function is stored in client context as client->connection.
In previous code we have implemented sending of query messages. Now we need to catch response from server and read it. When any message is comming from server, function client_process is called. When we implemented message processing at server side we send a message with unsigned char variable back to client. Now we have to read it.
// process incoming message int client_process(client_t* client, connection_t* connection, message_t* message) { int data = message_read_uchar(message); if(data == 1) fprintf(stderr,"server confirmed receiving of message\r\n"); return 0; }
Line 4 read unsigned char variable from incoming message and lines 5-6 only check its value and print info about it.