Internet Socket Helper Library inetListen() · inetConnect() · getaddrinfo()

 

Part 2: Internet Socket Helper Library
inetListen() · inetConnect() · getaddrinfo() · Chapter 59 · EmbeddedPathashala

Why a Helper Library?

Writing raw socket code for a TCP server involves many repetitive steps: create socket, set socket options, fill in the address structure, bind, listen. Every server you write repeats these same 10-15 lines of boilerplate. A helper library wraps all of this into two simple functions: inetListen() and inetConnect().

These functions also use the modern getaddrinfo() API, which handles both IPv4 and IPv6 automatically and does DNS resolution for you. This is how real production servers are written.

What the Library Provides

Raw Socket Code vs Helper Library

Without Helper (Server)
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, port, …);
sockfd = socket(…);
setsockopt(SO_REUSEADDR);
bind(sockfd, …);
listen(sockfd, backlog);
freeaddrinfo(…);
~15 lines, repeated everywhere

With Helper (Server)

fd = inetListen(port, backlog, &addrlen);

1 line, clean and reusable

inetListen() — Server Side

inetListen() creates a listening TCP socket bound to the given port on all available network interfaces. It handles IPv4, sets SO_REUSEADDR, and returns a ready-to-accept() file descriptor.

Steps inside inetListen()
getaddrinfo(NULL, port, AI_PASSIVE)
socket(AF_INET, SOCK_STREAM, 0)
setsockopt(SO_REUSEADDR)
bind(fd, addr, addrlen)
listen(fd, backlog)
return fd (ready to accept)
/*
 * inetListen() - Create a listening TCP socket on the given port.
 *
 * service  - port number as string, e.g. "54321" or service name like "http"
 * backlog  - listen() backlog (queue size for pending connections)
 * addrlen  - if non-NULL, filled with address length (useful for accept())
 *
 * Returns a file descriptor ready for accept(), or -1 on error.
 */
#include <sys/socket.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int inetListen(const char *service, int backlog, socklen_t *addrlen)
{
    struct addrinfo hints, *result, *rp;
    int sockfd, optval, s;

    memset(&hints, 0, sizeof(hints));
    hints.ai_canonname = NULL;
    hints.ai_addr      = NULL;
    hints.ai_next      = NULL;
    hints.ai_socktype  = SOCK_STREAM;
    hints.ai_family    = AF_UNSPEC;        /* IPv4 or IPv6 */
    hints.ai_flags     = AI_PASSIVE;       /* Bind to all interfaces */

    /* Resolve address for listening */
    s = getaddrinfo(NULL, service, &hints, &result);
    if (s != 0) {
        errno = ENOSYS;  /* Use a generic error */
        return -1;
    }

    /* Try each address returned by getaddrinfo */
    optval = 1;
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sockfd == -1)
            continue;   /* Try next address */

        /* Allow reuse of the port quickly after server restart */
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval,
                       sizeof(optval)) == -1) {
            close(sockfd);
            freeaddrinfo(result);
            return -1;
        }

        if (bind(sockfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break;   /* bind() succeeded — use this address */

        /* bind() failed — try next address */
        close(sockfd);
    }

    if (rp == NULL) {
        freeaddrinfo(result);
        return -1;   /* No address succeeded */
    }

    if (addrlen != NULL)
        *addrlen = rp->ai_addrlen;   /* Return address size to caller */

    freeaddrinfo(result);

    if (listen(sockfd, backlog) == -1) {
        close(sockfd);
        return -1;
    }

    return sockfd;   /* Ready to accept() */
}

inetConnect() — Client Side

inetConnect() creates a TCP socket and connects it to the given host and port. It handles DNS resolution automatically — pass a hostname like “localhost” or “192.168.1.1” and it does the right thing.

/*
 * inetConnect() - Connect to a TCP server.
 *
 * host     - hostname or IP address string (e.g. "localhost", "192.168.1.5")
 * service  - port/service name (e.g. "54321" or "http")
 * type     - SOCK_STREAM for TCP, SOCK_DGRAM for UDP
 *
 * Returns a connected file descriptor, or -1 on error.
 */
int inetConnect(const char *host, const char *service, int type)
{
    struct addrinfo hints, *result, *rp;
    int sockfd, s;

    memset(&hints, 0, sizeof(hints));
    hints.ai_canonname = NULL;
    hints.ai_addr      = NULL;
    hints.ai_next      = NULL;
    hints.ai_family    = AF_UNSPEC;   /* IPv4 or IPv6 */
    hints.ai_socktype  = type;        /* SOCK_STREAM or SOCK_DGRAM */

    /* Resolve hostname + port to a list of addresses */
    s = getaddrinfo(host, service, &hints, &result);
    if (s != 0) {
        errno = ENOSYS;
        return -1;
    }

    /* Try each address until one connects */
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sockfd == -1)
            continue;   /* Try next */

        if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) != -1)
            break;   /* connect() succeeded */

        close(sockfd);   /* connect() failed — try next */
    }

    freeaddrinfo(result);

    if (rp == NULL)
        return -1;   /* No address worked */

    return sockfd;   /* Connected and ready */
}

Sequence Number Server — Using the Library

The TLPI book uses a sequence number server (seqnum server) as the example application. The server keeps a counter; each client sends a request with a number N, and the server responds with N sequential numbers starting from the current counter value.

Sequence Number Protocol
Client
send “5\n”
recv “0\n”
(got 5 numbers: 0,1,2,3,4)
──── “5\n” ────>
TCP connection
<──── “0\n” ────
Server
recv N=5
send seq (0)
counter += 5

is_seqnum_sv.c — Server Using inetListen()

/* is_seqnum_sv.c — Sequence Number Server */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include "rlbuf.h"
#include "inet_sockets.h"   /* provides inetListen() */

#define PORT_NUM  "54321"
#define BACKLOG   50
#define MAXLINE   128

int main(int argc, char *argv[])
{
    int       listenfd, connfd;
    socklen_t addrlen, len;
    struct    sockaddr_storage claddr;
    char      line[MAXLINE];
    char      addr_str[INET6_ADDRSTRLEN];
    RlBuf     rlbuf;
    int       seq_num = 0;   /* Global sequence counter */

    /* Use inetListen() — one call instead of 15 lines */
    listenfd = inetListen(PORT_NUM, BACKLOG, &addrlen);
    if (listenfd == -1) {
        perror("inetListen"); exit(EXIT_FAILURE);
    }

    printf("Server ready on port %s\n", PORT_NUM);

    for (;;) {
        len = sizeof(claddr);
        connfd = accept(listenfd, (struct sockaddr *)&claddr, &len);
        if (connfd == -1) { perror("accept"); continue; }

        initRlBuf(connfd, &rlbuf);

        /* Read request: "N\n" where N = how many sequence numbers wanted */
        if (readLineBuf(&rlbuf, line, sizeof(line)) <= 0) {
            close(connfd); continue;
        }

        int req_len = atoi(line);   /* Client wants req_len numbers */
        if (req_len <= 0) {
            close(connfd); continue;
        }

        /* Send the starting sequence number */
        snprintf(line, sizeof(line), "%d\n", seq_num);
        if (write(connfd, line, strlen(line)) != (ssize_t)strlen(line))
            perror("write");

        seq_num += req_len;   /* Advance counter */

        close(connfd);
    }

    return 0;
}

is_seqnum_cl.c — Client Using inetConnect()

/* is_seqnum_cl.c — Sequence Number Client */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "rlbuf.h"
#include "inet_sockets.h"

#define PORT_NUM "54321"
#define MAXLINE  128

int main(int argc, char *argv[])
{
    int   sockfd;
    char  line[MAXLINE];
    RlBuf rlbuf;
    int   req_len;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s host len\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    req_len = atoi(argv[2]);
    if (req_len <= 0) {
        fprintf(stderr, "len must be positive\n");
        exit(EXIT_FAILURE);
    }

    /* Connect to server — one call instead of socket+fill+connect */
    sockfd = inetConnect(argv[1], PORT_NUM, SOCK_STREAM);
    if (sockfd == -1) {
        perror("inetConnect"); exit(EXIT_FAILURE);
    }

    /* Send request: "N\n" */
    snprintf(line, sizeof(line), "%d\n", req_len);
    if (write(sockfd, line, strlen(line)) != (ssize_t)strlen(line)) {
        perror("write"); exit(EXIT_FAILURE);
    }

    initRlBuf(sockfd, &rlbuf);

    /* Read response */
    if (readLineBuf(&rlbuf, line, sizeof(line)) <= 0) {
        fprintf(stderr, "Server error or EOF\n");
        exit(EXIT_FAILURE);
    }

    printf("Sequence number: %s", line);
    printf("(reserved %d numbers: %d to %d)\n",
           req_len, atoi(line), atoi(line) + req_len - 1);

    close(sockfd);
    return 0;
}

Build and Test

# Header file: inet_sockets.h
# int inetListen(const char *service, int backlog, socklen_t *addrlen);
# int inetConnect(const char *host, const char *service, int type);

gcc -Wall -o seqnum_sv is_seqnum_sv.c inet_sockets.c rlbuf.c
gcc -Wall -o seqnum_cl is_seqnum_cl.c inet_sockets.c rlbuf.c

# Terminal 1
./seqnum_sv

# Terminal 2 — request 5 numbers
./seqnum_cl localhost 5
Sequence number: 0
(reserved 5 numbers: 0 to 4)

# Terminal 2 — request 3 more numbers
./seqnum_cl localhost 3
Sequence number: 5
(reserved 3 numbers: 5 to 7)

Understanding getaddrinfo()

getaddrinfo() is the modern API that replaces gethostbyname(), getservbyname(), and manual struct filling. It resolves both hostname and service name, supports IPv4 and IPv6, and is thread-safe.

#include <netdb.h>

int getaddrinfo(
    const char *node,      /* hostname, IP string, or NULL (for servers) */
    const char *service,   /* port number as string, or service name */
    const struct addrinfo *hints,  /* constraints on result */
    struct addrinfo **res  /* OUTPUT: linked list of results */
);

struct addrinfo {
    int              ai_flags;       /* AI_PASSIVE, AI_CANONNAME, etc. */
    int              ai_family;      /* AF_INET, AF_INET6, AF_UNSPEC */
    int              ai_socktype;    /* SOCK_STREAM, SOCK_DGRAM */
    int              ai_protocol;    /* IPPROTO_TCP, IPPROTO_UDP, 0 */
    socklen_t        ai_addrlen;     /* Size of ai_addr */
    struct sockaddr *ai_addr;        /* The address to use with socket APIs */
    char            *ai_canonname;   /* Canonical hostname (if requested) */
    struct addrinfo *ai_next;        /* Next result in linked list */
};

/*
 * IMPORTANT: Always call freeaddrinfo() when done — it's a linked list
 * and getaddrinfo() allocates memory for it.
 */

/* Example: resolve "localhost:80" */
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_family   = AF_UNSPEC;     /* Accept IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM;   /* TCP only */

int rc = getaddrinfo("localhost", "80", &hints, &res);
if (rc != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc));
    /* NOTE: use gai_strerror(), not strerror(), for getaddrinfo errors */
}

/* Use res->ai_addr, res->ai_addrlen, res->ai_family etc. */

freeaddrinfo(res);   /* Always free! */

Important hints.ai_flags values
Flag Meaning Used By
AI_PASSIVE node=NULL means “all interfaces” (INADDR_ANY) Servers
AI_CANONNAME Fill ai_canonname with the canonical hostname Diagnostics
AI_NUMERICHOST node must be a numeric IP string (no DNS lookup) Performance
AI_NUMERICSERV service must be a port number string Performance

Why SO_REUSEADDR Is Essential

When a TCP server closes, the OS keeps the port in a TIME_WAIT state for about 60-120 seconds (to handle any late-arriving packets). Without SO_REUSEADDR, if you restart your server during that window, bind() fails with EADDRINUSE — “Address already in use”. This is extremely annoying during development.

/* Without SO_REUSEADDR — this happens during development: */
$ ./seqnum_sv
Server ready on port 54321
^C
$ ./seqnum_sv
bind: Address already in use   <-- Frustrating!

/* With SO_REUSEADDR (as in inetListen): */
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
/* Now restart works immediately */

/* SO_REUSEADDR allows: */
/* 1. Binding to a port in TIME_WAIT state */
/* 2. Multiple sockets bound to same port (with SO_REUSEPORT on Linux) */
/* NOTE: SO_REUSEADDR does NOT bypass "port already in use" if another  */
/*       live process is actively listening on that port.                */

TCP Connection States After Server Close
ESTABLISHED
(server running)
FIN_WAIT
(closing)
TIME_WAIT
60-120s
Without SO_REUSEADDR: bind() fails while in TIME_WAIT.
With SO_REUSEADDR: bind() succeeds immediately.

Interview Questions & Answers

Q1. What does getaddrinfo() do and why use it over gethostbyname()?

getaddrinfo() resolves a hostname and service name into a list of socket addresses ready to use with socket() and connect()/bind(). It supports both IPv4 and IPv6 (unlike gethostbyname() which is IPv4-only), handles both name resolution and service lookup in one call, and is thread-safe (gethostbyname uses global state).

Q2. What is AI_PASSIVE and when do you use it?

AI_PASSIVE tells getaddrinfo() that you’re setting up a server socket. When used with node=NULL, it returns an address with INADDR_ANY (0.0.0.0 for IPv4), meaning “accept connections on all network interfaces”. Without AI_PASSIVE, NULL resolves to loopback (127.0.0.1), which only accepts local connections.

Q3. Why does inetListen() loop over the results from getaddrinfo()?

getaddrinfo() can return multiple addresses — for example, both an IPv4 and an IPv6 address for a hostname, or addresses from multiple DNS records. The library tries each one with socket() and bind() until one succeeds. This makes the code robust across different network environments without hardcoding address families.

Q4. Why is SO_REUSEADDR set on server sockets?

When a TCP server closes a connection, the port enters TIME_WAIT state for 60-120 seconds. Without SO_REUSEADDR, restarting the server during this window fails with EADDRINUSE. Setting SO_REUSEADDR before bind() allows reuse of a port in TIME_WAIT. It does NOT let you steal a port that another active server is using.

Q5. What is the purpose of freeaddrinfo()?

getaddrinfo() dynamically allocates a linked list of struct addrinfo results. You must call freeaddrinfo(result) when done to release this memory. Forgetting it causes a memory leak. Common mistake: calling freeaddrinfo() before finishing with the address data.

Q6. What is the difference between gai_strerror() and strerror()?

getaddrinfo() returns its own error codes (EAI_NONAME, EAI_FAIL, etc.) which are NOT errno values. strerror() only understands errno codes. Use gai_strerror(rc) to get human-readable error strings for getaddrinfo failures.

Q7. What is the listen() backlog parameter?

The backlog specifies the maximum number of pending (not yet accepted) connections the kernel will queue for a listening socket. If a client connects and the server hasn’t called accept() yet, the kernel holds it in this queue. Once the queue is full, new connection attempts are silently dropped or get a RST. Typical values: 5 to 128. On Linux, the actual limit is also capped by /proc/sys/net/core/somaxconn.

Q8. Can inetConnect() handle both IPv4 and IPv6 hostnames?

Yes. By setting hints.ai_family = AF_UNSPEC, getaddrinfo() returns both IPv4 and IPv6 addresses if available. The loop tries each address in order until one connects successfully. This makes the client automatically prefer IPv6 when available (since it’s typically returned first) without any code changes.

Leave a Reply

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