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.