C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
227: Building a Simple Chat Application Over Sockets
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_REUSEADDRsocket 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 atrecv()waiting for a message from your friend, it can't move forward to thesend()call to let your friend know you're typing. Your app will feel frozen.In a professional environment, we'd use
select()orpoll(), 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 callingfgets()andsend()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 toprintfthat 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 therecvcall 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.
There are no comments for now.