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
214: Building a Simple TCP Server
I remember a developer I mentored a few years back who was building a custom telemetry system for a fleet of small drones. He was trying to use a heavy-duty web framework to collect the coordinates, but the overhead was killing the drones' limited battery life and causing massive latency spikes. We spent a weekend stripping everything away and writing a raw TCP server in C. The moment he saw the data streaming in with sub-millisecond latency, he realized that while high-level libraries are great for websites, sometimes you need to talk directly to the network stack to get the performance you actually need.
When we talk about "sockets" in C, it's helpful to stop thinking about "networking" for a second and start thinking about "files." In Unix-like systems, a socket is essentially a file descriptor. You open it, you write to it, you read from it, and you close it. The only difference is that instead of pointing to a spot on your hard drive, it points to a network interface and a specific port.
Setting Up the Listening Post
To get a server running, you can't just start reading data; you have to prepare the OS to receive connections. First, you call socket() to create the endpoint. You'll use AF_INET for IPv4 and SOCK_STREAM because we want TCP—which ensures our data arrives in order and intact. If you used SOCK_DGRAM, you'd be dealing with UDP, which is faster but doesn't guarantee delivery (not what we want for a stable server).
Once you have the socket, you have to bind() it. This is where you tell the OS, "I want this specific program to own port 8080." You'll wrap your IP and port into a sockaddr_in structure. I've seen plenty of people forget to set sin_family to AF_INET, which leads to some very confusing "Invalid Argument" errors. After binding, you call listen(). This doesn't actually "do" the connecting; it just tells the kernel to start queuing up incoming connection requests in a backlog.
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; // Listen on all interfaces
address.sin_port = htons(8080); // Host-to-Network Short
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
listen(server_fd, 3); // Queue up to 3 pending connections
Handshaking and Data Exchange
Now comes the part where the server actually interacts with a client. The accept() function is a "blocking" call. Your program will literally stop right there and wait until a client tries to connect. When a connection happens, accept() returns a new file descriptor. This is a crucial distinction: the original server_fd stays open to listen for more people, while the new descriptor is your private line to that specific client.
With that private line, you can use read() to get data and write() to send it back. In a real-world scenario, you'd probably wrap this in a loop or use threads to handle multiple clients, but for a simple server, a synchronous read-write cycle is the best way to understand the flow. Once you're done, you must close() the client socket, or you'll leak file descriptors until the OS refuses to let you open any more.
int addrlen = sizeof(address);
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
char buffer[1024] = {0};
read(new_socket, buffer, 1024);
printf("Client said: %s\n", buffer);
char *response = "Message received. System Online.";
write(new_socket, response, strlen(response));
close(new_socket);
close(server_fd);
📋 Practical Task
Build a Remote System-Status Query Server
Your task is to create a TCP server that acts as a basic status monitor. Instead of just echoing back what the client says, the server should listen for a specific "command" and respond with a status report.
Requirements:
- The server must listen on port
9000. - If the client sends the string
"STATUS", the server should respond with"SYSTEM_OK: CPU 12%, MEM 45%". - If the client sends anything else, the server should respond with
"UNKNOWN_COMMAND". - The server should handle one request and then gracefully shut down (close both the client and server sockets).
Testing Tip: You don't need to write a separate C client to test this. Once your server is running, open a terminal and use the nc (netcat) command: nc localhost 9000. Then type "STATUS" and hit enter.
There are no comments for now.