Socket#

Join provides a comprehensive socket API for network programming. Sockets are template classes parameterized by protocol type and integrate seamlessly with the Join reactor.

Sockets are:

  • non‑blocking by default
  • protocol‑agnostic — work with any protocol
  • event‑driven — integrate with the reactor
  • type‑safe — compile‑time protocol checking

Three socket hierarchies are available:

  • BasicSocket — base socket class
  • BasicDatagramSocket — connectionless datagram sockets
  • BasicStreamSocket — connection‑oriented stream sockets
  • BasicTlsSocket — encrypted stream sockets with TLS/SSL

Socket modes#

Sockets can operate in blocking or non‑blocking mode.

Non‑blocking mode (default)#

Operations return immediately with an error if they would block.

#include <join/socket.hpp>

using join;

Tcp::Socket socket;  // Non-blocking by default

Blocking mode#

Operations wait until they complete or timeout.

#include <join/socket.hpp>

using join;

Tcp::Socket socket(BasicSocket<Tcp>::Mode::Blocking);

You can change the mode at runtime:

socket.setMode(BasicSocket<Tcp>::Mode::NonBlocking);

BasicSocket#

The base socket class providing fundamental socket operations.

Opening a socket#

#include <join/socket.hpp>

using join;

Tcp protocol;
BasicSocket<Tcp> socket;

if (socket.open(protocol) == -1)
{
    // Handle error
}

Binding to an endpoint#

Tcp::Endpoint endpoint("127.0.0.1", 8080);

if (socket.bind(endpoint) == -1)
{
    // Handle error
}

Reading and writing#

char buffer[1024];

// Read data
int bytesRead = socket.read(buffer, sizeof(buffer));

// Write data
const char* data = "Hello";
int bytesWritten = socket.write(data, 5);

Waiting for I/O readiness#

// Wait until data is available for reading
if (socket.waitReadyRead(5000))
{  // 5 second timeout
    int available = socket.canRead();
}

// Wait until socket is ready for writing
if (socket.waitReadyWrite(5000))
{
    socket.write(data, size);
}

BasicDatagramSocket#

Connectionless datagram sockets for UDP, ICMP, and Unix datagram protocols.

Creating a UDP socket#

#include <join/socket.hpp>

using join;

Udp::Socket socket;
Udp::Endpoint local("0.0.0.0", 8080);

socket.bind(local);

Sending and receiving datagrams#

// Receive from any endpoint
char buffer[1024];
Udp::Endpoint remote;
int bytesRead = socket.readFrom(buffer, sizeof(buffer), &remote);

// Send to specific endpoint
Udp::Endpoint dest("192.168.1.100", 9000);
socket.writeTo(data, size, dest);

Connected datagram socket#

Datagrams can be “connected” to a specific peer:

Udp::Socket socket;
Udp::Endpoint remote("192.168.1.100", 9000);

socket.connect(remote);

// Now use regular read/write
socket.write(data, size);
socket.read(buffer, sizeof(buffer));

// Disconnect when done
socket.disconnect();

Binding to a network interface#

socket.bindToDevice("eth0");

Multicast options#

// Set TTL for multicast packets
socket.setOption(BasicSocket<Udp>::Option::MulticastTtl, 10);

// Enable/disable multicast loopback
socket.setOption(BasicSocket<Udp>::Option::MulticastLoop, 1);

BasicStreamSocket#

Connection‑oriented stream sockets for TCP and Unix stream protocols.

Creating a TCP client#

#include <join/socket.hpp>

using join;

Tcp::Socket socket;
Tcp::Endpoint server("example.com", 80);

if (socket.connect(server) == -1)
{
    // Handle error
}

// Wait for connection to complete (non-blocking mode)
if (socket.waitConnected(5000))
{
    // Connected
}

Reading and writing streams#

// Regular read/write
socket.write(data, size);
socket.read(buffer, sizeof(buffer));

// Read exact number of bytes
if (socket.readExactly(buffer, 100, 5000) == 0)
{
    // Successfully read 100 bytes
}

// Write all data
if (socket.writeExactly(data, size, 5000) == 0)
{
    // All data written
}

Graceful disconnect#

// Initiate shutdown
socket.disconnect();

// Wait for shutdown to complete
if (socket.waitDisconnected(5000))
{
    // Connection closed gracefully
}

TCP options#

// Disable Nagle's algorithm
socket.setOption(BasicSocket<Tcp>::Option::NoDelay, 1);

// Enable TCP keepalive
socket.setOption(BasicSocket<Tcp>::Option::KeepAlive, 1);
socket.setOption(BasicSocket<Tcp>::Option::KeepIdle, 60);
socket.setOption(BasicSocket<Tcp>::Option::KeepIntvl, 10);
socket.setOption(BasicSocket<Tcp>::Option::KeepCount, 5);

BasicTlsSocket#

Encrypted stream sockets using OpenSSL for TLS/SSL connections.

Creating a TLS client#

#include <join/socket.hpp>

using join;

Tls::Socket socket;
Tls::Endpoint server("example.com", 443);

// Connect and encrypt in one step
if (socket.connectEncrypted(server) == -1)
{
    // Handle error
}

// Wait for TLS handshake to complete
if (socket.waitEncrypted(5000))
{
    // TLS connection established
}

Upgrading existing connection (STARTTLS)#

Tcp::Socket plainSocket;
plainSocket.connect(server);

// ... exchange plaintext ...

// Upgrade to TLS
Tls::Socket tlsSocket(std::move(plainSocket));
tlsSocket.startEncryption();
tlsSocket.waitEncrypted(5000);

Certificate verification#

// Enable peer verification
socket.setVerify(true);

// Set trusted CA certificates
socket.setCaFile("/etc/ssl/certs/ca-bundle.crt");
socket.setCaPath("/etc/ssl/certs/");

⚠️ By default, peer verification is disabled.

Setting client certificate#

// Set certificate and private key
if (socket.setCertificate("client.pem", "client-key.pem") == -1)
{
    // Handle error
}

Configuring cipher suites#

// TLSv1.2 and below
socket.setCipher("ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256");

// TLSv1.3
socket.setCipher_1_3("TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256");

Custom TLS context#

// Create shared context
join::SslCtxPtr context(SSL_CTX_new(TLS_client_method()));
SSL_CTX_set_options(context.get(), SSL_OP_NO_TLSv1);

// Create multiple sockets with same context
Tls::Socket socket1(context);
Tls::Socket socket2(context);

Reading and writing encrypted data#

// Same API as BasicStreamSocket
socket.write(data, size);
socket.read(buffer, sizeof(buffer));

// Check encryption status
if (socket.encrypted())
{
    // Connection is encrypted
}

Socket options#

Common options available across socket types:

using Option = BasicSocket<Protocol>::Option;

// Socket‑level options
socket.setOption(Option::ReuseAddr, 1);      // Allow address reuse
socket.setOption(Option::ReusePort, 1);      // Allow port reuse
socket.setOption(Option::SndBuffer, 65536);  // Set send buffer size
socket.setOption(Option::RcvBuffer, 65536);  // Set receive buffer size
socket.setOption(Option::Broadcast, 1);      // Allow broadcast (UDP)
socket.setOption(Option::TimeStamp, 1);      // Receive timestamps

// IP‑level options (datagram sockets)
socket.setOption(Option::Ttl, 64);           // Set packet TTL
socket.setOption(Option::PathMtuDiscover, 1); // Enable PMTU discovery
socket.setOption(Option::RcvError, 1);        // Receive ICMP errors

// TCP‑level options (stream sockets)
socket.setOption(Option::NoDelay, 1);        // Disable Nagle
socket.setOption(Option::KeepAlive, 1);      // Enable keepalive

Socket state inspection#

Check socket state#

if (socket.opened())
{
    // Socket is open
}

if (socket.connected())
{
    // Socket is connected
}

if (socket.connecting())
{
    // Socket is in connecting state
}

if (socket.encrypted())
{
    // Socket has TLS encryption (TlsSocket only)
}

Query endpoints#

// Get local endpoint
auto local = socket.localEndpoint();
std::cout << local.ip() << ":" << local.port() << std::endl;

// Get remote endpoint (datagram/stream sockets)
auto remote = socket.remoteEndpoint();

Protocol information#

int family = socket.family();      // AF_INET, AF_INET6, AF_UNIX
int type = socket.type();          // SOCK_STREAM, SOCK_DGRAM
int proto = socket.protocol();     // IPPROTO_TCP, IPPROTO_UDP

MTU discovery#

// Get path MTU
int mtu = socket.mtu();

Error handling#

Join uses std::error_code for error reporting through the global lastError variable.

if (socket.connect(endpoint) == -1)
{
    if (lastError == std::errc::operation_in_progress)
    {
        // Non-blocking connect in progress
    }
    else if (lastError == Errc::ConnectionClosed)
    {
        // Connection closed by peer
    }
    else
    {
        std::cerr << "Error: " << lastError.message() << std::endl;
    }
}

Common error codes#

  • Errc::TemporaryError — operation would block (try again)
  • Errc::ConnectionClosed — peer closed connection
  • Errc::TimedOut — operation timed out
  • Errc::InUse — resource already in use
  • TlsErrc::TlsCloseNotifyAlert — TLS close notify received
  • TlsErrc::TlsProtocolError — TLS protocol error

Checksum calculation#

Static utility for computing checksums (useful for ICMP):

uint16_t sum = BasicSocket<Protocol>::checksum(
    reinterpret_cast<const uint16_t*>(data),
    length
);

Protocol examples#

UDP echo server#

Udp::Socket server;
Udp::Endpoint local("0.0.0.0", 9000);
server.bind(local);

char buffer[1024];
Udp::Endpoint client;

while (true)
{
    int n = server.readFrom(buffer, sizeof(buffer), &client);
    if (n > 0)
    {
        server.writeTo(buffer, n, client);
    }
}

TCP echo server#

Tcp::Acceptor acceptor;
Tcp::Endpoint local("0.0.0.0", 9000);
acceptor.listen(local);

Tcp::Socket client = acceptor.accept();
char buffer[1024];

while (true)
{
    int n = client.read(buffer, sizeof(buffer));
    if (n <= 0) break;
    client.writeExactly(buffer, n);
}

HTTPS GET request#

Tls::Socket socket;
socket.setVerify(true);
socket.setCaFile("/etc/ssl/certs/ca-bundle.crt");

Tls::Endpoint server("example.com", 443);
socket.connectEncrypted(server);
socket.waitEncrypted(5000);

std::string request =
    "GET / HTTP/1.1\r\n"
    "Host: example.com\r\n"
    "Connection: close\r\n\r\n";

socket.writeExactly(request.c_str(), request.size());

char buffer[4096];
while (socket.read(buffer, sizeof(buffer)) > 0)
{
    // Process response
}

Best practices#

  • Always check return values for -1 errors
  • Use non‑blocking mode with the reactor for scalable I/O
  • Use blocking mode for simple synchronous operations
  • Enable TCP_NODELAY for low‑latency applications
  • Enable peer verification for TLS clients in production
  • Use readExactly/writeExactly for framing protocols
  • Handle TemporaryError by calling wait methods
  • Call disconnect() before closing stream sockets for graceful shutdown
  • Reuse TLS contexts when creating multiple secure connections

Summary#

Socket TypeUse CaseProtocol Examples
BasicSocketRaw socket operationsAny
BasicDatagramSocketConnectionless communicationUDP, ICMP, UnixDgram
BasicStreamSocketConnection‑oriented streamsTCP, UnixStream
BasicTlsSocketEncrypted communicationTLS, HTTPS, SMTPS
FeatureSupported
Non‑blocking I/O
Blocking I/O
IPv4/IPv6
Unix domain sockets
TLS/SSL encryption
Certificate verify
Reactor integration
Move semantics