This episode opens up network programming with sockets: the differences between TCP and UDP, the socket API from socket, bind, listen, accept, connect, send, and recv, building a simple client-server, as well as network byte order, socket addresses, and error handling.

C is the language that gave birth to the internet: the TCP/IP stack, DNS resolvers, and the first HTTP servers were all written in C. Episode 12 takes you to that level by studying socket programming — how programs talk to each other over a network.
We'll cover the two main protocols: TCP, reliable and ordered for data transfer, and UDP, fast and lightweight for real-time applications. The socket API you'll learn will look familiar because it became the foundation of other languages and frameworks.
By the end of the episode, you'll build a real TCP server and client, complete with network byte order and error handling — core skills for episodes 13 and 14.
TCP provides a reliable connection: data arrives in order, is not lost, and has an acknowledgment mechanism. UDP only sends datagrams with no delivery guarantee. Choose TCP for HTTP, databases, and file transfer. Choose UDP for streaming, gaming, and DNS where speed matters more than delivery guarantees.
The TCP socket flow on the server side: socket creates the socket, bind binds it to an address and port, listen starts listening, accept accepts a connection, then send and recv exchange data. The client side is shorter: socket, connect, then send and recv.
A TCP server starts by creating a socket and binding it to an address:
cat > server.c <<'EOF'
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
return 1;
}
if (listen(fd, 8) < 0) {
perror("listen");
return 1;
}
printf("server mendengarkan port 8080\n");
close(fd);
return 0;
}
EOF
gcc -Wall -Wextra server.c -o server && ./serverThe call socket(AF_INET, SOCK_STREAM, 0) creates an IPv4 socket based on TCP. htons(8080) converts the port from host byte order to network byte order (big-endian), and INADDR_ANY means listening on all interfaces. listen(fd, 8) sets the length of the waiting connection queue.
After listen, the server calls accept to receive incoming connections, and the client calls connect:
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
return 1;
}
close(fd);
return 0;
}
EOF
gcc -Wall -Wextra client.c -o clientinet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) converts an address text into binary. This function is safer than inet_addr because it can check the validity of the format. For host names like localhost, use getaddrinfo, which handles DNS and IPv6 at once.
A complete server accepts a connection and exchanges messages. Note that the server uses the address from accept, not from a static variable:
int klien = accept(fd, (struct sockaddr *)NULL, NULL);
if (klien < 0) {
perror("accept");
return 1;
}
char buffer[256];
ssize_t n = recv(klien, buffer, sizeof(buffer) - 1, 0);
if (n > 0) {
buffer[n] = '\0';
printf("diterima: %s\n", buffer);
}
close(klien);
close(fd);accept(fd, (struct sockaddr *)NULL, NULL) blocks until an incoming connection arrives and returns a new socket dedicated to that connection. recv returns the number of bytes read, -1 on error, and 0 when the peer closes the connection.
To test the server without writing a client, use nc in another terminal:
nc 127.0.0.1 8080The command nc 127.0.0.1 8080 opens a TCP connection to the server and sends whatever you type. This is a real example of using external tools to validate a program — a habit that proves very useful in episode 22.
Computer architectures store numbers in different byte orders. Because the network uses big-endian, all ports and addresses passing through a socket must be converted. Four helper functions: htons, htonl, ntohs, ntohl. A simple rule: host-to-network when sending, network-to-host when receiving.
Every socket call can fail: socket runs out of descriptors, bind finds the port already in use, connect refuses the connection, and recv is interrupted. Check the return value of every call and use perror for clear messages:
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind gagal");
return 1;
}The pattern if (bind(...) < 0) { perror(...); return 1; } catches errors early and leaves a clear trail. Opened sockets must be closed with close on every path, including error paths.
Warning
The example server above serves only one connection because accept is called once. To serve many clients at once, you need a loop or multithreading — precisely the topic of episode 16, and replacing the blocking accept with select or poll in episode 18.
Key takeaways:
In the next episode 13 we will discuss security and safe coding — buffer overflow, format string vulnerabilities, and their mitigations, secure coding with bounds checking as well as strncpy and snprintf, the differences between stack and heap overflow, up to input validation and defensive programming.