Skip to content

Addresses, Ports and Sockets in C++

Before sending data, identify the destination and the operating-system resource used to reach it. This program prints socket addresses and creates a UDP socket without sending a message.

Before you start

Complete the development setup. Run from the same examples/network directory on the Pi.

Outcome: explain the difference between an address, a port and a socket, then identify who closes the descriptor.

Picture an endpoint

flowchart TB accTitle: A socket and a destination are different things accDescr: The program accesses a kernel socket through a descriptor. A later send specifies a destination by transport protocol, IP address and port. A["C++ program"] -->|uses a descriptor| S["Socket<br/>Operating-system resource"] S -.->|destination for a later send| E["UDP endpoint<br/>127.0.0.1 : 9000"]
Name Meaning in this example
IP address 127.0.0.1 reaches this computer through IPv4 loopback.
Port 9000 identifies the UDP service endpoint on that address.
Socket A kernel communication resource, accessed through an integer descriptor.

TCP port 9000 and UDP port 9000 are separate. Neither a port number nor a descriptor is an IP address.

Build and run

1
2
3
cmake --build --preset native-debug --target addresses
./build/native/debug/addresses 127.0.0.1
./build/native/debug/addresses ::1

The first command prints:

IPv4 address=127.0.0.1 port=9000
UDP socket created; no packets sent

The IPv6 command prints IPv6 address=::1 port=9000 before the same socket message. It inspects an IPv6 address; the demonstration socket itself is deliberately IPv4.

Follow the code

Complete file: app/addresses/main.cpp.

#include <cerrno>
#include <iostream>
#include <netdb.h>
#include <stdexcept>
#include <sys/socket.h>
#include <system_error>
#include <unistd.h>

int main(int argc, char** argv) {
    int fd = -1;
    addrinfo* addresses = nullptr;
    int status = 0;
    try {
        if (argc > 2) {
            throw std::invalid_argument("usage: addresses [host]");
        }
        const char* host = argc == 2 ? argv[1] : "127.0.0.1";
        addrinfo hints{};
        hints.ai_family = AF_UNSPEC;     // Accept IPv4 and IPv6 results.
        hints.ai_socktype = SOCK_DGRAM;  // UDP endpoint addresses.
        hints.ai_flags = AI_NUMERICSERV; // 9000 is a numeric port.
        const int result = ::getaddrinfo(host, "9000", &hints, &addresses);
        if (result != 0) {
            throw std::runtime_error(::gai_strerror(result));
        }
        for (auto* item = addresses; item; item = item->ai_next) {
            char name[NI_MAXHOST]{};
            char service[NI_MAXSERV]{};
            const int error = ::getnameinfo(
                item->ai_addr, item->ai_addrlen, name, sizeof(name), service,
                sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV);
            if (error != 0) {
                throw std::runtime_error(::gai_strerror(error));
            }
            std::cout << (item->ai_family == AF_INET ? "IPv4" : "IPv6")
                      << " address=" << name << " port=" << service << '\n';
        }
        fd = ::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        if (fd < 0) {
            throw std::system_error(errno, std::generic_category(), "socket");
        }
        std::cout << "UDP socket created; no packets sent\n";
    } catch (const std::exception& error) {
        std::cerr << error.what() << '\n';
        status = 1;
    }
    // Explicit cleanup on both success and failure.
    if (addresses) {
        ::freeaddrinfo(addresses);
    }
    if (fd >= 0) {
        ::close(fd);
    }
    return status;
}

getaddrinfo() returns a list of possible socket addresses. AF_UNSPEC allows IPv4 and IPv6, while SOCK_DGRAM asks for datagram endpoints. Numeric loopback inputs work without external DNS; a hostname such as localhost may return several entries according to local configuration.

The returned list needs freeaddrinfo(). The explicit cleanup at the bottom of main() releases it. getnameinfo() formats each endpoint numerically; it does not send a request to that port. These resolver APIs report their own error codes, formatted with gai_strerror(), rather than the socket API's errno.

Release the resources explicitly

socket() returns an integer file descriptor or -1 on failure. The example keeps that integer in fd and calls close(fd) explicitly at the bottom of main(). The same cleanup section runs after either success or the error handler.

The address list has a separate lifetime: freeaddrinfo(addresses) releases it. Both resources start in an empty state so the cleanup section can check whether acquisition succeeded. There is no custom socket class or course header to follow.

Try a different input

./build/native/debug/addresses localhost

The number and order of results can vary. Resolving a name does not prove that a service is listening. A resolver error is different from a failed bind or a receive timeout.

Check your understanding

The program prints port 9000. Has it reserved port 9000?

Compare your reasoning

No. It only resolves and prints an endpoint. Reserving a local UDP endpoint requires binding a socket, which the next lesson demonstrates.

Continue the course

Previous: Development Setup · Course overview · Next: Your First UDP Exchange

Reference: getaddrinfo and its result list.

Donate