What Is This Library?
When you write a TCP or UDP network program on Linux, you always repeat the same steps: create a socket, fill in address structures, call getaddrinfo(), loop over results, bind or connect, check errors. It’s tedious and error-prone.
The Internet Domain Sockets Library (from TLPI Section 59.12) wraps all of this into four clean, reusable functions. You call one function instead of ten lines of boilerplate. The library internally uses getaddrinfo() and getnameinfo(), so everything works with both IPv4 and IPv6 — you don’t have to change anything when your server moves to IPv6.
Think of it as a thin helper layer above the raw socket API — not hiding the concepts, but eliminating repetitive plumbing code.
Consider what a basic TCP client normally does without any helper:
/* Without the library – lots of manual boilerplate */
struct addrinfo hints, *result, *rp;
int sfd;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;
if (getaddrinfo("example.com", "80", &hints, &result) != 0) {
/* handle error */
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) continue;
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) break;
close(sfd);
}
freeaddrinfo(result);
if (rp == NULL) { /* handle failure */ }
Every single client program repeats this 20-line block. The library reduces it to one line:
/* With the library */
int sfd = inetConnect("example.com", "80", SOCK_STREAM);
The same simplification applies to servers (binding and listening) and to address formatting. The library also makes it trivial to switch between IPv4 and IPv6 since getaddrinfo() handles both transparently.
All four functions share three recurring parameters. Understanding them once means you understand all four functions:
| Argument | Type | What You Pass |
|---|---|---|
host |
const char * |
A hostname like "example.com", or a numeric IP like "192.168.1.1", or an IPv6 address like "::1". Pass NULL to use the loopback address (127.0.0.1 / ::1). |
service |
const char * |
A service name like "http", "ssh", "ftp", or a port number as a string like "8080". Service names are looked up in /etc/services. |
type |
int |
SOCK_STREAM for TCP (reliable, connection-based), or SOCK_DGRAM for UDP (unreliable, connectionless). |
Example usages to make it concrete:
/* Connect to a web server over TCP */
inetConnect("example.com", "http", SOCK_STREAM);
/* Connect to local DNS server over UDP */
inetConnect(NULL, "domain", SOCK_DGRAM);
/* Connect to custom port by number */
inetConnect("192.168.0.10", "9090", SOCK_STREAM);
The header file declares all four functions and one important constant. Any program using this library must include this header.
/* inet_sockets.h */
#ifndef INET_SOCKETS_H
#define INET_SOCKETS_H /* guard: prevent accidental double inclusion */
#include <sys/socket.h>
#include <netdb.h>
int inetConnect(const char *host, const char *service, int type);
int inetListen(const char *service, int backlog, socklen_t *addrlen);
int inetBind(const char *service, int type, socklen_t *addrlen);
char *inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
char *addrStr, int addrStrLen);
#define IS_ADDR_STR_LEN 4096
/* Suggested buffer size for inetAddressStr().
Must be > (NI_MAXHOST + NI_MAXSERV + 4) */
#endif
The #ifndef / #define / #endif guard is standard practice to prevent the same header from being included twice in a translation unit, which would cause duplicate declaration errors.
IS_ADDR_STR_LEN is set to 4096 bytes — large enough to hold the longest possible hostname (NI_MAXHOST) plus the longest service name (NI_MAXSERV) plus a few extra characters for formatting.
#include "inet_sockets.h"
int inetConnect(const char *host, const char *service, int type);
/* Returns: file descriptor on success, -1 on error */
What it does internally:
- Calls
getaddrinfo(host, service, ...)to resolve the address - Loops through the returned address list
- For each address: creates a socket, tries
connect() - Returns the first socket that connects successfully
- Calls
freeaddrinfo()to clean up the list
Usage example — a simple TCP client:
#include "inet_sockets.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
/* Connect to an echo server on port 7 */
int sfd = inetConnect("localhost", "7", SOCK_STREAM);
if (sfd == -1) {
perror("inetConnect failed");
return 1;
}
/* Send a message */
const char *msg = "Hello, server!";
write(sfd, msg, strlen(msg));
/* Read the echo back */
char buf[256];
ssize_t n = read(sfd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Echo: %s\n", buf);
}
close(sfd);
return 0;
}
Notice: no struct addrinfo, no loop, no getaddrinfo() call in your code. The library handles all of that.
#include "inet_sockets.h"
int inetListen(const char *service, int backlog, socklen_t *addrlen);
/* Returns: file descriptor on success, -1 on error */
Parameters:
| Parameter | Meaning |
|---|---|
service |
Port or service name to listen on, e.g. "8080" |
backlog |
Max pending connections in the queue (passed to listen()) |
addrlen |
Output parameter. If non-NULL, the library writes the size of the socket address structure here. Use this to allocate the right buffer size for accept(). Pass NULL if you don’t need it. |
What it does internally:
- Calls
getaddrinfo(NULL, service, ...)— NULL host = wildcard (0.0.0.0) - Creates a
SOCK_STREAMsocket - Sets
SO_REUSEADDRso you can restart the server quickly - Calls
bind()to attach to the port - Calls
listen()to start accepting connections
Usage example — a TCP server skeleton:
#include "inet_sockets.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
int main(void)
{
socklen_t addrlen;
/* Create listening socket on port 8080, backlog of 5 */
int lfd = inetListen("8080", 5, &addrlen);
if (lfd == -1) {
perror("inetListen failed");
return 1;
}
/* Allocate a buffer of the correct size for accept() */
struct sockaddr *claddr = malloc(addrlen);
for (;;) {
socklen_t len = addrlen;
/* Accept an incoming connection */
int cfd = accept(lfd, claddr, &len);
if (cfd == -1) {
perror("accept");
continue;
}
/* Handle client ... */
write(cfd, "Hello!\n", 7);
close(cfd);
}
free(claddr);
close(lfd);
return 0;
}
The addrlen returned by inetListen() tells you exactly how many bytes to allocate for the struct sockaddr buffer passed to accept(). This is important because the size differs between IPv4 and IPv6.
#include "inet_sockets.h"
int inetBind(const char *service, int type, socklen_t *addrlen);
/* Returns: file descriptor on success, -1 on error */
How is inetBind() different from inetListen()?
- Always creates
SOCK_STREAM - Also calls
listen() - For TCP servers only
- Takes a
typeargument (SOCK_STREAMorSOCK_DGRAM) - Does NOT call
listen() - Flexible — for UDP servers, UDP clients, or custom TCP use
Both inetListen() and inetBind() share most of their internal logic. In the library source, this shared logic lives in a private function called inetPassiveSocket() which both call internally.
Usage example — a UDP server:
#include "inet_sockets.h"
#include <stdio.h>
#include <sys/socket.h>
int main(void)
{
socklen_t addrlen;
/* Create a UDP socket bound to port 9000 */
int sfd = inetBind("9000", SOCK_DGRAM, &addrlen);
if (sfd == -1) {
perror("inetBind failed");
return 1;
}
struct sockaddr *claddr = malloc(addrlen);
char buf[512];
for (;;) {
socklen_t len = addrlen;
/* Receive a datagram, also get sender's address */
ssize_t n = recvfrom(sfd, buf, sizeof(buf) - 1, 0, claddr, &len);
if (n == -1) { perror("recvfrom"); continue; }
buf[n] = '\0';
printf("Received: %s\n", buf);
/* Echo back to sender */
sendto(sfd, buf, n, 0, claddr, len);
}
free(claddr);
return 0;
}
The addrlen value from inetBind() is especially useful here: recvfrom() needs a properly sized address buffer to store the sender’s address.
struct sockaddr (binary address) into a human-readable string like (example.com, 8080). Useful for logging and debugging.#include "inet_sockets.h"
char *inetAddressStr(const struct sockaddr *addr, socklen_t addrlen,
char *addrStr, int addrStrLen);
/* Returns: pointer to addrStr (the filled buffer) */
Parameters:
| Parameter | Meaning |
|---|---|
addr |
Pointer to the socket address (from accept(), recvfrom(), etc.) |
addrlen |
Size of the address structure |
addrStr |
Caller-supplied buffer where the result string is written |
addrStrLen |
Size of addrStr buffer. Use IS_ADDR_STR_LEN (4096) |
Output format:
(hostname, port-number)
/* Examples: */
(example.com, 443)
(192.168.1.5, 8080)
(::1, 9090)
If the resulting string would be longer than addrStrLen - 1 bytes, it is truncated silently. Always use IS_ADDR_STR_LEN (4096) as your buffer size to avoid truncation.
Usage example — log the address of an accepted client:
#include "inet_sockets.h"
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
socklen_t addrlen;
int lfd = inetListen("8080", 5, &addrlen);
struct sockaddr *claddr = malloc(addrlen);
for (;;) {
socklen_t len = addrlen;
int cfd = accept(lfd, claddr, &len);
if (cfd == -1) continue;
/* Convert the client's address to a printable string */
char addrStr[IS_ADDR_STR_LEN];
inetAddressStr(claddr, len, addrStr, IS_ADDR_STR_LEN);
printf("New connection from: %s\n", addrStr);
/* prints: New connection from: (192.168.1.10, 54321) */
close(cfd);
}
free(claddr);
close(lfd);
return 0;
}
| Function | Who Uses It | Socket Type | What It Returns |
|---|---|---|---|
inetConnect() |
Client | TCP or UDP | Connected socket fd |
inetListen() |
TCP Server | TCP only (SOCK_STREAM) | Listening socket fd |
inetBind() |
UDP Server / UDP Client | TCP or UDP | Bound socket fd |
inetAddressStr() |
Client & Server (logging) | Any | Readable address string |
TCP Server flow using the library:
TCP Client flow using the library:
UDP Server flow using the library:
getaddrinfo() is protocol-independent. It returns a linked list of address structures that can include both IPv4 (AF_INET) and IPv6 (AF_INET6) entries depending on the system and DNS resolution. By using AF_UNSPEC in the hints, the library allows the OS to choose the best available protocol. This means the same library code works on IPv4-only, IPv6-only, and dual-stack systems without any changes.sizeof(struct sockaddr_in) = 16 bytes) and IPv6 (sizeof(struct sockaddr_in6) = 28 bytes). The addrlen output parameter lets the library tell the caller exactly how many bytes to allocate for the address buffer used in subsequent accept() or recvfrom() calls. Without this, you would have to guess or hardcode a size, which would fail for the other protocol family.inetListen() always creates a SOCK_STREAM (TCP) socket and calls listen(), making it ready to accept incoming connections. It is for TCP servers only. inetBind() takes a type argument so it can create either a SOCK_STREAM or SOCK_DGRAM (UDP) socket, and it does not call listen(). It is primarily used by UDP servers and UDP clients that need to bind to a fixed local port.NULL as the host tells getaddrinfo() to use the loopback address — 127.0.0.1 for IPv4 or ::1 for IPv6. This is used when the client and server are on the same machine and you want to connect locally without specifying a hostname.NI_MAXHOST + NI_MAXSERV + 4. On Linux, NI_MAXHOST is 1025 and NI_MAXSERV is 32, so the minimum required is about 1061 bytes. 4096 provides a large, safe margin. You could technically use a smaller buffer (like 1100), but 4096 is chosen for safety because it is well above any realistic maximum and avoids risk of truncation. If truncation occurs, the string is silently cut — which could cause misleading log output.SOCK_DGRAM as the type, inetConnect() creates a UDP socket and calls connect() on it. For UDP, connect() does not establish a real connection — it just records the peer’s address in the kernel. After this, you can use send()/recv() instead of sendto()/recvfrom(), and the kernel automatically uses the stored peer address. The kernel also filters incoming datagrams, discarding any that do not come from the connected peer.