Linux Sockets Binding a Socket โ€“ bind() & Generic sockaddr Structure

 

Linux Sockets โ€“ Chapter 56
Part 3: Binding a Socket โ€“ bind() & Generic sockaddr Structure
๐Ÿ“Œ bind()
๐Ÿ  Well-known Address
๐Ÿงฑ struct sockaddr

Sections 56.3 & 56.4 โ€“ bind() and struct sockaddr

After creating a socket with socket(), the socket has no address. It is like a telephone handset that is not yet connected to a phone number. The bind() system call assigns an address to the socket โ€” it gives it an identity that other processes can use to reach it.

The struct sockaddr structure is the generic address type that the sockets API uses so that a single set of system calls works across all socket domains.

Key Terms in This Part

bind() struct sockaddr sa_family socklen_t well-known address ephemeral port getsockname() address family domain-specific struct

bind() โ€“ Function Signature
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

/* Returns: 0 on success, -1 on error */
Argument Type Purpose
sockfd int File descriptor returned by socket()
addr const struct sockaddr * Pointer to a domain-specific address structure, cast to generic sockaddr *
addrlen socklen_t Size in bytes of the address structure pointed to by addr

What bind() Does โ€“ The Phone Number Analogy
๐Ÿ“ž
Socket Created
No address yet
(phone with no number)
โ†’
๐Ÿ“ฒ
bind() Called
Address assigned
(phone number registered)

After bind(), the socket has a known address. Client processes can now use that address to connect or send data to this socket.

Typically a server calls bind() to register a well-known address (a fixed, publicly advertised address like port 80 for HTTP). A client usually does NOT call bind() โ€” the kernel automatically assigns an ephemeral (temporary) port to the client’s socket when it calls connect() or sends its first datagram.

Well-Known Address vs Ephemeral Port
๐Ÿข Well-Known Address (Server)
  • Fixed, advertised in advance
  • Server calls bind() explicitly
  • Clients know this address to connect
  • Example: HTTP server always binds to port 80
๐ŸŽฒ Ephemeral Port (Client)
  • Temporary port assigned by the kernel
  • Client usually skips bind()
  • Kernel picks an unused port automatically
  • Port is released when socket closes

A server can also skip bind() and call listen() directly. The kernel then assigns an ephemeral port to the server. The server must then use getsockname() to find out which port it got, and publish that information (e.g., register with a directory service like Sun RPC portmapper) so clients can find it.

The Generic Address Structure โ€“ struct sockaddr

Different socket domains use different address formats. For example:

  • AF_UNIX uses a filesystem pathname (e.g., /tmp/mysocket)
  • AF_INET uses a 4-byte IP address + 2-byte port number
  • AF_INET6 uses a 16-byte IPv6 address + port number

Since system calls like bind() and connect() must work with all domains, the sockets API defines a single generic address type:

struct sockaddr {
    sa_family_t  sa_family;   /* Address family: AF_UNIX, AF_INET, etc. */
    char         sa_data[14]; /* Address data (size varies by domain) */
};

This structure is just a template. You never fill it in directly. Instead, you fill in the domain-specific structure (e.g., struct sockaddr_in for AF_INET) and then cast its pointer to (struct sockaddr *) when passing to system calls.

Generic Type Domain-Specific Type Domain Key Fields
struct sockaddr
(cast target)
struct sockaddr_un AF_UNIX sun_family, sun_path[108]
struct sockaddr_in AF_INET sin_family, sin_port, sin_addr
struct sockaddr_in6 AF_INET6 sin6_family, sin6_port, sin6_addr

The first field in every domain-specific structure is always a family field (sun_family, sin_family, etc.) that matches the sa_family field of the generic struct sockaddr. The kernel reads this field to determine which type of address it is dealing with.

Why the (struct sockaddr *) Cast?

This is a common source of confusion for beginners. Here is the explanation:

The bind() function prototype takes a const struct sockaddr * as its second argument. But you fill in a domain-specific structure like struct sockaddr_in. Since C does not allow implicit pointer type conversion, you must explicitly cast:

struct sockaddr_in addr;
/* ... fill in addr ... */
bind(sockfd, (struct sockaddr *) &addr, sizeof(addr));

The cast is safe because all domain-specific address structures are designed to start with the same sa_family_t field at the same offset. The kernel uses that field to interpret the rest of the structure correctly.

struct sockaddr_in (AF_INET) โ€“ memory layout:
sin_family
2 bytes
sin_port
2 bytes
sin_addr (IP)
4 bytes
sin_zero (padding)
8 bytes
struct sockaddr (generic) โ€“ same memory start:
sa_family
2 bytes โ† same offset
sa_data[14]
14 bytes (rest of address)
โ†‘ The kernel reads sa_family first, then interprets the rest based on the domain.

Coding Examples

Example 1: Binding a TCP server to port 8080

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>   /* struct sockaddr_in, htons(), INADDR_ANY */

int main(void)
{
    int sockfd;
    struct sockaddr_in server_addr;

    /* Step 1: Create socket */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket"); exit(1); }

    /* Step 2: Fill in address structure */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family      = AF_INET;       /* IPv4 */
    server_addr.sin_port        = htons(8080);   /* port 8080, network byte order */
    server_addr.sin_addr.s_addr = INADDR_ANY;    /* accept on all interfaces */

    /* Step 3: Bind โ€“ note the cast to (struct sockaddr *) */
    if (bind(sockfd,
             (struct sockaddr *) &server_addr,
             sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }

    printf("Socket bound to port 8080\n");
    close(sockfd);
    return 0;
}

Example 2: Binding a UNIX domain socket

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>    /* struct sockaddr_un */
#include <unistd.h>

#define SOCKET_PATH "/tmp/my_ipc_socket"

int main(void)
{
    int sockfd;
    struct sockaddr_un addr;

    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket"); exit(1); }

    /* Remove any existing socket file */
    unlink(SOCKET_PATH);

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);

    if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(1);
    }

    printf("UNIX domain socket bound to %s\n", SOCKET_PATH);
    close(sockfd);
    unlink(SOCKET_PATH);  /* cleanup */
    return 0;
}

Example 3: Server skips bind() โ€“ kernel assigns ephemeral port

#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 assigned_addr;
    socklen_t addr_len = sizeof(assigned_addr);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) { perror("socket"); exit(1); }

    /*
     * Skip bind() โ€” call listen() directly.
     * Kernel assigns an ephemeral port.
     */
    if (listen(sockfd, 5) == -1) { perror("listen"); exit(1); }

    /* Use getsockname() to find out which port the kernel assigned */
    if (getsockname(sockfd,
                    (struct sockaddr *) &assigned_addr,
                    &addr_len) == -1) {
        perror("getsockname");
        exit(1);
    }

    printf("Kernel assigned port: %d\n", ntohs(assigned_addr.sin_port));
    close(sockfd);
    return 0;
}

๐ŸŽฏ Interview Questions โ€“ bind() & struct sockaddr
Q1. What does bind() do?bind() assigns an address to an unbound socket. After bind(), other processes know the socket’s address and can connect to it or send it data. Without bind(), a socket has no address โ€” the kernel assigns one automatically when needed (for clients).

Q2. Why does bind() take a (struct sockaddr *) even though you fill in a struct sockaddr_in?Because bind() is a generic system call that works with all socket domains. Each domain has its own address structure with different sizes and fields. The sockets API uses struct sockaddr as a common base type. The caller fills a domain-specific structure and casts its pointer to (struct sockaddr *). The kernel uses the sa_family field at the start of every structure to interpret the rest of the address correctly.

Q3. What is a well-known address?A well-known address is a fixed, pre-advertised address that clients already know before they try to connect. For example, HTTP servers traditionally bind to port 80. Servers call bind() to register this fixed address so clients can find them.

Q4. What is an ephemeral port?An ephemeral port is a temporary port number assigned automatically by the kernel when a socket has not explicitly called bind(). Clients typically rely on ephemeral ports because the server does not need to know the client’s port in advance. The kernel picks an available port from the ephemeral range (typically 32768โ€“60999 on Linux).

Q5. Can a server skip bind() and still accept connections?Yes. A server can call listen() without first calling bind(). The kernel assigns an ephemeral port. The server then calls getsockname() to find out which port was assigned and must publish this address through some directory service (like a portmapper) so clients can locate it. This approach is less common because clients need a way to discover the server’s address.

Q6. What is socklen_t?socklen_t is an integer type specified by POSIX/SUSv3 for expressing the size of socket address structures. It is used as the type of the addrlen argument in bind(), accept(), getsockname(), getpeername(), and similar calls. On Linux it is typedef’d to unsigned int.

Q7. What is the difference between struct sockaddr_in and struct sockaddr_in6?sockaddr_in is used for AF_INET (IPv4) and holds a 4-byte IP address and a 2-byte port. sockaddr_in6 is used for AF_INET6 (IPv6) and holds a 16-byte IPv6 address, a 2-byte port, a flow label, and a scope ID. Both start with a sin_family field and are cast to (struct sockaddr *) when passed to socket system calls.

Q8. Why do we use htons() when setting a port number?htons() converts a 16-bit value from host byte order to network byte order (big-endian). Network protocols mandate big-endian byte ordering. On little-endian machines (x86, ARM), the host byte order is little-endian, so htons() is needed. On big-endian machines it is a no-op, but code should always call it for portability.

Next: Stream Sockets โ€“ listen(), connect(), accept()

Learn the complete connection flow for stream sockets and the active/passive socket model.

Part 4 โ†’ โ† Part 2

Leave a Reply

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