Fundamentals of Network Programming with Socket API
Core Networking Concepts
1.1 Local and Wide Area Networks
A local area network (LAN) connects computers and devices within a limited geographic area, forming a private communication network. In contrast, a wide area network (WAN), also known as the public internet, links multiple LANs or metropolitan networks across regions.
1.2 IP Addressing
An IP address is essentially an integer identifying a device on a network. Two versions exist: IPv4 and IPv6.
-
IPv4
- Uses a 32-bit integer (4 bytes), represented as four 8-bit segments.
- Commonly expressed in dotted-decimal notation:
192.168.10.27. - Each segment ranges from 0 to 255, yielding a total of $2^{32}$ unique addresses.
-
IPv6
- Employs a 128-bit integer (16 bytes), allowing for $2^{128}$ possible addresses.
- Written in hexadecimal format:
2001:0db8:3c4d:0015:0000:0000:1a2b. - Divided into eight 16-bit segments.
1.3 Port Numbers
Ports are numeric identifiers used to direct network traffic to specific processes on a host. They are unsigned 16-bit integers ranging from 0 to 65535. A process must bind to a port to receive network data; multiple processes cannot share the same port.
1.4 OSI Model
The Open Systtems Interconnection (OSI) model is a conceptual framework developed by ISO in 1985 to standardize network communication layers.
Network Protocols
TCP, UDP, IP, and Ethernet form the foundation of modern networking.
Socket Programming Interface
Sockets provide a standardized API for network communication between client and server applications. Key componnets include IP address, port number, and data payload.
3.1 Byte Order
Data representation in memory varies between systems:
- Little-endian (host byte order): Lower-order bytes stored at lower memory addresses (common on x86).
- Big-endian (network byte order): Lower-order bytes stored at higher memory addresses. All socket operations use big-endian format.
For example, the value 0x12345678 is transmitted as 12 34 56 78 over the network.
3.2 Endianness Conversion Functions
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
These functions convert between host and network byte orders.
3.3 IP Address Translation
Convert string representations to binary format:
int inet_pton(int af, const char *src, void *dst);
af: Adress family (AF_INETorAF_INET6).src: Input string like192.168.10.27.dst: Output buffer for the converted 32-bit or 128-bit integer.
Convert binary back to string:
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
Legacy functions for IPv4 only:
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
TCP Communication Flow
TCP is connection-oriented, reliable, and stream-based.
4.1 Server Workflow
int lfd = socket(...); // Create listening socket
bind(lfd, ...); // Bind to IP and port
listen(lfd, backlog); // Start listening
int cfd = accept(lfd, ...); // Accept incoming connection
read(cfd, buf, size); // Receive data
write(cfd, buf, size); // Send data
close(cfd); // Close connection
4.2 Client Workflow
int cfd = socket(...); // Create socket
connect(cfd, ...); // Connect to server
recv(cfd, buf, size, 0); // Receive data
send(cfd, buf, size, 0); // Send data
close(cfd); // Close connection
4.3 Socket Function
int socket(int domain, int type, int protocol);
domain: Protocol family (AF_INET,AF_UNIX, etc.).type: Socket type (SOCK_STREAMfor TCP,SOCK_DGRAMfor UDP).protocol: Usually 0 to use default protocol.
4.4 Binding Addresses
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
4.5 Listening Queue
int listen(int sockfd, int backlog);
Maximum pending connections in queue (typically up to 128).
4.6 Accepting Connections
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
Returns a new file descriptor representing the established connection. The original sockfd remains open for listening.
4.7 Connecting Clients
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Initiates TCP handshake. For UDP, only records server address without sending packets.
4.8 Data Transfer
Use recv() and send() for reliable communication:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- Returns number of bytes transferred, 0 on peer close, -1 on error.
4.9 File Descriptor Roles
- Server uses one listening FD and one per active connection.
- Client uses one FD for all communication.
Example Server Implementation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("Socket creation failed");
exit(-1);
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "192.168.23.139", &server_addr.sin_addr);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_fd);
exit(-1);
}
if (listen(server_fd, 128) < 0) {
perror("Listen failed");
close(server_fd);
exit(-1);
}
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
char ip_str[32];
printf("Client connected: %s:%d\n",
inet_ntop(AF_INET, &client_addr.sin_addr, ip_str, sizeof(ip_str)),
ntohs(client_addr.sin_port));
while (1) {
char buffer[1024];
ssize_t n = recv(client_fd, buffer, sizeof(buffer), 0);
if (n > 0) {
printf("Received: %s\n", buffer);
send(client_fd, buffer, n, 0);
} else if (n == 0) {
break;
} else {
perror("Receive error");
break;
}
}
close(client_fd);
close(server_fd);
return 0;
}
Example Client Implementation
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) {
perror("Socket creation failed");
exit(-1);
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
inet_pton(AF_INET, "192.168.23.139", &server_addr.sin_addr);
if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(client_fd);
exit(-1);
}
char ip_str[32];
printf("Connected to server: %s:%d\n",
inet_ntop(AF_INET, &server_addr.sin_addr, ip_str, sizeof(ip_str)),
ntohs(server_addr.sin_port));
int count = 0;
while (1) {
char message[1024];
snprintf(message, sizeof(message), "Hello World %d\n", count++);
send(client_fd, message, strlen(message), 0);
char response[1024];
ssize_t n = recv(client_fd, response, sizeof(response), 0);
if (n > 0) {
printf("Server: %s\n", response);
} else if (n == 0) {
break;
} else {
perror("Receive error");
break;
}
sleep(1);
}
close(client_fd);
return 0;
}
Concurrent Server Using Threads
#include <pthread.h>
void* handle_client(void* arg) {
struct addrInfo* info = (struct addrInfo*)arg;
char ip[32];
printf("Client: %s:%d\n",
inet_ntop(AF_INET, &info->addr.sin_addr, ip, sizeof(ip)),
ntohs(info->addr.sin_port));
char buffer[1024];
while ((recv(info->cfd, buffer, sizeof(buffer), 0)) > 0) {
printf("Client %d: %s\n", info->number, buffer);
send(info->cfd, buffer, strlen(buffer), 0);
}
close(info->cfd);
free(arg);
return NULL;
}
// Main loop creates thread for each incoming connection
TCP Handshake and Termination
Three-Way Handshake
- SYN: Client sends SYN=1, seq=J.
- SYN+ACK: Server responds with SYN=1, ACK=1, ack=J+1, seq=K.
- ACK: Client confirms with ACK=1, ack=K+1.
Four-Way Termination
- FIN: Client sends FIN=1.
- ACK: Server replies with ACK=1, ack=seq+1.
- FIN: Server sends its own FIN=1.
- ACK: Client acknowledges, waits 2MSL before closing.
Thread Pool Design
A thread pool manages a fixed number of worker threads to handle tasks efficiently:
- Task Queue: Stores pending work items (producer-consumer pattern).
- Worker Threads: Continuously poll the queue for jobs.
- Manager Thread: Adjusts pool size based on load.
Benefits:
- Reduces overhead of frequent thread creation/destruction.
- Enables immediate task execution.
- Prevents system overload during high concurrency.
Common use cases: web servers, real-time systems, bursty request handling.