host→net 16-bit
host→net 32-bit
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.
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!
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 |
|
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.
| 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;
}
#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;
}
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.
