Network communication between different computers on the Internet is built on the Berkeley Sockets API. In Linux, true to the Unix philosophy that "Everything is a file", open network connections are represented and manipulated using standard integer File Descriptors. Socket programming enables two independent processes running on the same host or on computers separated across the world to communicate over TCP/IP or UDP networks.
Figure 1: Complete execution lifecycle of a TCP Server and Client across connection handshake and payload exchange.
A reliable TCP connection requires a coordinated sequence of kernel system calls on both server and client:
Side |
System Call |
Role & Operation |
|---|---|---|
Server & Client |
|
Creates an unbound network communication endpoint and returns a file descriptor. |
Server |
|
Assigns a local IP address and port number (e.g. |
Server |
|
Puts socket into passive listening mode and defines maximum pending connection backlog queue. |
Server |
|
Blocking call: waits for incoming client handshake and returns a new socket descriptor for data exchange. |
Client |
|
Initiates 3-way TCP SYN/ACK handshake with the server's listening IP and port. |
Both |
|
Transmits and receives byte streams across the established socket file descriptor. |
Both |
|
Terminates the TCP session via 4-way FIN/ACK handshake and releases file descriptors. |
struct sockaddr_in & Byte OrderingInternet addresses are stored in the struct sockaddr_in structure declared in <netinet/in.h>. Because different computer chips represent multibyte numbers differently (Intel/AMD use Little-Endian, while network headers strictly require Big-Endian), programmers must convert values using byte-ordering helper functions:
htons() : Host TO Network Short (converts 16-bit integer port number).ntohs() : Network TO Host Short.inet_pton() : Converts presentation IP string (e.g. "127.0.0.1") to binary network format.struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPv4 Internet protocols
server_addr.sin_port = htons(8080); // Port 8080 converted to Network Byte Order
server_addr.sin_addr.s_addr = INADDR_ANY; // Bind to all available local network interfaces
server.c)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE] = {0};
char response[] = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from Veewom Linux Socket Server!";
// 1. Create socket: AF_INET = IPv4, SOCK_STREAM = TCP
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Optional: Allow instant socket address reuse to prevent "Address already in use" errors
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 2. Configure server address
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// 3. Bind socket to IP & Port
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}
// 4. Put socket in listening mode (max queue of 5 pending connections)
if (listen(server_fd, 5) < 0) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("[SERVER] Listening on port %d... Waiting for incoming client.\n", PORT);
// 5. Accept client connection (Blocks until client arrives)
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);
if (client_fd < 0) {
perror("Accept failed");
close(server_fd);
exit(EXIT_FAILURE);
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
printf("[SERVER] Connection accepted from %s:%d\n", client_ip, ntohs(client_addr.sin_port));
// 6. Read client payload
ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("[SERVER] Received from client: '%s'\n", buffer);
}
// 7. Send response
write(client_fd, response, strlen(response));
printf("[SERVER] Response transmitted. Closing client socket.\n");
// 8. Clean up
close(client_fd);
close(server_fd);
return 0;
}
client.c)#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int sock_fd;
struct sockaddr_in serv_addr;
char message[] = "Client Query: Requesting Linux programming data";
char buffer[BUFFER_SIZE] = {0};
// 1. Create client socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("Socket creation failed");
return 1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Convert IPv4 address from text to binary form (localhost)
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("Invalid address / Address not supported");
return 2;
}
// 2. Connect to server
printf("[CLIENT] Connecting to server at 127.0.0.1:%d...\n", PORT);
if (connect(sock_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
close(sock_fd);
return 3;
}
printf("[CLIENT] Connected! Sending message...\n");
// 3. Write data to server socket
write(sock_fd, message, strlen(message));
// 4. Read response from server
ssize_t bytes_received = read(sock_fd, buffer, sizeof(buffer) - 1);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
printf("[CLIENT] Server response received:\n%s\n", buffer);
}
// 5. Close connection
close(sock_fd);
return 0;
}
# Terminal 1: Compile and run server
$ gcc server.c -o server
$ ./server
# Terminal 2: Compile and run client
$ gcc client.c -o client
$ ./client
Problem: An iterative server can only serve one client at a time. Write a concurrent TCP server that forks a child process for each incoming client to handle multiple simultaneous connections.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
// Automatically reap zombie child processes
void sigchld_handler(int sig) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in serv_addr, cli_addr;
socklen_t sin_size = sizeof(cli_addr);
// Prevent zombie children with SIGCHLD handler
signal(SIGCHLD, sigchld_handler);
server_fd = socket(AF_INET, SOCK_STREAM, 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(9090);
bind(server_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(server_fd, 10);
printf("Concurrent Server listening on port 9090...\n");
while (1) {
client_fd = accept(server_fd, (struct sockaddr*)&cli_addr, &sin_size);
if (client_fd < 0) continue;
// Fork child process to handle connected client
if (fork() == 0) {
// --- CHILD PROCESS ---
close(server_fd); // Child does not need listening socket
char echo_buf[256];
ssize_t n = read(client_fd, echo_buf, sizeof(echo_buf));
write(client_fd, echo_buf, n); // Echo back
close(client_fd);
exit(0); // Child terminates safely
}
// --- PARENT PROCESS ---
close(client_fd); // Parent does not need client socket; continues listening
}
close(server_fd);
return 0;
}
SOCK_STREAM) is connection-oriented, guaranteed, ordered, and error-checked, using handshakes and acknowledgments. UDP (SOCK_DGRAM) is connectionless, fast, and does not guarantee packet arrival or ordering.
TIME_WAIT state for up to two minutes. Setting SO_REUSEADDR with setsockopt() allows the server to bind to the port immediately without waiting.
bind() is called by the server to associate an open socket with a well-known local port and IP address. connect() is called by the client to initiate an active connection with a remote server's address.
htons() stands for "Host TO Network Short". It converts a 16-bit short integer (such as a port number) from the host machine's native CPU byte order into the standardized Big-Endian Network Byte Order.
Understand virtual memory layout, page tables, and memory-mapped files in Class 31: Virtual Memory & mmap().
Continue to Class 31: Linux Virtual Memory & mmap() → Previous: ← Pthreads | Full Syllabus