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
213: Building a Simple TCP Client
We've talked about the theory of sockets, but the real magic happens when you actually see bytes moving across a wire. To keep this concrete, we aren't just going to send "Hello World" to a void. We're going to build a client that connects to a real web server—example.com—and requests the page headers using a basic HTTP HEAD request. It's a great way to see how a TCP handshake leads to an actual application-layer conversation.
Opening the Socket Door
Before we can talk to a server, we need a socket. Think of this as creating the phone handset before you actually dial the number. I'm using AF_INET because we're sticking with IPv4, and SOCK_STREAM because TCP is a stream-oriented protocol. If we wanted UDP, we'd use SOCK_DGRAM, but for a web request, we need the reliability of TCP.
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Could not create socket");
return 1;
}
Targeting Example.com
Now we need to tell C where we're going. We use a sockaddr_in struct. I've decided to hardcode the IP for example.com (93.184.216.34) just to keep this lesson focused on the socket logic rather than getting bogged down in DNS resolution with getaddrinfo, which we can cover in a later session.
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = 80; // Standard HTTP port
server.sin_addr.s_addr = inet_addr("93.184.216.34");
The Byte-Order Blunder
Here is where I almost tripped up—and where you probably will too. I initially tried to connect and it failed miserably. Why? Because I passed the port 80 directly. Computers disagree on "Endianness" (the order in which bytes are stored). Network hardware expects "Big Endian," but your x86 machine is likely "Little Endian."
If you just put 80 in there, the network sees a completely different number. I had to wrap the port in htons() (host-to-network short). It's a small function, but forgetting it is a rite of passage in C networking.
// The fix:
server.sin_port = htons(80);
Establishing the Connection
With our address struct ready, we call connect(). This is where the "Three-Way Handshake" actually happens. The program will hang here for a moment while the OS handles the SYN, SYN-ACK, and ACK packets. If the server is down or the port is blocked, this returns -1.
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) {
perror("Connection failed");
return 1;
}
Asking for the Headers
Since we're talking to a web server, we have to speak HTTP. We'll send a HEAD request. This tells the server, "I want the metadata (headers), but don't bother sending me the whole HTML page." It keeps our buffer small and our output clean.
char *request = "HEAD / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n";
send(sock, request, strlen(request), 0);
char buffer[1024];
int bytes_received = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0'; // Null-terminate the string
printf("Server Response:\n%s\n", buffer);
}
close(sock);
I made sure to null-terminate the buffer manually. recv() doesn't care about C strings; it just dumps raw bytes into your memory. If you try to printf a buffer that isn't null-terminated, you'll end up printing random garbage from your RAM until the program crashes.
📋 Practical Task
Build a TCP Port Checker
Now that you've seen how to connect to a known service, create a small utility program called port_check.c. Your program should take an IP address and a port number as command-line arguments (using argv).
The program should attempt to connect to that specific IP and port. Instead of sending a request, simply report whether the connection was successful or if it failed. This is essentially the core logic of a basic port scanner. Ensure you handle the byte-order conversion for the port provided by the user.
There are no comments for now.