IPv4 Domain
IPv6 Domain
Transport Layer
What Are Internet Domain Sockets?
In the previous chapter we studied generic socket concepts and the TCP/IP protocol suite. In this chapter we write real programs using sockets in the IPv4 (AF_INET) and IPv6 (AF_INET6) domains.
An Internet domain socket address is made up of two things: an IP address and a port number. Computers store these as binary numbers, but humans find names much easier to work with. So this chapter also covers how to look up IP addresses from hostnames (using DNS) and port numbers from service names.
Internet domain stream sockets are built on top of TCP (Transmission Control Protocol). They give you a reliable, bidirectional, byte-stream communication channel.
Think of TCP like a phone call — the connection is established first, data flows in order, and the receiver always gets everything the sender sent.
| Client | socket(AF_INET, SOCK_STREAM, 0) | Server |
| connect() | ── TCP 3-Way Handshake ──> | accept() |
| send() / write() | ──── Reliable Data ────> | recv() / read() |
| close() | ──── FIN / ACK ────> | close() |
Properties of TCP Stream Sockets:
- Data arrives in the same order it was sent
- No data is lost (TCP retransmits if needed)
- No duplicates
- Built-in flow control and congestion control
Internet domain datagram sockets are built on top of UDP (User Datagram Protocol). Unlike TCP, UDP is connectionless and does not guarantee delivery.
Think of UDP like dropping a letter in a mailbox — it might get there, it might not, and two letters sent together could arrive in any order.
| Property | UNIX Domain Datagram | UDP (Internet Domain) |
|---|---|---|
| Reliability | ✔ Reliable | ✘ Not Reliable |
| Data Loss | Never lost | May be lost / duplicated |
| Order | In-order delivery | Out-of-order possible |
| Queue Full Behavior | send() blocks | Datagram silently dropped |
| Use Case | Local IPC only | DNS, VoIP, Games, Streaming |
Key difference to remember: If the receiver’s queue is full:
- UNIX domain datagram: the sender’s
send()call blocks until space is available. - UDP: the datagram is silently dropped — no error is returned to the sender.
Every Internet domain socket needs an address. For IPv4, we use struct sockaddr_in:
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; /* AF_INET */
in_port_t sin_port; /* Port number (network byte order) */
struct in_addr sin_addr; /* IPv4 address */
};
struct in_addr {
in_addr_t s_addr; /* 32-bit IPv4 address (network byte order) */
};
For IPv6, we use struct sockaddr_in6:
#include <netinet/in.h>
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* Port number (network byte order) */
uint32_t sin6_flowinfo; /* IPv6 flow info */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID */
};
| sin_family 2 bytes (AF_INET=2) |
sin_port 2 bytes (network order) |
sin_addr.s_addr 4 bytes (IPv4 address, network order) |
A simple TCP server setup looks like:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sfd;
struct sockaddr_in addr;
/* Create an IPv4 TCP socket */
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Fill in server address */
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; /* IPv4 */
addr.sin_port = htons(8080); /* Port 8080, network byte order */
addr.sin_addr.s_addr = INADDR_ANY; /* Accept on any local interface */
/* Bind socket to address */
if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
/* Start listening */
if (listen(sfd, 5) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("Server listening on port 8080...\n");
/* accept() loop would go here */
close(sfd);
return 0;
}
Humans use names like www.google.com; computers use IP addresses like 142.250.182.4. DNS is the distributed database that translates between them.
| Your App calls getaddrinfo(“www.example.com”) |
→ | Resolver /etc/resolv.conf |
→ | DNS Server Recursive lookup |
→ | IP Address 93.184.216.34 |
The key function for hostname-to-IP resolution in modern programs is getaddrinfo(), which replaces the older gethostbyname(). It supports both IPv4 and IPv6.
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/* Prototype */
int getaddrinfo(const char *node, /* hostname or IP string */
const char *service, /* port or service name */
const struct addrinfo *hints,
struct addrinfo **res); /* result linked list */
void freeaddrinfo(struct addrinfo *res);
Example — resolve a hostname:
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
int main() {
struct addrinfo hints, *res, *p;
char ipstr[INET6_ADDRSTRLEN];
int status;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* TCP */
status = getaddrinfo("www.example.com", "80", &hints, &res);
if (status != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 1;
}
printf("IP addresses for www.example.com:\n");
for (p = res; p != NULL; p = p->ai_next) {
void *addr;
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
}
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
printf(" %s\n", ipstr);
}
freeaddrinfo(res);
return 0;
}
Q1. What is the difference between AF_INET and AF_INET6?
AF_INET is the address family for IPv4 (32-bit addresses). AF_INET6 is for IPv6 (128-bit addresses). You choose the family when calling socket(). IPv6 was introduced to solve the address exhaustion problem of IPv4.
Q2. What is the key difference between a TCP socket and a UDP socket in terms of reliability?
TCP (SOCK_STREAM) guarantees reliable, ordered, duplicate-free delivery. UDP (SOCK_DGRAM) is unreliable — datagrams may be lost, duplicated, or arrive out of order. TCP achieves reliability through sequence numbers, acknowledgments, and retransmissions.
Q3. What happens when you send a UDP datagram and the receiver’s queue is full?
The datagram is silently dropped. The sender gets no error. This is different from UNIX domain datagram sockets, where the sender’s send() would block until space is available.
Q4. What does INADDR_ANY mean in a socket bind call?
INADDR_ANY (value 0) tells the kernel to bind the socket to all available local network interfaces. This means the server will accept connections on any IP address the machine has, whether it’s 127.0.0.1 (loopback), a LAN address, or a public IP.
Q5. Why is getaddrinfo() preferred over gethostbyname()?
getaddrinfo() supports both IPv4 and IPv6, is reentrant (thread-safe), and handles both name-to-address and service-to-port translation in one call. The older gethostbyname() only handles IPv4 and is not reentrant.
Q6. What structure holds an IPv4 socket address?
struct sockaddr_in defined in <netinet/in.h>. It has three important fields: sin_family (set to AF_INET), sin_port (port in network byte order), and sin_addr.s_addr (IPv4 address in network byte order).
Next: Network Byte Order
Learn about big-endian vs little-endian and the htons/htonl/ntohs/ntohl conversion functions.
