Network Architecture Models
Data transmission across networks is structured using layered reference models. The theoretical Open Systems Interconnection (OSI) framework defines seven distinct layers: Physical, Data Link, Network, Transport, Session, Presentation, and Application. During transmission, data moves downward, with each lower layer providing services to the layer directly above it. In practical deployments, the TCP/IP four-layer suite is the industry standard:
- Application Layer: Handles high-level protocols and application-specific logic (e.g., HTTP, FTP, SSH, SMTP).
- Transport Layer: Manages end-to-end communication, flow control, and reliability (TCP, UDP).
- Internet (Network) Layer: Responsible for logical addressing, routing decisions, and packet forwarding across disparate networks (IPv4/IPv6, ICMP).
- Link (Network Interface) Layer: Interfaces with hardware drivers and network interface cards, handling frame construction, MAC addressing, and physical bitstream conversion.
Protocol Encapsulation
As application payloads descend through the protocol stack, each layer performs encapsulation. The receiving layer appends its own header (and occasionally a trailer) to the payload from the upper layer. This metadata contains routing information, sequencing parameters, and error-checking values. The encapsulated units then passed to the lower layer until it reaches the physical medium for transmission. Upon receipt, the reverse process (decapsulation) strips each header to deliver the original payload to the target application.
Anatomy of a TCP Segment Header
Transmission Control Protocol (TCP) guarantees reliable, ordered, and error-checked delivery. Its header structure contains critical state management fields:
- Source & Destination Ports: Combined with IP addresses, these 16-bit values uniquely identify a communication session.
- Sequence Number: 32-bit value tracking the first byte of data in the segment.
- Acknowledgment Number: 32-bit value indicating the next expected byte. Valid only when the ACK flag is set.
- Data Offset (Header Length): 4-bit field specifying the header size in 32-bit words (maximum 60 bytes).
- Control Flags:
SYN: Synchronize sequence numbers (connection initiation).ACK: Acknowledgment of received data.FIN: Connection termination.RST: Reset the connection immediately.PSH: Push buffered data to the receiving application.URG: Indicates urgent pointer is active.
- Window Size: 16-bit flow control field specifying the receiver's available buffer capacity (max 65,535 bytes).
- Checksum: Mandatory 16-bit field verifying header and payload integrity. Calculated by the sender and validated by the receiver.
- Urgent Pointer: 16-bit offset marking the end of urgent data.
- Options & Padding: Variable-length fields aligned to 32 bits. The Maximum Segment Size (MSS) is commonly negotiated here to prevent IP fragmentation. Defaults to 536 bytes if omitted.
Connection Lifecycle
Three-Way Handshake (Establishment)
TCP connections follow a strict deterministic state machine to synchronize sequence numbers before data exchange:
- SYN: The client transmits a segment with
SYN=1and an initial sequence numberseq=x. State transitions toSYN_SENT. - SYN-ACK: The server acknowledges the request by replying with
SYN=1andACK=1. It sets its sequence numberseq=yand acknowledgment numberack=x+1. State shifts toSYN_RCVD. - ACK: The client confirms receipt by sending a segment with
ACK=1,seq=x+1, andack=y+1. Both endpoints transition toESTABLISHED, enabling bidirectional data flow. The monotonically increasing sequence numbers ensure segment ordering and packet loss detection.
Four-Way Termination
Because TCP connections are full-duplex, each direction must be closed independently:
- Active FIN: The initiating host sends a segment with
FIN=1and a sequence numberseq=u. It entersFIN_WAIT_1. - Passive ACK: The receiving host replies with
ACK=1,ack=u+1, and moves toCLOSE_WAITwhile draining remaining outbound buffers. - Passive FIN: Once application data is fully transmitted, the receiver sends its own
FIN=1segment withseq=vand entersLAST_ACK. - Final ACK: The initiator acknowledges with
ACK=1,ack=v+1, transitions throughTIME_WAITto absorb delayed packets, and finally reachesCLOSED.
UDP Socket Implemantation Paradigm
User Datagram Protocol (UDP) operates as a connectionless, best-effort datagram service. It omits handshakes, sequencing, and retransmission mechanisms, resulting in lower latency and reduced overhead. This makes it suitable for real-time streaming, DNS queries, or local network environments where application-layer reliability suffices.
Key differences from TCP sockets:
- Socket type:
SOCK_DGRAMreplacesSOCK_STREAM. - Addressing: Functions like
sendto()andrecvfrom()explicitly specify destination and source addresses per packet. - Workflow: Bypasses
listen(),accept(), and explicit connection binding.
Server Implementation (Datagram Handler)
The server binds to a specific port, enters an infinite loop to ingest datagrams, processes the payload (converting text to uppercase), and routes the response back to the originating client address.
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define SERVICE_PORT 8800
#define MAX_PAYLOAD 1024
void transform_to_upper(char *data) {
for (int idx = 0; data[idx] != '\0'; idx++) {
data[idx] = toupper(data[idx]);
}
}
int main(void) {
int udp_listener = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_listener < 0) {
perror("Failed to create datagram socket");
return EXIT_FAILURE;
}
struct sockaddr_in bind_config = {0};
bind_config.sin_family = AF_INET;
bind_config.sin_addr.s_addr = htonl(INADDR_ANY);
bind_config.sin_port = htons(SERVICE_PORT);
if (bind(udp_listener, (struct sockaddr*)&bind_config, sizeof(bind_config)) < 0) {
perror("Port binding failed");
close(udp_listener);
return EXIT_FAILURE;
}
printf("UDP endpoint listening on port %d\n", SERVICE_PORT);
struct sockaddr_in peer_info;
socklen_t addr_size = sizeof(peer_info);
char buffer[MAX_PAYLOAD];
while (1) {
ssize_t rx_count = recvfrom(udp_listener, buffer, sizeof(buffer), 0,
(struct sockaddr*)&peer_info, &addr_size);
if (rx_count <= 0) {
if (rx_count < 0) perror("Datagram reception failed");
continue;
}
buffer[rx_count] = '\0';
printf("Received %zd bytes from %s: %s\n", rx_count,
inet_ntoa(peer_info.sin_addr), buffer);
transform_to_upper(buffer);
ssize_t tx_count = sendto(udp_listener, buffer, strlen(buffer) + 1, 0,
(struct sockaddr*)&peer_info, addr_size);
if (tx_count < 0) {
perror("Datagram transmission failed");
}
}
close(udp_listener);
return 0;
}
Client Implementation (Request/Response)
The client constructs a destination endpoint, dispatches a string payload to the server, and blocks until the echoed response arrives. The response is validated and printed to standard output.
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define TARGET_PORT 8800
#define TARGET_IP "127.0.0.1"
#define RX_BUFFER_SIZE 1024
int main(void) {
int comm_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (comm_fd < 0) {
perror("Socket initialization error");
return EXIT_FAILURE;
}
struct sockaddr_in remote_endpoint = {0};
remote_endpoint.sin_family = AF_INET;
remote_endpoint.sin_port = htons(TARGET_PORT);
remote_endpoint.sin_addr.s_addr = inet_addr(TARGET_IP);
char request_payload[] = "socket communication test";
char response_payload[RX_BUFFER_SIZE];
socklen_t endpoint_len = sizeof(remote_endpoint);
ssize_t dispatch_bytes = sendto(comm_fd, request_payload,
strlen(request_payload) + 1, 0,
(struct sockaddr*)&remote_endpoint,
sizeof(remote_endpoint));
if (dispatch_bytes < 0) {
perror("Data dispatch failed");
close(comm_fd);
return EXIT_FAILURE;
}
printf("Dispatched %zd bytes to %s:%d\n", dispatch_bytes, TARGET_IP, TARGET_PORT);
ssize_t receive_bytes = recvfrom(comm_fd, response_payload,
sizeof(response_payload), 0,
(struct sockaddr*)&remote_endpoint,
&endpoint_len);
if (receive_bytes < 0) {
perror("Data retrieval failed");
} else {
response_payload[receive_bytes] = '\0';
printf("Server response [%zd bytes]: %s\n", receive_bytes, response_payload);
}
close(comm_fd);
return 0;
}