Simple multiuser chat for POSIX systems

Here’s a little multiuser chat server written for various POSIX-compatible operating systems. Written and tested on Mac OS X 10.6, but it should work on your favorite Linux, too.

Placed in public domain, use it for whatever you want (since it’s so simple). 177 lines of pure C powah, dood.

Code follows after the break.

server.c:

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SLOTCOUNT 300

int listener = 0;
int slots[SLOTCOUNT] = {0};
int maxslot = 0;
int usage(char* call)
{
  char* fn = strrchr(call, '/')+1;
  if(!fn)
    fn = call;
  printf("usage: %s port\n", fn);
  return 1;
}

void broadcast_message(int sender, char* msg)
{
  int i;
  for(i = 0; i < SLOTCOUNT; i++)
  {
    if(!slots[i])
      continue;
    if(i == sender)
      continue;

    send(i, msg, strlen(msg), 0);
  }

  if(sender != 0)
    printf("%s", msg);
}

int work()
{
  maxslot = listener;
  do
  {
    fd_set set;
    int i;

    FD_ZERO(&set);

    FD_SET(1,        &set);
    FD_SET(listener, &set);
    for(i=0; i < SLOTCOUNT; i++)
    {
      if(slots[i])
      {
        FD_SET(slots[i], &set);
      }
    }

    int koliko = select(maxslot+1, &set, NULL, NULL, NULL);
    if(koliko==-1)
      err(5, "select()");
    
    if(FD_ISSET(1, &set))
    {

      int size;
      if(ioctl(1, FIONREAD, &size) != -1){
        char *buf;

    //    printf("Reading %d bytes...\n", size);
        buf = malloc(size+1);
        read(1, buf, size);
        buf[size] = 0;
    //    printf("Received %s\n", buf);

        broadcast_message(0, buf);

        free(buf);
      }
      else
        err(7, "ioctl() stdio");

    }
    if(FD_ISSET(listener, &set))
    {
      int accepted = accept(listener, NULL, NULL);
      if(accepted == -1)
        err(6, "accept()");

      slots[accepted] = accepted;
      if(accepted > maxslot)
        maxslot = accepted;
    }
  
    for(i = 0; i < SLOTCOUNT; i++)
    {
      if(slots[i] && FD_ISSET(slots[i], &set))
      {
    //    printf("Received on %d\n", i);


        int size;


        if(ioctl(i, FIONREAD, &size) != -1){

          if(size > 0)
          {
            char *buf;

    //        printf("Reading %d bytes...\n", size);
            buf = malloc(size+1);
            read(i, buf, size);
            buf[size] = 0;
    //        printf("Received %s\n", buf);
            
            broadcast_message(i, buf);

            free(buf);
          }
          else
          {
            shutdown(i, SHUT_RDWR);
            close(i);

            slots[i] = 0;

    //        printf("Disconnect on %d\n", i);
          }
        }
        else
          err(8, "ioctl() net");


      }
    }
    
  } while(1);
  return 0;
}


int main(int argc, char** argv)
{
  if(argc < 2)
    return usage(argv[0]);

  if((listener = socket(PF_INET, SOCK_STREAM, 0))==-1)
    err(1, "socket()");
  
  int one=1;
  setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
  
  struct sockaddr_in socketAddress;
  memset(&socketAddress, 0, sizeof(socketAddress));
  socketAddress.sin_len = sizeof(socketAddress);
  socketAddress.sin_family = AF_INET;                   // Address family (IPv4 vs IPv6)
  socketAddress.sin_port = htons(atoi(argv[1]));        // If we passed 0, the actual port would get assigned automatically by kernel and we'd have to retrieve it. Also, we use network byte order for 16bit numbers here by using htons
  socketAddress.sin_addr.s_addr = htonl(INADDR_ANY);    // We must use "network byte order" format (big-endian) for the 32bit value here by using htonl

  printf("Listener: %d\n", listener);
  if(bind(listener, (struct sockaddr*) &socketAddress, sizeof(socketAddress))==-1)
    err(2, "bind()");

  if(listen(listener, 1) == -1)
    err(3, "listen()");

  return work();

}

Makefile:

CFLAGS=-g3
LDFLAGS=-g3

all: server

server: server.o
  gcc server.o $(LDFLAGS) -o server

clean:
  -rm server server.o

Note, in Makefiles, instead of two spaces, use tabs. You need to.

Try it out:

make
./server 1337

And on a few other terminals (or other machines):

telnet localhost 1337

Works over the network, too, of course.

Nota bene, you will probably need to install telnet on your Linux machine. Also, telnet does not come with latest Windows editions (Vista and 7); you will need to use putty over there.

-=-
Tip me with Bitcoin to: 1ASA9q5VQUxPZvit8X2AP4JYzPcSDk7dFV or using ChangeTip (button below)


Leave a Reply

Your email address will not be published. Required fields are marked *