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
#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 |
(phone with no number)
(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.
- Fixed, advertised in advance
- Server calls
bind()explicitly - Clients know this address to connect
- Example: HTTP server always binds to port 80
- 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.
Different socket domains use different address formats. For example:
AF_UNIXuses a filesystem pathname (e.g.,/tmp/mysocket)AF_INETuses a 4-byte IP address + 2-byte port numberAF_INET6uses 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.
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.
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;
}
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.
