Internet Domain Sockets Library Library Overview & API

 

Internet Domain Sockets Library
TLPI Chapter 59.12 — Part 1 of 2: Library Overview & API
4
Library Functions
IPv4+6
Protocol Support
TCP/UDP
Socket Types

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.

Key Terms in This Tutorial
inetConnect() inetListen() inetBind() inetAddressStr() getaddrinfo() getnameinfo() SOCK_STREAM SOCK_DGRAM socklen_t wildcard IP IS_ADDR_STR_LEN AF_UNSPEC

Why Wrap Socket Calls Into a Library?

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.

Common Arguments Across All Four Functions

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: inet_sockets.h

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.

Function 1: inetConnect() — For Clients
Purpose: Create a socket and connect it to a remote server in one step. Designed for TCP or UDP clients.
#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.

Function 2: inetListen() — For TCP Servers
Purpose: Create a listening TCP socket bound to all local interfaces. Designed for TCP servers.
#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_STREAM socket
  • Sets SO_REUSEADDR so 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.

Function 3: inetBind() — For UDP Servers and Clients
Purpose: Create a socket of any type and bind it to a port. Designed primarily for UDP servers and UDP clients that need a fixed local address.
#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()?

inetListen()
  • Always creates SOCK_STREAM
  • Also calls listen()
  • For TCP servers only
inetBind()
  • Takes a type argument (SOCK_STREAM or SOCK_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.

Function 4: inetAddressStr() — Convert Address to Readable String
Purpose: Turn a 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;
}

Putting It All Together: A Quick Reference

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:

inetListen()
accept()
inetAddressStr()
read/write
close()

TCP Client flow using the library:

inetConnect()
write() request
read() response
close()

UDP Server flow using the library:

inetBind()
recvfrom()
inetAddressStr()
sendto()

Interview Questions — Library Concepts
Q1. Why does this library use getaddrinfo() internally instead of hardcoding IPv4 struct sockaddr_in?
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.
Q2. What is the purpose of the addrlen output parameter in inetListen() and inetBind()?
The size of the socket address structure differs between IPv4 (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.
Q3. What is the difference between inetListen() and inetBind()?
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.
Q4. What does passing NULL as the host argument mean in inetConnect()?
Passing 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.
Q5. Why is IS_ADDR_STR_LEN defined as 4096? Could you use a smaller value?
The documentation requires that the buffer size must be greater than 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.
Q6. Can inetConnect() be used for UDP? How does that work?
Yes. By passing 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.

Next: Part 2 — Library Implementation
See the full source code of inet_sockets.c with line-by-line explanation of inetConnect, inetPassiveSocket, inetBind, inetListen, and inetAddressStr.

Go to Part 2 → EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *