Saturday, August 11, 2007

Chat program in C

Here is a proof of concept chat program written in C.
Beeing written in C, the code brings a higher level of understanding
to the programmer about the underlying functionality of network programming.

Download the code here

GitHub repo has been created to host this project: http://github.com/dtolj/simple-chat-client-server

This is the server code:

Server accepts parameters "chat" or "chat -p [PORTNUM]"
In case of only one parameter we will use MYPORT which is 7400



/*Server==================================================*/
if(argc==1 || argc == 3){
if(argc==3){
if(!strcmp("-p",argv[1])){
sscanf(argv[2],"%i",&port);
}else{
printf("Invalid parameter.\nUsage: chat [-p PORT] HOSTNAME\n");
exit(0);
}
}else port=MYPORT;

printf("\n*** Server program starting (enter \"quit\" to stop): \n");
fflush(stdout);

Here we are creating a socket for the server which is defined by this function: int socket(int domain, int type, int protocol);
Next we populate the server_address instance of sockadd_in structure
Finally bind the port to the servers file descriptor.
A note about file descriptors, fd0=stdin, fd1=stdout, fd2=stderr. These are preserved and therefore the next available fd is 3 for listening socket and the first client connected to the server will receive a socket fd 4.


/* Create and name a socket for the server */
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(port);
bind(server_sockfd, (struct sockaddr *)&server_address, addresslen);

int listen(int sockfd, int backlog);
We are now listening for incoming connections on that fd.
We zero out the readfds set and assign the server_sockfd to it as well as the stdin aka fd0


/* Create a connection queue and initialize a file descriptor set */
listen(server_sockfd, 1);
FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);
FD_SET(0, &readfds); /* Add keyboard to file descriptor set */

//struct clientinfo c;

//printf("sizeof(readfds)/(testfds): %d/%d\n",sizeof(readfds),sizeof(testfds));
//printf("Number of bits in readfds/testfds: %d/%d\n", sizeof(readfds)*8,sizeof(testfds)*8);

Assign readfds to testfds, we are now working with testfds.
select() allows for monitoring several sockets simultaniously.
The set currently contains 2 fds 0 and 3


/* Now wait for clients and requests */
while (1) {
testfds = readfds;
select(FD_SETSIZE, &testfds, NULL, NULL, NULL);

/* If there is activity, find which descriptor it's on using FD_ISSET */
for (fd = 0; fd < FD_SETSIZE; fd++) {
if (FD_ISSET(fd, &testfds)) {

if (fd == server_sockfd) { /* Accept a new connection request */
client_sockfd = accept(server_sockfd, NULL, NULL);
/*printf("client_sockfd: %d\n",client_sockfd);*/


if (num_clients < MAX_CLIENTS) {
FD_SET(client_sockfd, &readfds);
fd_array[num_clients]=client_sockfd;
/*Client ID*/
printf("Client %d joined\n",num_clients++);
fflush(stdout);

sprintf(msg,"M%2d",client_sockfd);
/*write 2 byte clientID */
send(client_sockfd,msg,strlen(msg),0);
}
else {
sprintf(msg, "XSorry, too many clients. Try again later.\n");
write(client_sockfd, msg, strlen(msg));
close(client_sockfd);
}
}
else if (fd == 0) { /* Process keyboard activity */
fgets(kb_msg, MSG_SIZE + 1, stdin);
//printf("%s\n",kb_msg);
if (strcmp(kb_msg, "quit\n")==0) {
sprintf(msg, "XServer is shutting down.\n");
for (i = 0; i < num_clients ; i++) {
write(fd_array[i], msg, strlen(msg));
close(fd_array[i]);
}
close(server_sockfd);
exit(0);
}
else {
//printf("server - send\n");
sprintf(msg, "M%s", kb_msg);
for (i = 0; i < num_clients ; i++)
write(fd_array[i], msg, strlen(msg));
}
}
else if(fd) { /*Process Client specific activity*/
//printf("server - read\n");
//read data from open socket
result = read(fd, msg, MSG_SIZE);

if(result==-1) perror("read()");
else if(result>0){
/*read 2 bytes client id*/
sprintf(kb_msg,"M%2d",fd);
msg[result]='\0';

/*concatinate the client id with the client's message*/
strcat(kb_msg,msg+1);

/*print to other clients*/
for(i=0;i < num_clients;i++){
if (fd_array[i] != fd) /*dont write msg to same client*/
write(fd_array[i],kb_msg,strlen(kb_msg));
}
/*print to server*/
printf("%s",kb_msg+1);

/*Exit Client*/
if(msg[0] == 'X'){
exitClient(fd,&readfds, fd_array,&num_clients);
}
}
}
else { /* A client is leaving */
exitClient(fd,&readfds, fd_array,&num_clients);
}//if
}//if
}//for
}//while
}//end Server code


This is client code:

Client accepts parameters "chat [hostname]" or "chat -p [portnum] [hostname]"
In case of only two parameter we will use MYPORT which is 7400


/*Client variables=======================*/
int sockfd;
int result;
char hostname[MSG_SIZE];
struct hostent *hostinfo;
struct sockaddr_in address;
char alias[MSG_SIZE];
int clientid;
/*Client==================================================*/
if(argc==2 || argc==4){
if(!strcmp("-p",argv[1])){
if(argc==2){
printf("Invalid parameters.\nUsage: chat [-p PORT] HOSTNAME\n");
exit(0);
}else{
sscanf(argv[2],"%i",&port);
strcpy(hostname,argv[3]);
}
}else{
port=MYPORT;
strcpy(hostname,argv[1]);
}
printf("\n*** Client program starting (enter \"quit\" to stop): \n");
fflush(stdout);

/* Create a socket for the client */
sockfd = socket(AF_INET, SOCK_STREAM, 0);

/* Name the socket, as agreed with the server */
hostinfo = gethostbyname(hostname); /* look for host's name */
address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;
address.sin_family = AF_INET;
address.sin_port = htons(port);

/* Connect the socket to the server's socket */
if(connect(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0){
perror("connecting");
exit(1);
}

fflush(stdout);

FD_ZERO(&clientfds);
FD_SET(sockfd,&clientfds);
FD_SET(0,&clientfds);//stdin

/* Now wait for messages from the server */
while (1) {
testfds=clientfds;
select(FD_SETSIZE,&testfds,NULL,NULL,NULL);

for(fd=0;fd if(FD_ISSET(fd,&testfds)){
if(fd==sockfd){ /*Accept data from open socket */
//printf("client - read\n");

//read data from open socket
result = read(sockfd, msg, MSG_SIZE);
msg[result] = '\0'; /* Terminate string with null */
printf("%s", msg +1);

if (msg[0] == 'X') {
close(sockfd);
exit(0);
}
}
else if(fd == 0){ /*process keyboard activiy*/
// printf("client - send\n");

fgets(kb_msg, MSG_SIZE+1, stdin);
//printf("%s\n",kb_msg);
if (strcmp(kb_msg, "quit\n")==0) {
sprintf(msg, "XClient is shutting down.\n");
write(sockfd, msg, strlen(msg));
close(sockfd); //close the current client
exit(0); //end program
}
else {
/* sprintf(kb_msg,"%s",alias);
msg[result]='\0';
strcat(kb_msg,msg+1);*/

sprintf(msg, "M%s", kb_msg);
write(sockfd, msg, strlen(msg));
}
}
}
}
}
}// end client code