Skip to content

Fast, header-only, dependency-free, cross platform C++ library that makes working with UDP/TCP sockets and IPv4/IPv6 addresses as easy as possible.

License

Notifications You must be signed in to change notification settings

biaks/ip-sockets-cpp-lite

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

12 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ip-sockets-cpp-lite πŸ”Œ

Build examples

Simple. Lightweight. Cross-platform.

ip-sockets-cpp-lite is a fast, header-only, dependency-free, cross platform C++ library that makes working with UDP/TCP sockets and IPv4/IPv6 addresses straightforward and predictable.

No more endless getaddrinfo calls, confusing sockaddr_in / sockaddr_in6 structures, or platform-specific workarounds.


✨ Why ip-sockets-cpp-lite?

  • πŸ“¦ Header-only β€” just copy headers into your project
  • 🎯 Zero dependencies β€” only the C++ standard library
  • πŸ–₯️ Cross-platform β€” Linux, Windows, macOS
  • πŸš€ Minimal learning curve β€” up and running in minutes
  • πŸͺΆ Lightweight β€” no hidden magic
  • πŸ”’ Predictable behavior β€” explicit control over sockets
  • πŸ“‹ Readable errors β€” clear error codes instead of errno confusion

πŸš€ Quick Example

// Send a UDP packet to a hostname
udp_socket_t<v4, socket_type_e::client> sock (log_e::debug);
ip4_t   ip   = sock.resolve("localhost", nullptr, log_e::debug);
addr4_t addr = {ip, 8080};
sock.open(addr);
sock.send("Hello!", 6);

// Send a TCP message to an IPv6 address
tcp_socket_t<v6, socket_type_e::client> sock6 (log_e::debug);
sock6.open("[::1]:8081");
sock6.send("Hello!", 6);

// Zero-copy overlay on raw memory
uint8_t raw_data[10] = {0x45,0x00,0x00, 0x7f,0x00,0x00,0x01, 0xa8,0x00,0x00};
ip4_t& reint_ip = *reinterpret_cast<ip4_t*>(&raw_data[3]);
std::cout << "ip from raw data: " << reint_ip << std::endl;

The log in the console will be like this:

udp<ip4,client>: [static].resolve() [undefined -> localhost] DNS resolution success, resolved to '127.0.0.1'
udp<ip4,client>: [3].open() [127.0.0.1:50902 <> 127.0.0.1:8080] success
udp<ip4,client>: [3].send() [127.0.0.1:50902 -> 127.0.0.1:8080] sended 6 bytes
tcp<ip6,client>: [4].open() [[::1]:39129 <> [::1]:8081] success
tcp<ip6,client>: [4].send() [[::1]:39129 -> [::1]:8081] sended 6 bytes
ip from raw data: 127.0.0.1

🧠 Design Philosophy

This library provides blocking I/O sockets with configurable timeouts β€” the simplest model that works for most real-world applications.

Keep it simple. Keep it portable.


πŸ”§ Features

🌐 IP Address Handling (ip_address.h)

  • Unified IPv4 and IPv6 API
  • String ↔ binary conversion
  • Network prefixes and masks
  • IPv4-mapped IPv6 support
  • Address + port as a single type
  • Zero-copy overlays on existing buffers
  • Flexible parsing (hex, decimal, dotted)
  • Rich constructors from strings, numbers, and byte arrays

πŸ“‘ UDP Sockets (udp_socket.h)

  • Consistent API across platforms
  • Client and server modes
  • Blocking I/O with timeouts
  • Configurable logging
  • Clear states and error codes

πŸ”Œ TCP Sockets (tcp_socket.h)

  • Simple accept workflow
  • Automatic connection lifecycle
  • API consistent with UDP sockets

πŸ“‹ Requirements

  • C++11 or newer
  • Standard library
  • Nothing else

🏁 Getting Started

πŸ—οΈ Integration

Option 1 β€” Copy headers

  • ip_address.h
  • udp_socket.h
  • tcp_socket.h

Option 2 β€” Use CMake

add_subdirectory(ip-sockets-cpp-lite)
target_link_libraries(your_app ip-sockets-cpp-lite)

Then include what you need:

#include "ip_address.h"  // work only with ipv4/ipv6 addresses
#include "udp_socket.h"  // work with UDP ipv4/ipv6 client/server sockets
#include "tcp_socket.h"  // work with TCP ipv4/ipv6 client/server sockets

🌐 Working with IP Addresses

Create from different formats

// IPv4 addresses - all the common formats
ip4_t ip0 = "192.168.1.1";       // from string
ip4_t ip1 = 0xc0a80101;          // from uint32
ip4_t ip2 = {192, 168, 1, 1};    // from bytes
ip4_t ip3 = "0xc0a80101";        // from hex string
ip4_t ip4 = "3232235777";        // from decimal string
ip4_t ip5 = "127.1";             // 127.0.0.1 (missing bytes are zero)
// IPv6 addresses - all the common formats
ip6_t ip6 = "fe80::";            // link-local address
ip6_t ip7 = "::1";               // loopback
ip6_t ip8 = "64:ff9b::10.0.0.7"; // IPv4-translated (NAT64)
ip6_t ip9 = "11.0.0.1";          // auto IPv4-mapped to ::ffff:11.0.0.1
// Parsing of all variants of input strings always occurs in one pass

Zero-copy overlay on existing memory

uint8_t raw_data[10] = {0x45,0x00,0x00, 0x7f,0x00,0x00,0x01, 0xa8,0x00,0x00};
// Treat memory region as ip4_t without copying!
ip4_t& reint_ip = *reinterpret_cast<ip4_t*>(&raw_data[3]);
std::cout << reint_ip << std::endl; // prints "127.0.0.1"

Network operations

ip4_t ip = "192.168.1.100";
ip4_t mask(24);                         // 255.255.255.0 from prefix
ip4_t network = ip & mask;              // 192.168.1.0

ip6_t ipv6 = "2001:db8::1";
ip6_t ipv6_from_v4 = ip4_t("10.0.0.1"); // ::ffff:10.0.0.1 (IPv4-mapped)

Address + port

addr4_t server  = "192.168.1.100:8080";  // IP and port together
addr6_t server6 = "[2001:db8::1]:8080";
std::cout << server;                     // prints "192.168.1.100:8080"

Two Ways to Use IP and Address Types

// Direct types (when IP version is known)
ip4_t ip4     = "192.168.1.1";      // IPv4
ip6_t ip6     = "2001:db8::1";      // IPv6
addr4_t addr4 = "192.168.1.1:8080"; // IPv4 + port
addr6_t addr6 = "[::1]:8080";       // IPv6 + port

// Generic types (template-friendly)
ip_t<v4> ip4     = "192.168.1.1";      // same as ip4_t
ip_t<v6> ip6     = "2001:db8::1";      // same as ip6_t
addr_t<v4> addr4 = "192.168.1.1:8080"; // same as addr4_t
addr_t<v6> addr6 = "[::1]:8080";       // same as addr6_t

// Perfect for templates!
template <ip_type_e Type>
void print_endpoint(const addr_t<Type>& endpoint) {
    std::cout << "Connecting to: " << endpoint << '\n';
}

πŸ“‘ UDP Client Example

udp_socket_t<v4, socket_type_e::client> sock;

if (sock.open("192.168.1.100:8080") == no_error) {
    sock.send("Hello, world!", 13);

    char buffer[1024];
    int bytes = sock.recv(buffer, sizeof(buffer));
    if (bytes > 0)
        std::cout << "Received: " << std::string(buffer, bytes) << '\n';
}

πŸ–₯️ UDP Server Example

udp_socket_t<v4, socket_type_e::server> server;

if (server.open("0.0.0.0:8080") == no_error) {

    char buffer[1024];
    addr4_t client;

    while (true) {
        int bytes = server.recvfrom(buffer, sizeof(buffer), client);
        if (bytes > 0) {
            std::cout << "Received from " << client << ": "
                      << std::string(buffer, bytes) << '\n';

            server.sendto("OK", 2, client);
        }
    }
}

πŸ”Œ TCP Echo Server

tcp_socket_t<v4, socket_type_e::server> server;

if (server.open("0.0.0.0:8080") == no_error) {

    std::cout << "TCP server listening on port 8080\n";

    while (true) {
        addr4_t client_addr;
        tcp_socket_t<v4, socket_type_e::client> client = server.accept(client_addr);

        if (client.state != state_e::state_opened)
            continue;

        std::cout << "Client connected: " << client_addr << '\n';

        char buffer[1024];
        int bytes = client.recv(buffer, sizeof(buffer));

        if (bytes > 0)
            client.send(buffer, bytes); // echo back

        client.close();
    }
}

πŸ“š More Examples

Check out the examples/ directory for complete working examples:

  • ip_address.cpp - all IP address manipulation features
  • udp_socket.cpp - UDP client-server interaction
  • tcp_socket.cpp - TCP client-server interaction
  • resolve_host.cpp - resolving host to ipv4/ipv6 address example
  • http_server.cpp - compact multi-page HTTP server

πŸ€” Why is this convenient?

Instead of this:

// ...
// ifdef and include hell there for cross platform work
// ...
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);
// ... and 20 more lines to send a single packet

Just write this:

// single include and simple create socket object there
sock.open("192.168.1.100:8080");  // done!
sock.send("Hello", 5);

πŸ€” Why Blocking I/O with Timeouts?

βœ… Cross-platform simplicity

Async APIs differ significantly between platforms. Blocking sockets allow a single portable implementation without platform-specific code paths.

βœ… Linear, readable code

No callbacks, no event loops, no complex state machines. Clean code is better than complex async machinery.

βœ… Threads are cheap, complexity is expensive

Modern systems handle thousands of threads. For most applications, simplicity beats premature optimization.

βœ… Fits most real-world use cases

  • REST clients
  • IoT
  • Game servers
  • Protocol implementations
  • Embedded systems

For very high concurrency workloads, the library can still be used with thread pools or higher-level async frameworks.

πŸ“„ License

MIT License β€” free for personal and commercial use.

❀️ Contributing

Issues, ideas, and pull requests are welcome!


ip-sockets-cpp-lite β€” sockets that don’t scare you.