C & Networking · tool

Byte order converter: what htons and htonl do to your bytes

Type a number and see its bytes in host order and in network order. Below the converter: what endianness means, the four conversion functions, three ways to check a machine and the port bug they prevent.

Runsin your browser, nothing is sent
DatePublished Nov 05, 2026
Tested withgcc 14.4.0 with -Wall -Wextra (Docker image gcc:14), glibc 2.41, Debian 13.7, x86_64; lscpu from util-linux 2.39.3 and Python 3.12.3 on Ubuntu 24.04.4 LTS
Bridge at night with traffic trails flowing in both directions (AI-generated image)
AI-generated image

A 16-bit or 32-bit integer occupies two or four bytes, and CPUs disagree about which of those bytes comes first in memory. The byte order converter below shows both layouts for any value you enter, together with the result of htons() or htonl().

Byte order converter

Little-endian host (x86, ARM)
Network order (big-endian)
Result of the call
Read back without ntoh*
Binary

What endianness means

Endianness is the order in which a CPU stores the bytes of a multi-byte number. Take the 32-bit value 0x12345678. A big-endian machine puts 12 at the lowest address, then 34, 56, 78, in the order a person reads the hex digits. A little-endian machine stores 78 56 34 12, least significant byte first.

OrderBytes of 0x12345678, lowest address firstTypical CPUs
Big-endian12 34 56 78SPARC, IBM z/Architecture, network protocols
Little-endian78 56 34 12x86, x86-64, ARM and RISC-V in their usual modes

Neither is better. The difference only shows when bytes leave one machine and are read by another, or when code looks at an integer through a char pointer.

Network byte order and the four functions

The Internet protocols fixed one order for header fields: big-endian, also called network byte order. Port numbers in TCP and UDP headers and addresses in IPv4 headers all travel that way, so a program on a little-endian CPU has to swap the bytes before writing them into a sockaddr. That is what these functions from arpa/inet.h do:

  • htons(): host to network, 16 bits (ports)
  • htonl(): host to network, 32 bits (IPv4 addresses)
  • ntohs() and ntohl(): the reverse, for values read from the network

On a big-endian CPU all four return their argument unchanged. The program below prints the bytes before and after conversion, using the documentation address 192.0.2.1:

bytes.c
#include <arpa/inet.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

static void dump(const char *label, const void *p, size_t n)
{
    const unsigned char *b = p;
    printf("%-18s", label);
    for (size_t i = 0; i < n; i++)
        printf(" %02x", b[i]);
    putchar('\n');
}

int main(void)
{
    uint16_t port = 8080;            /* 0x1f90 */
    uint32_t addr = 0xc0000201;      /* 192.0.2.1 */

    uint16_t nport = htons(port);
    uint32_t naddr = htonl(addr);

    dump("8080 host:", &port, sizeof port);
    dump("8080 htons:", &nport, sizeof nport);
    dump("192.0.2.1 host:", &addr, sizeof addr);
    dump("192.0.2.1 htonl:", &naddr, sizeof naddr);

    printf("ntohs(htons(8080)) = %u\n", (unsigned)ntohs(nport));
    printf("forgot htons: peer sees port %u\n", (unsigned)ntohs(port));
    return 0;
}

Output on x86-64:

output
8080 host:         90 1f
8080 htons:        1f 90
192.0.2.1 host:    01 02 00 c0
192.0.2.1 htonl:   c0 00 02 01
ntohs(htons(8080)) = 8080
forgot htons: peer sees port 36895
The 32-bit value 0x0A0B0C0D in memory: a little-endian host stores 0D 0C 0B 0A from the lowest address, network byte order stores 0A 0B 0C 0D. address a+0a+1a+2a+3 little-endianx86-64, most ARM 0D 0C 0B 0A 0A 0B 0C 0D network orderbig-endian, on the wire 0x0A0B0C0D · navy = most significant byte The 32-bit value 0x0A0B0C0D in memory: a little-endian host stores 0D 0C 0B 0A from the lowest address, network byte order stores 0A 0B 0C 0D. little-endianx86-64, most ARM 0Da+0 0Ca+1 0Ba+2 0Aa+3 0Aa+0 0Ba+1 0Ca+2 0Da+3 network orderbig-endian, on the wire 0x0A0B0C0D · a+n = addressnavy = most significant byte
One 32-bit value, two byte orders.

The classic bug: port 8080 becomes 36895

Fill a struct sockaddr_in by hand, write addr.sin_port = 8080; and the server binds without any error. It just listens on the wrong port. In memory the value is 90 1F; the kernel reads that field as network order, so it sees 0x901F, which is 36895 in decimal. The last line of the output above computes exactly that, and so does the byte order converter's "Read back without ntoh*" line for its default value.

The client usually makes the matching mistake and connects to 36895 as well, so everything works until a correct client, or a tool such as nc, tries port 8080 and gets "Connection refused". getaddrinfo() returns the port already converted, which is how the socket programming guide builds its server.

Row of dominoes on a glass surface, the first ones falling (AI-generated image)
AI-generated image

How to check your machine

The byte order converter assumes a little-endian host, which fits x86 and ARM in their usual modes. To confirm what a given machine does, lscpu from util-linux reports it directly:

shell
$ lscpu | grep -i endian
Byte Order:                              Little Endian

Python exposes the same fact in the sys module:

shell
$ python3 -c "import sys; print(sys.byteorder)"
little

In C, store 1 in a uint32_t and look at the byte at the lowest address. memcpy() into an unsigned char is the clean way to do it; a union works with gcc as well, but memcpy() avoids any argument about aliasing rules.

endian.c
#include <stdint.h>
#include <stdio.h>
#include <string.h>

int main(void)
{
    uint32_t x = 1;
    unsigned char first;
    memcpy(&first, &x, 1);   /* look at the byte at the lowest address */
    puts(first == 1 ? "little-endian" : "big-endian");
    return 0;
}
shell
$ gcc -Wall -Wextra -o endian endian.c && ./endian
little-endian

Floats are not covered

htonl() takes an integer. Passing a float converts its value to an integer first, so 3.75 arrives as 3, and gcc 14 with -Wall -Wextra gave no warning. Copying the bits through memcpy() instead, as the FAQ below describes, relies on both machines using IEEE 754. Nearly every current CPU does, but the C standard does not promise it; the IEEE 754 page covers the bit layout.

A last question the converter cannot answer: what about a struct written to a socket with one send() call? Byte order is only half the problem there, because the compiler may also insert padding between fields, and two compilers need not agree on it.

Last stops · FAQ

Questions people ask

Is network byte order big-endian or little-endian?

Big-endian. The Internet protocols send the most significant byte of a multi-byte field first, so port 8080 (hex 1F90) travels as 1F followed by 90. IPv4 addresses in the IP header go out the same way. On a little-endian host, htons() and htonl() put the bytes into that order before they reach a sockaddr.

What is the difference between htons and htonl?

The letter at the end gives the width. htons() converts a 16-bit short, used for port numbers. htonl() converts a 32-bit long in the old naming, which today means uint32_t, used for IPv4 addresses and 32-bit protocol fields. There is no standard 64-bit version in POSIX; glibc offers htobe64() in endian.h for that.

Is calling htons twice a bug?

Yes. On a little-endian machine the second call swaps the bytes back, so the value goes out in host order again. The bug is easy to miss because on a big-endian machine both calls do nothing and the code works. Convert exactly once, at the point where the value enters a struct or buffer that goes on the wire.

How can a float be sent in network byte order?

There is no htonf(). Copy the float into a uint32_t with memcpy(), convert that integer with htonl(), send it, and reverse the steps on the other side. A double goes the same way through a uint64_t and glibc's htobe64(). The IEEE 754 converter shows which bits a float contains.

Next stop

Where this line continues