Chatting
This tutorial will guide you on how to implement simple client and server for chatting purposes. You should have know the basics about building-up client-server architecture using NNSP and about message processing and its sending.
Client side
Implementing client is very easy. All you need is to send a message to server and to print incoming message from server. First of all we create basic source code for user actions into client_control function.
.// controlling of client int client_control(client_t* client) { // buffer for nickname char nickname[16] = {0}; int logged = 0; // infinite loop while(1) { // check flag if(logged == 1) { // menu fprintf(stderr,"1) sent message\r\n"); fprintf(stderr,"2) logout\r\n"); int item = 0; scanf("%d",&item); getchar(); // send message if(item == 1) { fprintf(stderr,"enter message: "); char buffer[256] = {0}; scanf("%s",&buffer); } // logout else if(item == 2) { break; } } else { fprintf(stderr,"enter your nickname to login: "); scanf("%s",&nickname); // set flag logged = 1; } } }
Client is asked to write his nickname after program execution (when he is not logged-in yet). Once he writes his nickname he is considered as logged (flag logged is set) and he can write messages or log himself out.
Because of better specification of messages we define 3 message-types: MSG_CHAT, MSG_LOGIN and MSG_LOGOUT and we fill our code with NNSP functions for creation and sending messages.
enum message_types { MSG_CHAT, MSG_LOGIN, MSG_LOGOUT }; // controlling of client int client_control(client_t* client) { // buffer for nickname char nickname[16] = {0}; int logged = 0; // infinite loop while(1) { // check flag if(logged == 1) { // menu fprintf(stderr,"1) sent message\r\n"); fprintf(stderr,"2) logout\r\n"); int item = 0; scanf("%d",&item); getchar(); // send message if(item == 1) { fprintf(stderr,"enter message: "); char buffer[256] = {0}; scanf("%s",&buffer); // chat message message_t* message = message_create(); message_write_uchar(message, MSG_CHAT); message_write_string(message,(unsigned char*)nickname,strlen(nickname)); message_write_string(message,(unsigned char*)buffer,strlen(buffer)); connection_send(client->connection, message); message_destroy(message); } // logout else if(item == 2) { // logout message message_t* message = message_create(); message_write_uchar(message, MSG_LOGOUT); message_write_string(message,(unsigned char*)nickname,strlen(nickname)); connection_send(client->connection, message); message_destroy(message); break; } } else { fprintf(stderr,"enter your nickname to login: "); scanf("%s",&nickname); // login message message_t* message = message_create(); message_write_uchar(message, MSG_LOGIN); message_write_string(message,(unsigned char*)nickname,strlen(nickname)); connection_send(client->connection, message); message_destroy(message); // set flag logged = 1; } } }
As can be seen, at line 1 we defined enum with all message-types and every user action (login, logout, send message) creates outgoing message and sends it.
At lines 31-38 message for chat purposes with three variables is create - unsigned char which defines a type of message MSG_CHAT, string which defines nickname of sender and string again for chat message.
At lines 44-50 a message for logout purposes with two variables is create - unsigned char which defines a type of message MSG_LOGOUT and string which defines choosen nickname.
At lines 60-66 a message for login purposes with two variables is create - unsigned char which defines a type of message MSG_LOGIN and string which defines choosen nickname.
When client sends a message to server it has to process it and send it to all other connected clients in order to visible message for everyone. It means that our client also has to catch some messages and read it. This has to be done in client_process function. First of all we create basic source code for getting message-type.
// process incoming message int client_process(client_t* client, connection_t* connection, message_t* message) { char msg_type = message_read_uchar(message); // login process if(msg_type == MSG_LOGIN) { } // text process else if(msg_type == MSG_CHAT) { } // logout process else if(msg_type == MSG_LOGOUT) { } return 0; }
At line 4 the message-type as unsigned char variable is read.
Lines 6-22 create basic classification according message-type.
Next step is to process each type of message seperately.
// process incoming message int client_process(client_t* client, connection_t* connection, message_t* message) { char msg_type = message_read_uchar(message); // login process if(msg_type == MSG_LOGIN) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); fprintf(stderr,"%s has been logged in\r\n", nickname); } // text process else if(msg_type == MSG_CHAT) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); // get data unsigned char* data; unsigned long size = message_read_string(message, &data); fprintf(stderr,"%s: %s\r\n\r\n",nickname,data); } // logout process else if(msg_type == MSG_LOGOUT) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); fprintf(stderr,"%s has been logged out\r\n", nickname); } return 0; }
As can bee seen, reading variables from message is done in the same order in which the message has been created by client before. Message of type MSG_LOGIN carries only string variable with nickname as in the case of message of type MSG_LOGOUT. Message of type MSG_CHAT carries two string variables - first with nickname and second with the message-text itself.
Server side
Purpose of server is to distribute incoming messages to all clients. Function server_control must not perform any special task, thus it can only be defined in following way.
// controlling of server int server_control(server_t* server) { fprintf(stderr, "type 'q' to stop server\r\n"); // infinite loop while(1) { if(getchar() == 'q') break; } return 0; }
The most important source code will be implemented in server_process function where every message will be distributed to other clients. Again, we prepare basic structure for message-type classification.
enum message_types { MSG_CHAT, MSG_LOGIN, MSG_LOGOUT }; // process incoming message int server_process(server_t* server, connection_t* connection, message_t* message) { char msg_type = message_read_uchar(message); // login process if(msg_type == MSG_LOGIN) { } // text process else if(msg_type == MSG_CHAT) { } else if(msg_type == MSG_LOGOUT) { } return 0; }
At line 1 we define types of messages as in the case of client side.
Line 6 gets message-type from message and the rest of the code is a preparation for the next code.
enum message_types { MSG_CHAT, MSG_LOGIN, MSG_LOGOUT }; // process incoming message int server_process(server_t* server, connection_t* connection, message_t* message) { char msg_type = message_read_uchar(message); // login process if(msg_type == MSG_LOGIN) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); fprintf(stderr,"%s has been logged in\r\n", nickname); // get all connected clients size_t i; for(i=0; i<vector_length(server->connections); i++) { // get connection and send message connection_t* con; vector_get(server->connections,i,&con); connection_send(con, message); } } // text process else if(msg_type == MSG_CHAT) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); // get data unsigned char* data; unsigned long size = message_read_string(message, &data); fprintf(stderr,"%s: %s\r\n\r\n",nickname,data); // get all connected clients size_t i; for(i=0; i<vector_length(server->connections); i++) { // get connection and send message connection_t* con; vector_get(server->connections,i,&con); connection_send(con, message); } } else if(msg_type == MSG_LOGOUT) { // get nickname unsigned char* nickname; unsigned long nickname_size = message_read_string(message, &nickname); fprintf(stderr,"%s has been logged out\r\n", nickname); // get all connected clients size_t i; for(i=0; i<vector_length(server->connections); i++) { // get connection and send message connection_t* con; vector_get(server->connections,i,&con); connection_send(con, message); } } return 0; }
Lines 10-14, 29-37 and 51-55 get data from message based on message-type.
Lines 16-24, 39-47 and 57-65 are doing the same thing. It accesses to dynamic array of connections, gets every connection from that array and sends message to it. Dynamic array is implemented here as vector_t structure. Pointer to that structure is part of server_t context which enters server_process function as the first parameter. Declarations of vector functions follows.
void vector_init(vector_t*, size_t, size_t, void (*free_func)(void*)); void vector_dispose(vector_t*); void vector_copy(vector_t*, vector_t*); void vector_insert(vector_t*, void*, size_t index); void vector_insert_at(vector_t*, void *, size_t index); void vector_push(vector_t*, void*); void vector_pop(vector_t*, void*); void vector_shift(vector_t*, void*); void vector_unshift(vector_t*, void*); void vector_get(vector_t*, size_t, void*); void vector_remove(vector_t*, size_t); void vector_transpose(vector_t*, size_t, size_t); size_t vector_length(vector_t*); size_t vector_size(vector_t*); void vector_get_all(vector_t*, void*); void vector_cmp_all(vector_t*, void*, int (*cmp_func)(const void*, const void*)); void vector_qsort(vector_t*, int (*cmp_func)(const void*, const void*)); static void vector_grow(vector_t*, size_t); static void vector_swap(void*, void*, size_t);
After compiling and executing program you should have simple but fully functional client-server architecture for chat purposes.