How to Use ASIO for Inter-Process Communication (IPC): A Complete Guide to Local Sockets

ASIO enables inter-process communication on POSIX systems through UNIX domain sockets via the asio::local namespace, offering stream, datagram, and sequenced-packet protocols that use the same asynchronous I/O model as TCP/IP sockets.

The chriskohlhoff/asio repository provides a header-only C++ library that abstracts operating system IPC mechanisms into a unified, portable API. Whether you need reliable byte-stream communication or message-oriented data transfer, ASIO's local socket protocols in include/asio/local/ allow you to leverage the same io_context, executors, and asynchronous operations used for network programming.

Understanding ASIO's Local Socket Protocols

ASIO implements three distinct UNIX domain socket protocols in the asio::local namespace. Each protocol header declares an endpoint type, socket class, and associated I/O objects that follow the same patterns as their IP counterparts.

Stream Protocol (Connection-Oriented)

The stream_protocol declared in [include/asio/local/stream_protocol.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/local/stream_protocol.hpp) provides reliable, bidirectional byte-stream communication similar to TCP. It uses stream_protocol::socket and stream_protocol::acceptor classes, with endpoints defined by filesystem paths.

Datagram Protocol (Message-Oriented)

The datagram_protocol declared in [include/asio/local/datagram_protocol.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/local/datagram_protocol.hpp) offers message-boundary preservation like UDP. This protocol is ideal for discrete command or event messages where maintaining packet boundaries is critical.

Sequenced-Packet Protocol (Reliable Frames)

The seq_packet_protocol declared in [include/asio/local/seq_packet_protocol.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/local/seq_packet_protocol.hpp) combines reliability with message boundaries, corresponding to SOCK_SEQPACKET sockets. It ensures that messages are delivered in order and retrieved in the same discrete chunks they were sent.

Building an IPC Server with stream_protocol

To establish a server, create an endpoint using a filesystem path, instantiate an acceptor bound to that endpoint, and accept incoming connections. The server must remove any stale socket file before binding to avoid address_in_use errors.

#include <asio.hpp>
#include <iostream>
#include <memory>

using asio::local::stream_protocol;

void start_server(asio::io_context& ioc, const std::string& path)
{
    // Remove any stale socket file to prevent bind errors
    std::remove(path.c_str());

    auto acceptor = std::make_shared<stream_protocol::acceptor>(
        ioc, stream_protocol::endpoint(path));

    auto socket = std::make_shared<stream_protocol::socket>(ioc);

    acceptor->async_accept(*socket,
        [socket, acceptor](const asio::error_code& ec) {
            if (!ec) {
                std::cout << "Server: client connected.\n";
                
                // Echo received data back to client
                auto buf = std::make_shared<std::array<char, 1024>>();
                socket->async_read_some(
                    asio::buffer(*buf),
                    [socket, buf](const asio::error_code& ec, std::size_t n) {
                        if (!ec) {
                            asio::async_write(*socket,
                                asio::buffer(buf->data(), n),
                                [](const asio::error_code&, std::size_t) {});
                        }
                    });
            }
        });
}

Key implementation details from the source:

  • stream_protocol::endpoint stores the filesystem path in a sockaddr_un structure internally
  • The acceptor constructor automatically calls bind() on the underlying UNIX domain socket
  • Socket ownership follows ASIO's standard pattern using std::shared_ptr to keep objects alive during asynchronous operations

Implementing the IPC Client

Clients connect to the server's endpoint path using a stream_protocol::socket. The connection mechanism mirrors TCP client implementation, using async_connect with the same endpoint type used by the server.

void start_client(asio::io_context& ioc, const std::string& path)
{
    auto socket = std::make_shared<stream_protocol::socket>(ioc);
    
    socket->async_connect(stream_protocol::endpoint(path),
        [socket](const asio::error_code& ec) {
            if (!ec) {
                std::cout << "Client: connected.\n";
                const std::string msg = "Hello from client!\n";
                
                asio::async_write(*socket,
                    asio::buffer(msg),
                    [socket](const asio::error_code& ec, std::size_t) {
                        if (!ec) {
                            auto buf = std::make_shared<std::array<char, 1024>>();
                            socket->async_read_some(
                                asio::buffer(*buf),
                                [buf](const asio::error_code& ec, std::size_t n) {
                                    if (!ec) {
                                        std::cout << "Client received: "
                                                  << std::string(buf->data(), n);
                                    }
                                });
                        }
                    });
            }
        });
}

Anonymous IPC with connect_pair

For scenarios requiring IPC without filesystem exposure—such as parent-child process communication or unit testing—ASIO provides the connect_pair utility in [include/asio/local/connect_pair.hpp](https://github.com/chriskohlhoff/asio/blob/master/include/asio/local/connect_pair.hpp).

This function creates two sockets that are already connected to each other, bypassing the need for pathnames, bind operations, or acceptors.

#include <asio.hpp>
#include <iostream>

using asio::local::connect_pair;
using asio::local::stream_protocol;

int main()
{
    asio::io_context ioc;
    
    stream_protocol::socket s1(ioc);
    stream_protocol::socket s2(ioc);
    
    // Create a pair of connected sockets immediately
    connect_pair(s1, s2);  // Throws asio::system_error on failure
    
    // Exchange data without filesystem involvement
    const std::string msg = "Ping";
    asio::async_write(s1, asio::buffer(msg),
        [&](const asio::error_code&, std::size_t) {
            std::array<char, 4> buf;
            asio::async_read(s2, asio::buffer(buf),
                [&](const asio::error_code&, std::size_t) {
                    std::cout << "Received: " 
                              << std::string(buf.data(), 4) << "\n";
                });
        });
    
    ioc.run();
}

The connect_pair function utilizes the socketpair() system call on POSIX systems, creating sockets that share a common buffer without kernel filesystem overhead.

Message-Oriented IPC with datagram_protocol

When application logic requires discrete message boundaries rather than byte streams, use datagram_protocol. This protocol supports async_send_to and async_receive_from operations similar to UDP networking.

#include <asio.hpp>
#include <iostream>

using asio::local::datagram_protocol;

int main()
{
    asio::io_context ioc;
    const std::string path = "/tmp/asio_datagram";
    
    // Clean up any old socket file
    std::remove(path.c_str());
    
    // Server socket bound to a pathname
    datagram_protocol::socket server(ioc, datagram_protocol::endpoint(path));
    
    // Client socket (OS assigns temporary name)
    datagram_protocol::socket client(ioc);
    client.open();
    
    const std::string payload = "Hello, UDP-style IPC!";
    
    client.async_send_to(
        asio::buffer(payload),
        datagram_protocol::endpoint(path),
        [&](const asio::error_code& ec, std::size_t) {
            if (!ec) {
                std::array<char, 128> recv_buf;
                datagram_protocol::endpoint sender_ep;
                
                server.async_receive_from(
                    asio::buffer(recv_buf), 
                    sender_ep,
                    [&](const asio::error_code& ec, std::size_t n) {
                        if (!ec) {
                            std::cout << "Server got: "
                                      << std::string(recv_buf.data(), n) << "\n";
                        }
                    });
            }
        });
    
    ioc.run();
    std::remove(path.c_str());
}

Note that datagram_protocol::socket requires an explicit open() call when not bound to an endpoint, and each async_receive_from operation yields the sender's endpoint for potential reply operations.

Key Implementation Files in the ASIO Repository

Understanding the source structure helps when debugging or extending IPC functionality:

Summary

  • ASIO IPC uses UNIX domain sockets through the asio::local namespace, providing three protocol families: stream, datagram, and sequenced-packet
  • Endpoints are filesystem paths; servers must remove stale socket files before binding to prevent errors
  • Stream protocols use acceptor and socket classes with async_accept and async_connect, identical to TCP patterns
  • Datagram protocols preserve message boundaries using async_send_to and async_receive_from operations
  • connect_pair creates pre-connected socket pairs without filesystem exposure, ideal for testing and parent-child process communication
  • All local socket operations integrate with io_context, strands, and executors, maintaining consistency with ASIO's networking API

Frequently Asked Questions

What is the difference between stream_protocol and datagram_protocol in ASIO?

stream_protocol provides reliable, ordered byte-stream communication where data appears as a continuous sequence of bytes without message boundaries, similar to TCP. datagram_protocol preserves discrete message boundaries, delivering each send_to operation as a separate unit to receive_from, similar to UDP. Choose stream_protocol for continuous data flows and datagram_protocol when you need atomic message handling.

How do I clean up UNIX domain socket files after use?

UNIX domain sockets create persistent filesystem entries that remain after program termination. Always call std::remove(path.c_str()) (or unlink) on the socket path before binding the server acceptor, and clean up again after the server shuts down. The [src/tests/unit/local/stream_protocol.cpp](https://github.com/chriskohlhoff/asio/blob/master/src/tests/unit/local/stream_protocol.cpp) file demonstrates this cleanup pattern in the test suite.

Can I use ASIO IPC on Windows?

ASIO's local socket implementation relies on POSIX UNIX domain sockets, which are not natively available on Windows (though recent Windows 10 versions support AF_UNIX sockets). For cross-platform IPC on Windows, consider using named pipes through ASIO's asio::windows::stream_handle or the standalone Asio provided support for Windows-specific IPC mechanisms. The local socket headers in include/asio/local/ are primarily targeted at Linux, macOS, and other POSIX-compliant systems.

What is the purpose of connect_pair in ASIO?

connect_pair creates two stream_protocol::socket objects that are already connected to each other without using the filesystem or network stack. This utility, defined in include/asio/local/connect_pair.hpp, is useful for unit testing (simulating client-server communication in a single process) and for parent-child process communication where sockets are passed via fork() and exec(). It eliminates the need for pathname management, bind operations, and acceptor setup.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →