Linux IPC
56 – TLPI
Beginner–Intermediate
What You Will Learn
When you create a socket you must choose a communication domain. The domain decides two things: (1) the format of the address used to identify a socket, and (2) whether communication stays on the local machine or travels over a network. This part explains every domain supported on Linux and shows the exact address structures you need in code.
Every socket lives inside a communication domain. You pick the domain as the first argument to socket(). The domain controls:
- The address format — how you write down “where” a socket is.
- The reach — same machine only, or across a network.
Think of it like a postal system: a local delivery note (just a room number) works inside one building, while a full postal address is needed for international mail. Domains work the same way.
| Domain Flag | Transport | Scope | Address Format | C Structure |
|---|---|---|---|---|
| AF_UNIX | Kernel (no network stack) | Same host only | Filesystem pathname | sockaddr_un |
| AF_INET | IPv4 network stack | Hosts on an IPv4 network | 32-bit IP + 16-bit port | sockaddr_in |
| AF_INET6 | IPv6 network stack | Hosts on an IPv6 network | 128-bit IP + 16-bit port | sockaddr_in6 |
AF_UNIX (also written AF_LOCAL in POSIX.1g, though SUSv3 uses AF_UNIX) keeps all data inside the kernel. No network packets are created. The address is simply a filesystem path like /tmp/myapp.sock.
When to use AF_UNIX:
- Communicating between a client and server daemon on the same machine.
- You need higher throughput than pipes but no network overhead.
- Example:
nginxtalking to a PHP-FPM worker process.
Address structure — sockaddr_un:
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* Always AF_UNIX */
char sun_path[108]; /* Filesystem path (null-terminated) */
};
| Client AF_UNIX socket |
Linux Kernel
Data copies via kernel memory Path: /tmp/myapp.sock |
Server AF_UNIX socket |
Code — create a Unix domain socket:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define SOCK_PATH "/tmp/demo.sock"
int main(void)
{
int sockfd;
struct sockaddr_un addr;
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); exit(1); }
/* Zero out then fill the address structure */
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);
printf("AF_UNIX socket created, path will be: %s\n", addr.sun_path);
close(sockfd);
return 0;
}
AF_INET is used for communication between processes on machines connected via an IPv4 network. An IPv4 address is 32 bits long (the familiar 192.168.1.100 format). Together with a 16-bit port number, it uniquely identifies a socket on the entire internet.
Address structure — sockaddr_in:
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* 16-bit port number (network byte order) */
struct in_addr sin_addr; /* 32-bit IPv4 address */
};
struct in_addr {
uint32_t s_addr; /* IPv4 address in network byte order */
};
Important: Port numbers and IP addresses must be stored in network byte order (big-endian). Use htons() for port and inet_pton() for IP conversion:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(void)
{
int sockfd;
struct sockaddr_in addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); exit(1); }
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); /* host-to-network-short */
/* Convert "192.168.1.1" string into binary network-order form */
if (inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr) <= 0) {
perror("inet_pton");
exit(1);
}
printf("AF_INET socket ready for 192.168.1.1:8080\n");
close(sockfd);
return 0;
}
| 32-bit IPv4 Address (4 octets) | 16-bit Port | ||||
| 192 | 168 | 1 | 1 | 8080 | |
IPv4 only has about 4 billion addresses — not enough for today’s internet. IPv6 uses 128-bit addresses (written as 2001:db8::1), providing a practically unlimited pool. AF_INET6 is the domain for IPv6 sockets.
Address structure — sockaddr_in6:
#include <netinet/in.h>
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* 16-bit port (network byte order) */
uint32_t sin6_flowinfo; /* IPv6 flow info (usually 0) */
struct in6_addr sin6_addr; /* 128-bit IPv6 address */
uint32_t sin6_scope_id; /* Scope ID for link-local addresses */
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(void)
{
int sockfd;
struct sockaddr_in6 addr;
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); exit(1); }
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(8080);
/* ::1 is the IPv6 loopback address (equivalent of 127.0.0.1) */
if (inet_pton(AF_INET6, "::1", &addr.sin6_addr) <= 0) {
perror("inet_pton");
exit(1);
}
printf("AF_INET6 socket ready for [::1]:8080\n");
close(sockfd);
return 0;
}
You may see both PF_INET and AF_INET in old code. Here is the story:
- AF_ stands for Address Family — how addresses are formatted.
- PF_ stands for Protocol Family — the protocol suite.
The original idea was that one protocol family might support multiple address families. That never happened. In every implementation, PF_INET == AF_INET, PF_UNIX == AF_UNIX, etc. — they are numeric aliases.
SUSv3 standardises only the AF_ constants. Always use AF_ in new code to keep things standard and clear.
/* These two lines do exactly the same thing — prefer AF_ */
int fd1 = socket(AF_INET, SOCK_STREAM, 0); /* correct — use this */
int fd2 = socket(PF_INET, SOCK_STREAM, 0); /* works but non-standard */
Q1. What are the three main socket domains in Linux and when do you choose each?
AF_UNIX — for local IPC on the same machine using a filesystem path as address, giving zero network overhead. AF_INET — for IPv4 network communication using a 32-bit IP address and 16-bit port. AF_INET6 — for IPv6 network communication using a 128-bit IP address and 16-bit port. Choose AF_UNIX for local daemons, AF_INET for legacy IPv4 services, and AF_INET6 for modern internet applications.
Q2. Why must you use htons() when setting a port number in sockaddr_in?
Network protocols require addresses and ports in big-endian (network byte order). x86/ARM machines are little-endian. htons() (host-to-network-short) converts a 16-bit value from host byte order to network byte order. Forgetting it means your port number will be interpreted wrongly on big-endian receivers.
Q3. What is the address of an AF_UNIX socket?
A filesystem pathname stored in sockaddr_un.sun_path, for example /var/run/nginx.sock. The kernel creates an entry in the filesystem when the server calls bind(), and removes it when the socket is unlinked with unlink().
Q4. What is the difference between AF_ and PF_ constants?
Historically AF_ was for address family and PF_ for protocol family. In practice they are numerically identical on every Linux implementation. SUSv3 only standardises AF_ constants, so those should always be used in portable code.
Q5. What function converts a dotted-decimal IPv4 string to binary form for sockaddr_in?
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr). The older inet_addr() is deprecated. inet_pton() also handles IPv6 addresses when called with AF_INET6.
Q6. What is the IPv6 loopback address equivalent of 127.0.0.1?
::1 — the 128-bit all-zeros address with the last bit set. In C code: inet_pton(AF_INET6, "::1", &addr.sin6_addr).
