Sockets – Internet Domains Network Byte Order – Big Endian vs Little Endian

 

Chapter 59: Sockets – Internet Domains
Part 2 of 4  |  Network Byte Order – Big Endian vs Little Endian
htons()
host→net 16-bit
htonl()
host→net 32-bit
ntohs/ntohl
net→host

Why Does Byte Order Matter in Networking?

IP addresses and port numbers are stored as integers. When you send them across the network, both the sender and receiver must agree on which byte of that integer comes first. Different CPU architectures store integers differently — this is the byte order problem.

To solve this, networking uses a fixed standard called network byte order, which is always big-endian. You must convert your integers to network byte order before putting them into socket address structures.

Key Terms

Big Endian Little Endian Host Byte Order Network Byte Order htons() htonl() ntohs() ntohl() MSB LSB arpa/inet.h uint16_t uint32_t

59.2a – Big Endian vs Little Endian

Consider the 4-byte (32-bit) integer 0x0A0B0C0D. Where does the most significant byte (MSB = 0x0A) get stored in memory?

  • Big Endian: MSB goes to the lowest memory address. MSB first.
  • Little Endian: LSB goes to the lowest memory address. LSB first.

Big-Endian — MSB at lowest address (Network Byte Order)
Address N Address N+1 Address N+2 Address N+3
0x0A
(MSB)
0x0B 0x0C 0x0D
(LSB)
Little-Endian — LSB at lowest address (x86, x86-64)
Address N Address N+1 Address N+2 Address N+3
0x0D
(LSB)
0x0C 0x0B 0x0A
(MSB)

x86 and x86-64 (your desktop/laptop) are little-endian. Most network hardware, ARM (default), MIPS, SPARC are big-endian. Network byte order is big-endian.

The terms “big endian” and “little endian” come from Jonathan Swift’s Gulliver’s Travels (1726), where they refer to political factions who crack their boiled eggs at opposite ends!

59.2b – The Four Byte Order Conversion Functions

These functions are defined in <arpa/inet.h>. The names encode what they do: h=host, n=network, s=short (16-bit), l=long (32-bit).

#include <arpa/inet.h>

/* Host to Network – 16-bit (for port numbers) */
uint16_t htons(uint16_t host_uint16);

/* Host to Network – 32-bit (for IPv4 addresses) */
uint32_t htonl(uint32_t host_uint32);

/* Network to Host – 16-bit */
uint16_t ntohs(uint16_t net_uint16);

/* Network to Host – 32-bit */
uint32_t ntohl(uint32_t net_uint32);

Host Byte Order
(little-endian on x86)
port = 8080
→ htons() / htonl() →
← ntohs() / ntohl() ←
Network Byte Order
(always big-endian)
0x1F90

Full example using all four functions:

#include <stdio.h>
#include <arpa/inet.h>

int main() {
    uint16_t port_host = 8080;
    uint32_t addr_host = 0xC0A80101;  /* 192.168.1.1 in host byte order */

    /* Convert to network byte order */
    uint16_t port_net = htons(port_host);
    uint32_t addr_net = htonl(addr_host);

    printf("Port in host order   : %u (0x%04X)\n", port_host, port_host);
    printf("Port in network order: %u (0x%04X)\n", port_net, port_net);

    printf("Addr in host order   : 0x%08X\n", addr_host);
    printf("Addr in network order: 0x%08X\n", addr_net);

    /* Convert back to host order */
    uint16_t port_back = ntohs(port_net);
    uint32_t addr_back = ntohl(addr_net);

    printf("Port converted back  : %u\n", port_back);
    printf("Addr converted back  : 0x%08X\n", addr_back);

    return 0;
}

Output on a little-endian (x86) machine:

Port in host order   : 8080 (0x1F90)
Port in network order: 36895 (0x901F)   <-- bytes swapped!
Addr in host order   : 0xC0A80101
Addr in network order: 0x0101A8C0       <-- bytes swapped!
Port converted back  : 8080
Addr converted back  : 0xC0A80101

On a big-endian machine, all four functions would be no-ops (they return the value unchanged), but you should always call them anyway so your code is portable.

59.2c – When to Use Each Function
Situation Function to Use Example
Setting a port number in sockaddr_in htons() addr.sin_port = htons(8080);
Hardcoding an IPv4 address (e.g. INADDR_LOOPBACK) htonl() addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
Reading a port from received sockaddr ntohs() port = ntohs(peer.sin_port);
Reading an IPv4 address from received sockaddr ntohl() ip = ntohl(peer.sin_addr.s_addr);

Note: Functions like getaddrinfo() and inet_pton() already return values in network byte order. Do not call htons()/htonl() on their output — it is already in the right order.

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    struct sockaddr_in server;
    int sfd;

    sfd = socket(AF_INET, SOCK_STREAM, 0);

    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;

    /*
     * htons() converts port from host to network byte order.
     * Always required even if the value "looks right" on your machine.
     */
    server.sin_port = htons(80);

    /*
     * INADDR_ANY is defined as 0 in host byte order.
     * htonl(0) = 0, so this is technically a no-op,
     * but it's good practice to always use htonl().
     */
    server.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
        perror("bind");
        return 1;
    }

    printf("Bound to port 80 successfully.\n");
    return 0;
}

Bonus: Detecting Your Machine’s Byte Order at Runtime
#include <stdio.h>

int is_little_endian() {
    unsigned int x = 1;
    /*
     * Cast to char* to look at the first byte.
     * On little-endian: the byte at lowest address is 1 (LSB).
     * On big-endian:    the byte at lowest address is 0 (MSB).
     */
    return *((char *)&x) == 1;
}

int main() {
    if (is_little_endian()) {
        printf("This machine is LITTLE-ENDIAN (e.g. x86).\n");
        printf("htons()/htonl() will swap bytes.\n");
    } else {
        printf("This machine is BIG-ENDIAN.\n");
        printf("htons()/htonl() are no-ops (return value unchanged).\n");
    }
    return 0;
}

Interview Questions – Part 2

Q1. What is network byte order and why is it needed?

Network byte order is big-endian byte ordering. It is needed because different computer architectures store multi-byte integers differently. Without a common standard, a port number written by an x86 sender (little-endian) would be misread by a big-endian receiver. All IP headers and socket addresses use network byte order.

Q2. What does htons() stand for and what does it do?

host to network short. It converts a 16-bit integer from host byte order to network byte order. It is used specifically for port numbers, which are 16-bit values stored in sin_port.

Q3. If I don’t call htons() when setting sin_port, what happens?

On a little-endian machine (like x86), the port bytes will be stored in the wrong order. For example, port 8080 (0x1F90) would be sent as 0x901F, which is port 36895. The connection will go to the wrong port or fail to connect.

Q4. Why are htons/htonl defined as no-ops on some machines?

On big-endian machines, the host byte order is already the same as network byte order. So no byte swapping is needed — the functions simply return the input unchanged. This is why you should always call these functions: they do the right thing on all architectures.

Q5. What is the data type used for a 16-bit port number in network socket code?

uint16_t (from <stdint.h>) or equivalently in_port_t. It is a guaranteed 16-bit unsigned integer, regardless of platform. This replaces the older use of unsigned short, which could be different sizes on different systems.

Q6. Do I need to convert the result of getaddrinfo() or inet_pton() with htons()?

No. Both getaddrinfo() and inet_pton() already return addresses and port information in network byte order. Applying htons()/htonl() again would incorrectly swap the bytes a second time.

Q7. How would you check at runtime whether your machine is big-endian or little-endian?

Store the integer 1 in a variable, then cast a pointer to it as char* and read the first byte. If it is 1, the machine is little-endian (LSB at lowest address). If it is 0, the machine is big-endian.

Next: Data Representation & Marshalling

Learn how to handle different data type sizes across heterogeneous systems and standard marshalling formats.

Part 3 → Data Representation ← Part 1: Domain Sockets

Leave a Reply

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