Skip to Content
Course content

227: Building a Simple Chat Application Over Sockets

Click on the "Edit" button in the top corner of the screen to edit your slide content.

How do I actually set up the server so it's ready to accept a connection?

When you're building a chat app, the server is basically just a listener. It doesn't "do" anything until a client knocks on the door. You've probably seen the basic socket functions, but the sequence is what usually trips people up. You have to create the socket, bind it to a port, tell the OS you're listening, and then wait.

I usually write the setup like this to keep it clean. Notice the sockaddr_in struct—that's where you define the "address" the server lives at. For a local chat app, we just use INADDR_ANY so it listens on all available interfaces.

int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(8080); // Using port 8080

// This is where the server "claims" the port
bind(server_fd, (struct sockaddr *)&address, sizeof(address));

// Tell the OS to queue up incoming connections
listen(server_fd, 3); 

// This blocks the program until someone actually connects
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);

One thing I'll warn you about: if you stop your server and try to restart it immediately, you might get a "Address already in use" error. That's the OS holding onto the port for a minute. You can fix that by setting the SO_REUSEADDR socket option, but for now, just wait a few seconds between restarts.

I'm stuck on the "blocking" problem—how do I send and receive messages at the same time?

This is the "aha!" moment of socket programming. By default, recv() is a blocking call. If your code is sitting at recv() waiting for a message from your friend, it can't move forward to the send() call to let your friend know you're typing. Your app will feel frozen.

In a professional environment, we'd use select() or poll(), but since we're building a simple chat app, the most intuitive way is to use threads. I recommend creating one thread specifically for receiving messages and leaving the main thread for sending them. It keeps the logic separate and clean.

void *receive_messages(void *socket_ptr) {
    int sock = *(int*)socket_ptr;
    char buffer[1024];
    while (1) {
        int valread = recv(sock, buffer, 1024, 0);
        if (valread <= 0) break; // Connection closed
        buffer[valread] = '\0'; 
        printf("\nFriend: %s\nMe: ", buffer);
        fflush(stdout);
    }
    return NULL;
}

// In your main function:
pthread_t recv_thread;
pthread_create(&recv_thread, NULL, receive_messages, &new_socket);

Now your program can sit in a while(1) loop calling fgets() and send() in the main thread, while the background thread silently waits for incoming data.

How do I make sure the messages are clean and not full of random memory garbage?

Sockets deal in raw bytes, not C-strings. This is a huge point of failure for beginners. When you call recv(), it doesn't add a \0 (null terminator) to the end of the data it puts in your buffer. If you try to printf that buffer, C will just keep reading memory until it happens to hit a zero, resulting in a bunch of weird symbols or a segmentation fault.

The trick is to use the return value of recv(). It tells you exactly how many bytes were actually read. You manually place the null terminator at that exact index.

char buffer[1024];
int bytes_received = recv(sock, buffer, 1023, 0); // Read 1023 to leave room for \0

if (bytes_received > 0) {
    buffer[bytes_received] = '\0'; // Manually terminate the string
    printf("Message received: %s\n", buffer);
}

Also, I always suggest clearing the buffer with memset(buffer, 0, sizeof(buffer)) before the recv call if you're paranoid. It's not strictly necessary if you terminate the string correctly, but it saves you from a lot of debugging headaches when you're staring at a screen full of gibberish.




📋 Practical Task

Build a Multi-Client Broadcast Relay Server

Up until now, we've discussed a one-to-one connection. Your task is to evolve the server into a "Relay Server." Instead of just talking to one client, your server should:

  • Accept multiple client connections (use an array of socket descriptors to keep track of them).
  • When the server receives a message from one client, it must loop through the array of all connected sockets and send() that message to everyone except the original sender.
  • Handle a basic "disconnect" scenario where the server removes a socket from the array if recv() returns 0.

Constraint: Use pthread_create to spawn a new handler thread for every client that connects, so the server can manage multiple conversations simultaneously.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.