Skip to content

Socket Programming

C and C++ socket programming quick reference from networking basics to robust concurrent servers.

Sockets are operating-system handles used to exchange bytes between processes. Most network programs follow the same shape:

Server: socket -> bind -> listen -> accept -> recv/send -> close
Client: socket -> connect ------------> send/recv -> close
UDP: socket -> bind (server) -> recvfrom/sendto -> close

This page uses the POSIX/BSD sockets API on Linux and Unix-like systems. The same ideas apply to Windows Winsock; portability notes are included near the end.


LayerExamplesWhat Your Program Sees
ApplicationHTTP, DNS, your protocolMessages and rules you define
TransportTCP, UDPPorts and byte delivery behavior
NetworkIPv4, IPv6Source and destination addresses
LinkEthernet, Wi-Fi, loopbackUsually handled by the OS
TermMeaning
IP addressIdentifies a network interface, such as 127.0.0.1 or ::1
PortIdentifies an application endpoint, from 0 to 65535
EndpointAddress plus port, such as 127.0.0.1:8080
Listening socketServer socket waiting for new TCP connections
Connected socketOne TCP conversation between two endpoints
LoopbackLocal host only: 127.0.0.1 for IPv4, ::1 for IPv6
Wildcard bindAccept traffic for all local interfaces: 0.0.0.0 or ::
BacklogQueue size hint for pending TCP connections
File descriptor (fd)Integer handle returned by POSIX socket calls
FeatureTCP (SOCK_STREAM)UDP (SOCK_DGRAM)
ConnectionRequiredNo handshake required
DeliveryReliable, ordered byte streamBest-effort datagrams
BoundariesNot preservedOne send corresponds to one datagram
Flow/congestion controlBuilt inApplication must cope
Typical useHTTP, SSH, database clientsDNS, telemetry, games, discovery
Key traprecv() may return only part of a messagePackets may be dropped, repeated, or reordered

#include <arpa/inet.h> // inet_pton, inet_ntop, htons, ntohs
#include <errno.h> // errno
#include <fcntl.h> // fcntl, O_NONBLOCK
#include <netdb.h> // getaddrinfo, freeaddrinfo, gai_strerror
#include <netinet/in.h> // sockaddr_in, sockaddr_in6
#include <poll.h> // poll
#include <signal.h> // signal, SIGPIPE
#include <stdint.h> // uint16_t, uint32_t
#include <stdio.h> // perror, fprintf
#include <stdlib.h> // exit
#include <string.h> // memset, strlen
#include <sys/socket.h> // socket, bind, listen, accept, connect
#include <sys/time.h> // timeval
#include <sys/types.h> // POSIX types
#include <unistd.h> // close
int fd; // Socket descriptor
ssize_t count; // Return type for send/recv
socklen_t addr_len; // Address structure length
struct sockaddr_storage addr; // Large enough for IPv4 or IPv6
Terminal window
cc -std=c17 -Wall -Wextra -Wpedantic -O2 server.c -o server
c++ -std=c++20 -Wall -Wextra -Wpedantic -O2 client.cpp -o client

On Unix-like systems, sockets are normally part of the standard C library. Windows programs link with Ws2_32.lib.


Integers sent on a network use network byte order (big-endian). Convert port numbers and binary integer fields at API boundaries.

uint16_t network_port = htons(8080); // Host to network, 16-bit
uint16_t host_port = ntohs(network_port);
uint32_t network_value = htonl(value); // Host to network, 32-bit
uint32_t host_value = ntohl(network_value);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
// Bind only to local machine:
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
// Or bind on every IPv4 interface:
addr.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in6 addr6 = {0};
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(8080);
addr6.sin6_addr = in6addr_any; // All local IPv6 interfaces
void print_peer(const struct sockaddr *sa) {
char ip[INET6_ADDRSTRLEN];
unsigned short port;
if (sa->sa_family == AF_INET) {
const struct sockaddr_in *v4 = (const struct sockaddr_in *)sa;
inet_ntop(AF_INET, &v4->sin_addr, ip, sizeof ip);
port = ntohs(v4->sin_port);
} else {
const struct sockaddr_in6 *v6 = (const struct sockaddr_in6 *)sa;
inet_ntop(AF_INET6, &v6->sin6_addr, ip, sizeof ip);
port = ntohs(v6->sin6_port);
}
printf("peer=%s port=%hu\n", ip, port);
}

CallPurposeTypical Failure Checks
socket(domain, type, protocol)Create a socketReturns -1
setsockopt(fd, ...)Configure optionsReturns -1
bind(fd, address, length)Choose local address/portAddress in use, permission denied
listen(fd, backlog)Mark TCP socket as passiveReturns -1
accept(fd, ...)Produce a new TCP client socketRetry on EINTR
connect(fd, ...)Start TCP connectionConnection refused/timeout
send(fd, data, len, flags)Send connected bytesPartial write or -1
recv(fd, data, len, flags)Receive connected bytes0 means peer closed cleanly
sendto(...) / recvfrom(...)UDP send/receiveDatagrams may be truncated/lost
shutdown(fd, how)Close one or both directionsUseful for protocol endings
close(fd)Release descriptorAlways do this when finished

Always check return values. Socket I/O is not guaranteed to complete an entire logical request in one call.


This single-client server listens on 127.0.0.1:8080, echoes received bytes, and demonstrates the basic lifecycle.

tcp_server.c
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
static void die(const char *message) {
perror(message);
exit(EXIT_FAILURE);
}
static int send_all(int fd, const void *buffer, size_t length) {
const char *p = buffer;
size_t sent = 0;
while (sent < length) {
ssize_t n = send(fd, p + sent, length - sent, 0);
if (n > 0) {
sent += (size_t)n;
} else if (n < 0 && errno == EINTR) {
continue;
} else {
return -1;
}
}
return 0;
}
int main(void) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) die("socket");
int yes = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0)
die("setsockopt");
struct sockaddr_in server = {0};
server.sin_family = AF_INET;
server.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) != 1) {
fputs("Invalid listen address\n", stderr);
close(server_fd);
return EXIT_FAILURE;
}
if (bind(server_fd, (struct sockaddr *)&server, sizeof server) < 0)
die("bind");
if (listen(server_fd, 16) < 0)
die("listen");
puts("Listening on 127.0.0.1:8080");
struct sockaddr_in peer;
socklen_t peer_len = sizeof peer;
int client_fd = accept(server_fd, (struct sockaddr *)&peer, &peer_len);
if (client_fd < 0) die("accept");
char buffer[4096];
for (;;) {
ssize_t n = recv(client_fd, buffer, sizeof buffer, 0);
if (n > 0) {
if (send_all(client_fd, buffer, (size_t)n) < 0)
die("send");
} else if (n == 0) {
puts("Client disconnected");
break;
} else if (errno != EINTR) {
die("recv");
}
}
close(client_fd);
close(server_fd);
return 0;
}
Terminal window
cc -std=c17 -Wall -Wextra -O2 tcp_server.c -o tcp_server
./tcp_server

From another terminal:

Terminal window
nc 127.0.0.1 8080

6. TCP Client with DNS and IPv4/IPv6 Support

Section titled “6. TCP Client with DNS and IPv4/IPv6 Support”

Prefer getaddrinfo() over hard-coding address structures. It handles hostnames, IPv4, IPv6, and service/port conversion.

tcp_client.c
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
static int send_all(int fd, const void *buffer, size_t length) {
const char *p = buffer;
size_t sent = 0;
while (sent < length) {
ssize_t n = send(fd, p + sent, length - sent, 0);
if (n > 0) sent += (size_t)n;
else if (n < 0 && errno == EINTR) continue;
else return -1;
}
return 0;
}
int main(int argc, char **argv) {
const char *host = argc > 1 ? argv[1] : "127.0.0.1";
const char *port = argc > 2 ? argv[2] : "8080";
const char message[] = "hello from client\n";
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC; // Try IPv4 or IPv6
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *results;
int rc = getaddrinfo(host, port, &hints, &results);
if (rc != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
return EXIT_FAILURE;
}
int fd = -1;
for (struct addrinfo *p = results; p != NULL; p = p->ai_next) {
fd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
if (fd < 0) continue;
if (connect(fd, p->ai_addr, p->ai_addrlen) == 0) break;
close(fd);
fd = -1;
}
freeaddrinfo(results);
if (fd < 0) {
fputs("Unable to connect\n", stderr);
return EXIT_FAILURE;
}
if (send_all(fd, message, sizeof message - 1) < 0) {
perror("send");
close(fd);
return EXIT_FAILURE;
}
char buffer[4096];
ssize_t n = recv(fd, buffer, sizeof buffer - 1, 0);
if (n > 0) {
buffer[n] = '\0';
printf("reply: %s", buffer);
} else if (n < 0) {
perror("recv");
}
close(fd);
return n < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
Terminal window
cc -std=c17 -Wall -Wextra -O2 tcp_client.c -o tcp_client
./tcp_client localhost 8080

TCP delivers ordered bytes, not messages. If a sender performs two send() calls, a receiver may observe one combined recv(), several partial receives, or anything in between.

StrategyExampleGood ForCaution
Delimiterhello\nLine commands, chatEscape or reject delimiter in payload
Fixed sizeExactly 64 bytesSimple binary recordsWastes space for variable content
Length prefixuint32 length then payloadBinary/protobuf/JSON messagesValidate maximum length
Connection closeRead until EOFSimple one-response protocolsPrevents reuse of connection
int recv_exact(int fd, void *buffer, size_t length) {
char *p = buffer;
size_t received = 0;
while (received < length) {
ssize_t n = recv(fd, p + received, length - received, 0);
if (n > 0) {
received += (size_t)n;
} else if (n == 0) {
return 0; // Peer closed before a complete message arrived
} else if (errno == EINTR) {
continue;
} else {
return -1;
}
}
return 1;
}
#define MAX_MESSAGE (1024u * 1024u)
uint32_t network_length;
int status = recv_exact(fd, &network_length, sizeof network_length);
if (status <= 0) {
/* Disconnect or error. */
}
uint32_t length = ntohl(network_length);
if (length > MAX_MESSAGE) {
/* Reject oversized input before allocating or reading it. */
}
char *payload = malloc((size_t)length + 1);
if (payload == NULL) {
/* Handle allocation failure. */
}
if (recv_exact(fd, payload, length) != 1) {
/* Handle truncated input. */
}
payload[length] = '\0';

Never deserialize or allocate based on an untrusted length without applying a limit.


UDP is useful when messages are small and an application can tolerate loss or implement its own reliability rules.

udp_server.c
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
int main(void) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in server = {0};
server.sin_family = AF_INET;
server.sin_port = htons(8080);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(fd, (struct sockaddr *)&server, sizeof server) < 0) {
perror("bind");
close(fd);
return EXIT_FAILURE;
}
for (;;) {
char buffer[2048];
struct sockaddr_storage peer;
socklen_t peer_len = sizeof peer;
ssize_t n = recvfrom(fd, buffer, sizeof buffer, 0,
(struct sockaddr *)&peer, &peer_len);
if (n < 0) {
perror("recvfrom");
continue;
}
if (sendto(fd, buffer, (size_t)n, 0,
(struct sockaddr *)&peer, peer_len) < 0) {
perror("sendto");
}
}
}
udp_client.c
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(void) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in server = {0};
server.sin_family = AF_INET;
server.sin_port = htons(8080);
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) != 1) {
fputs("Invalid destination address\n", stderr);
close(fd);
return EXIT_FAILURE;
}
const char request[] = "ping";
if (sendto(fd, request, sizeof request - 1, 0,
(struct sockaddr *)&server, sizeof server) < 0) {
perror("sendto");
close(fd);
return EXIT_FAILURE;
}
char reply[2048];
ssize_t n = recvfrom(fd, reply, sizeof reply - 1, 0, NULL, NULL);
if (n < 0) {
perror("recvfrom");
close(fd);
return EXIT_FAILURE;
}
reply[n] = '\0';
printf("reply: %s\n", reply);
close(fd);
return EXIT_SUCCESS;
}
  • A UDP datagram has a maximum size, but large datagrams may be fragmented or dropped. Keep internet-facing application datagrams small when practical.
  • recvfrom() gives the sender address; validate it when replies should only come from a known peer.
  • If the receive buffer is smaller than the datagram, extra bytes are discarded.
  • Add request IDs, retries, timeout handling, duplication checks, and rate limiting when your protocol needs them.
  • Calling connect() on a UDP socket does not create a TCP-style connection; it sets a default peer and filters unrelated inbound datagrams.

9. Errors, Interruptions, and Clean Shutdown

Section titled “9. Errors, Interruptions, and Clean Shutdown”
OperationResultMeaning
recv()> 0Number of bytes received
recv()0TCP peer performed an orderly shutdown
recv()-1, errno == EINTRSignal interrupted call; usually retry
recv()-1, errno == EAGAIN or EWOULDBLOCKNonblocking socket has no data now
send()> 0 but < requestedOnly part of the bytes were accepted
send()-1, errno == EPIPEPeer has closed; may also trigger SIGPIPE

Writing to a closed TCP connection may terminate a POSIX process unless handled.

signal(SIGPIPE, SIG_IGN); // Process-wide option

On systems that support it, passing MSG_NOSIGNAL to send() limits the behavior to that call:

send(fd, buffer, length, MSG_NOSIGNAL);
shutdown(fd, SHUT_WR); // No more sends; still receive peer response
shutdown(fd, SHUT_RD); // Stop receiving
shutdown(fd, SHUT_RDWR);
close(fd); // Release the descriptor after use

A request/response client can send its request, call shutdown(fd, SHUT_WR), and continue reading until the server closes its response stream.


int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
OptionLevelUse
SO_REUSEADDRSOL_SOCKETRebind a recently used listening address during restart
SO_REUSEPORTSOL_SOCKETPlatform-specific load distribution among listeners
SO_KEEPALIVESOL_SOCKETDetect some dead long-lived TCP peers over time
SO_RCVTIMEO / SO_SNDTIMEOSOL_SOCKETBlocking receive/send timeouts
SO_RCVBUF / SO_SNDBUFSOL_SOCKETKernel buffer sizing hint
TCP_NODELAYIPPROTO_TCPDisable Nagle delay for latency-sensitive small writes
IPV6_V6ONLYIPPROTO_IPV6Decide whether an IPv6 listener accepts mapped IPv4 traffic
SO_BROADCASTSOL_SOCKETPermit IPv4 UDP broadcasts

Options vary across operating systems. Check failures and document why each non-default setting is needed.

struct timeval timeout = {
.tv_sec = 5,
.tv_usec = 0
};
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO,
&timeout, sizeof timeout) < 0) {
perror("SO_RCVTIMEO");
}

11. Blocking, Nonblocking, and Multiplexed I/O

Section titled “11. Blocking, Nonblocking, and Multiplexed I/O”

Default sockets block the current thread until an operation can progress. It is simple and often enough for small tools or one-thread-per-client servers.

int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
perror("fcntl");
}

With a nonblocking socket:

  • accept(), recv(), and send() can fail with EAGAIN/EWOULDBLOCK; wait for readiness and try later.
  • A nonblocking connect() often returns -1 with errno == EINPROGRESS; wait for writable readiness and inspect SO_ERROR.
  • Keep per-connection input/output buffers because reads and writes may be partial.
MechanismBest FitNotes
select()Learning and very small descriptor setsDescriptor limit; rewrites sets each call
poll()Portable moderate-size serversArray scanned on each wait
epollMany Linux connectionsLinux-specific readiness API
kqueueBSD/macOS event loopsPlatform-specific event API
struct pollfd clients[128] = {
{ .fd = server_fd, .events = POLLIN }
};
nfds_t used = 1;
for (;;) {
int ready = poll(clients, used, -1);
if (ready < 0) {
if (errno == EINTR) continue;
perror("poll");
break;
}
if (clients[0].revents & POLLIN) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd >= 0 && used < 128) {
clients[used++] = (struct pollfd){
.fd = client_fd, .events = POLLIN
};
} else if (client_fd >= 0) {
close(client_fd);
}
}
for (nfds_t i = 1; i < used; ++i) {
if (clients[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
close(clients[i].fd);
clients[i] = clients[--used];
--i;
continue;
}
if (clients[i].revents & POLLIN) {
char buffer[4096];
ssize_t n = recv(clients[i].fd, buffer, sizeof buffer, 0);
if (n <= 0) {
close(clients[i].fd);
clients[i] = clients[--used];
--i;
} else {
/* Parse input and enqueue output; handle partial sends. */
}
}
}
}

Production event loops must also watch for writable readiness when queued output cannot be sent immediately.


DesignAdvantagesLimits
Handle one client at a timeMinimal codeAll other clients wait
Process per connection (fork)Isolation; classic Unix approachProcess overhead
Thread per connectionSimple blocking codeThread/resource limits
Thread poolBounded concurrencyNeeds work queue and backpressure
Event loopScales to many mostly idle connectionsMore state management
Event loop plus worker poolScalable I/O with CPU work offloadedMore coordination
  • Set limits: maximum clients, maximum frame length, idle timeout, request deadline, and outbound queue size.
  • Apply backpressure: do not read endlessly when a slow client cannot receive responses.
  • Do not let one client block the accept loop or other connections.
  • Handle SIGTERM/SIGINT so listening sockets and in-flight work can close cleanly.
  • Log peer endpoint, errors, timeouts, rejected oversized frames, and resource exhaustion.

13. Hostname Resolution and Binding Patterns

Section titled “13. Hostname Resolution and Binding Patterns”
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
struct addrinfo *results = NULL;
int error = getaddrinfo("example.com", "443", &hints, &results);
if (error != 0) {
fprintf(stderr, "%s\n", gai_strerror(error));
}
/* Try each result with socket() and connect(). */
freeaddrinfo(results);
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // NULL hostname means wildcard local address
struct addrinfo *addresses = NULL;
int error = getaddrinfo(NULL, "8080", &hints, &addresses);

Try each returned address until socket() and bind() succeed. This is more portable than assuming only IPv4 is available.

Bind TargetReachability
127.0.0.1 / ::1Only programs on the same host
LAN/interface addressNetworks that can reach that interface
0.0.0.0 / ::All matching local interfaces; protect with firewall/authentication

Unix domain sockets (AF_UNIX) communicate between processes on one machine. They avoid network exposure and can use filesystem permissions.

#include <sys/socket.h>
#include <sys/un.h>
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/tmp/my-service.sock",
sizeof addr.sun_path - 1);
unlink(addr.sun_path); // Remove stale path before server bind
bind(fd, (struct sockaddr *)&addr, sizeof addr);
listen(fd, 16);
/* accept(), send(), and recv() work as for TCP. */
/* On server shutdown: close(fd); unlink(addr.sun_path); */

Use a directory with appropriate permissions for sensitive local services. Avoid predictable writable public locations when another user could create the pathname first.


int yes = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof yes);
/* sendto() a subnet broadcast address only when the network permits it. */
struct ip_mreq group = {0};
inet_pton(AF_INET, "239.0.0.1", &group.imr_multiaddr);
group.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&group, sizeof group) < 0) {
perror("IP_ADD_MEMBERSHIP");
}

Broadcast and multicast are generally local-network tools, may be restricted by routers or cloud networks, and require care to avoid amplification or unintended data exposure.


16. Protocol Design: From Demo to Real Service

Section titled “16. Protocol Design: From Demo to Real Service”
+----------+----------+----------+-----------------+
| version | type | length | payload |
| 1 byte | 1 byte | 4 bytes | length bytes |
+----------+----------+----------+-----------------+

For any protocol you design:

  • Specify framing, encoding, byte order, valid message types, and protocol version.
  • Cap payload size and nesting before parsing or allocating memory.
  • Decide what happens on malformed input: error reply, connection close, or ignored UDP datagram.
  • Add authentication and authorization when data or actions are sensitive.
  • Use request IDs when multiple requests may be in flight or UDP retries may duplicate work.
  • Use a standard serialization format when interoperability matters.
GET user:42\n
OK 5\n
Alice

Even a text protocol needs limits: maximum line length, permitted commands, allowed encoding, and clear response termination.


RiskDefensive Practice
Plaintext snooping or tamperingUse TLS through a mature TLS library or terminate TLS at a trusted proxy
Buffer overflowPass buffer sizes; never assume inbound bytes are null-terminated
Memory exhaustionSet message and queue limits before allocating
Slow clients / slowlorisApply read, write, handshake, and idle timeouts
Connection floodsBound concurrency; apply rate limiting and OS/firewall controls
Command injectionParse input; never concatenate network data into shell commands
Unauthorized accessAuthenticate clients and authorize operations
Untrusted deserializationValidate types, lengths, ranges, and protocol state
Descriptor leaksClose sockets on every error and shutdown path
Public accidental exposureBind to loopback during development unless remote access is intended

Raw TCP does not encrypt or authenticate traffic. TLS is a separate layer, commonly implemented with OpenSSL, wolfSSL, mbed TLS, platform APIs, or a reverse proxy.


Terminal window
ss -ltnp # Listening TCP sockets and owning processes
ss -lunp # Listening UDP sockets
nc -vz 127.0.0.1 8080 # Check whether TCP connection succeeds
nc 127.0.0.1 8080 # Interact with a TCP text service
printf 'ping' | nc -u 127.0.0.1 8080
curl -v http://127.0.0.1:8080/
tcpdump -nn -i lo port 8080 # Inspect loopback traffic (often requires privileges)
strace -e trace=network ./tcp_client
SymptomCheck
bind: Address already in useExisting listener, wrong address/port, or restart without SO_REUSEADDR
connect: Connection refusedNo listener on destination or firewall rejection
Client hangsMissing message framing, missing response, timeout absent, or waiting for EOF
Server receives incomplete textTCP partial read; accumulate and parse frames
Works on localhost onlyBind address, firewall, routing, NAT, or service exposure policy
Unexpected disconnectPeer close, timeout, reset, protocol violation, or process termination from SIGPIPE

The socket model is similar, but Windows needs initialization and uses different cleanup/error functions.

#include <winsock2.h>
#include <ws2tcpip.h>
WSADATA data;
if (WSAStartup(MAKEWORD(2, 2), &data) != 0) {
// Handle failure.
}
SOCKET fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd == INVALID_SOCKET) {
int error = WSAGetLastError();
}
closesocket(fd);
WSACleanup();
POSIXWindows
int fdSOCKET fd
Failure is -1Failure is INVALID_SOCKET or SOCKET_ERROR
close(fd)closesocket(fd)
errnoWSAGetLastError()
fcntl(... O_NONBLOCK)ioctlsocket(... FIONBIO ...)
Link normallyLink with Ws2_32.lib / -lws2_32

C++ should give socket descriptors ownership semantics so exceptions and early returns do not leak them.

#include <unistd.h>
#include <utility>
class Socket {
public:
explicit Socket(int fd = -1) noexcept : fd_(fd) {}
~Socket() { reset(); }
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
Socket(Socket&& other) noexcept
: fd_(std::exchange(other.fd_, -1)) {}
Socket& operator=(Socket&& other) noexcept {
if (this != &other) {
reset(std::exchange(other.fd_, -1));
}
return *this;
}
int get() const noexcept { return fd_; }
explicit operator bool() const noexcept { return fd_ >= 0; }
int release() noexcept { return std::exchange(fd_, -1); }
void reset(int next = -1) noexcept {
if (fd_ >= 0) {
::close(fd_);
}
fd_ = next;
}
private:
int fd_;
};

On Windows, replace int, -1, and ::close() with SOCKET, INVALID_SOCKET, and ::closesocket(), or wrap platform details behind a small portability layer.


TopicPractical Meaning
Three-way handshakeTCP connection setup costs a round trip before application data in ordinary TCP
TIME_WAITA recently closed endpoint may remain recorded to protect later connections from old packets
Nagle algorithmCan batch small writes; TCP_NODELAY may reduce latency at a throughput cost
KeepaliveDetects some dead idle peers eventually; it is not an application request deadline
BackpressureA slow receiver eventually prevents unbounded sending; your application must bound queued data
Reset (RST)Abrupt termination; pending data may be lost
Half-closeOne direction may finish while the other still sends data

Do not assume one request per connection unless your protocol says so. Many protocols keep connections alive and carry multiple framed requests.


TopicPractical Meaning
No delivery guaranteeAdd retries only when duplicate effects are safe or detectable
No ordering guaranteeCarry sequence numbers when order matters
No built-in congestion controlAvoid uncontrolled high-rate sending
MTU and fragmentationOversized datagrams are less reliable across real networks
Source spoofingDo not treat an unauthenticated source address as identity
AmplificationKeep unauthenticated responses limited relative to requests

For reliable encrypted streams over the internet, choose TCP plus TLS or a mature QUIC implementation instead of inventing reliability or cryptography on raw UDP.


  1. Learn address, port, TCP, UDP, and client/server terminology.
  2. Compile and run the TCP echo server and client on loopback.
  3. Modify the message and print peer addresses.
  4. Run the UDP pair and observe the difference in API flow.
  1. Add newline or length-prefixed message framing.
  2. Support multiple clients using threads or poll().
  3. Add timeouts, error handling, size limits, and clean shutdown.
  4. Switch listeners and clients to getaddrinfo() for IPv4/IPv6 portability.
  1. Use nonblocking I/O with per-connection buffers and backpressure.
  2. Build an event-loop server with well-defined protocol state.
  3. Add TLS, authentication, rate limits, observability, and resource limits.
  4. Test malformed inputs, slow clients, disconnects, partial messages, and overload behavior.
TCP server: socket -> setsockopt -> bind -> listen -> accept -> recv/send -> close
TCP client: socket -> connect -> send/recv -> shutdown/close
UDP server: socket -> bind -> recvfrom/sendto -> close
UDP client: socket -> sendto/recvfrom -> close
Portable addressing: getaddrinfo -> try returned addresses -> freeaddrinfo
Never forget: check errors, loop for partial I/O, frame TCP messages, set limits.