Interprocess Communication (IPC) Methods: UNIX Domain Sockets

UNIX domain socket (UNIX domain socket) provides us with a convenient way to establish communication channels between processes and has many useful built-in functions. Itsupports both stream-oriented (TCP) and datagram-oriented (UDP) protocols as TCP/IP internet sockets. We can also choose between blocking and non-blocking modes.
First you need to create a socket and specify AF_UNIX as the domain socket in the socket function. After creating the socket, you must use the bind function to bind the socket to a unique file path. Unlike Internet sockets in the AF_INET domain, which are bound to unique IP addresses and port numbers, UNIX domain sockets are bound to file paths. This file is created in the file system and you must delete it manually when the program is closed and the file is no longer needed.
UNIX domain socket communication is not much different from server/client network socket communication, but it is intended for use by the local file system. Server/client network socket introduction reference: https://blog.csdn.net/fengbingchun/article/details/107848160

UNIX domain socket summary:
(1).Synchronization;
(2). Extremely high throughput; storage device speed limit;
(3). Two-way communication;
(4). Read and write in a linear manner;
(5). Automatic memory management.

Note: The above content is mainly collected from the Internet.

The test code is as follows:

#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <iostream>

int main()
{
    // reference: https://biendltb.github.io/tech/inter-process-communication-ipc-in-cpp/
    const char* server_sock_path = "/tmp/unix_sock.server";
    const char* client_sock_path = "/tmp/unix_sock.client";

    pid_t pid = fork(); // create two processes of client and server
    if (pid < 0) {
        fprintf(stderr, "fail to fork\
");
        return -1;
    }

    if (pid != 0) { // server process(parent process)
        auto server_sock = socket(AF_UNIX, SOCK_STREAM, 0); // open the server socket with the SOCK_STREAM type
        if (server_sock == -1) {
            fprintf(stderr, "SERVER: fail to socket: %s\
", strerror(errno));
            exit(1);
        }

        // bind to an address on file system
        // similar to other IPC methods, domain socket needs to bind to a file system, so that client know the address of the server to connect to
        struct sockaddr_un server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sun_family = AF_UNIX;
        strcpy(server_addr.sun_path, server_sock_path);
        
        unlink(server_sock_path); // unlink the file before bind, unless it can't bind: error info: Address already in use
        auto rc = bind(server_sock, (struct sockaddr *) & amp;server_addr, sizeof(server_addr));
        if (rc == -1) {
            fprintf(stderr, "SERVER: fail to bind: %s\
", strerror(errno));
            exit(1);
        }

        // listen and accept client connection
        // set the server in the "listen" mode and maximum pending connected clients in queue
        rc = listen(server_sock, 10);
        if (rc == -1) {
            fprintf(stderr, "SERVER: fail to listen: %s\
", strerror(errno));
            exit(1);
        }

        fprintf(stdout, "SERVER: Socket listening...\
");
        struct sockaddr_un client_addr;
        auto len = sizeof(client_addr);
        int client_fd = accept(server_sock, (struct sockaddr *) & amp;client_addr, (socklen_t*) & amp;len);
        if (client_fd == -1) {
            fprintf(stderr, "SERVER: fail to accept: %s\
", strerror(errno));
            exit(1);
        }
        fprintf(stdout, "SERVER: Connected to client at: %s\
", client_addr.sun_path);
        fprintf(stdout, "SERVER: Wating for message...\
");

        const int buf_len = 256;
        char buf[buf_len];
        memset(buf, 0, buf_len);
        int byte_recv = recv(client_fd, buf, buf_len, 0);
        if (byte_recv == -1) {
            fprintf(stderr, "SERVER: fail to recv: %s\
", strerror(errno));
            exit(1);
        }
        else
            fprintf(stdout, "SERVER: Server received message: %s.\
", buf);

        fprintf(stdout, "SERVER: Respond to the client...\
");
        memset(buf, 0, buf_len);
        strcpy(buf, "hello from server");
        rc = send(client_fd, buf, buf_len, 0);
        if (rc == -1) {
            fprintf(stderr, "SERVER: fail to send:%s\
", strerror(errno));
            exit(1);
        }
        fprintf(stdout, "SERVER: Done!\
");

        close(server_sock);
        close(client_fd);
        remove(server_sock_path); // remove access to a file named

        int status;
        auto pid2 = wait( & amp;status); // system call suspends execution of the calling thread until one of its children terminates
        fprintf(stdout, "process ID of the terminated child: %d\
", pid2);
        if (WIFEXITED(status)) { // returns true if the child terminated normally
            fprintf(stdout, "child process ended with: exit(%d)\
", WEXITSTATUS(status));
        }
        if (WIFSIGNALED(status)) { // returns true if the child process was terminated by a signal
            fprintf(stderr, "child process ended with: kill -%d\
", WTERMSIG(status));
        }
    }

    if (pid == 0) { // client process(child process)
        int client_sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (client_sock == -1) {
            fprintf(stderr, "CLIENT: fail to socket: %s\
", strerror(errno));
            exit(1);
        }

        // bind client to an address on file system
        // Note: this binding could be skip if we want only send data to server without receiving
        struct sockaddr_un client_addr;
        memset( & amp;client_addr, 0, sizeof(client_addr));
        client_addr.sun_family = AF_UNIX;
        strcpy(client_addr.sun_path, client_sock_path);

        unlink (client_sock_path);
        auto rc = bind(client_sock, (struct sockaddr *) & amp;client_addr, sizeof(client_addr));
        if (rc == -1) {
            fprintf(stderr, "CLIENT: fail to bind: %s\
", strerror(errno));
            exit(1);
        }

        // Set server address and connect to it
        struct sockaddr_un server_addr;
        server_addr.sun_family = AF_UNIX;
        strcpy(server_addr.sun_path, server_sock_path);
        rc = connect(client_sock, (struct sockaddr*) & amp;server_addr, sizeof(server_addr));
        if (rc == -1) {
            fprintf(stderr, "CLIENT: fail to connect: %s\
", strerror(errno));
            exit(1);
        }
        fprintf(stdout, "CLIENT: Connected to server.\
");

        // Send message to server
        const int buf_len = 256;
        char buf[buf_len];
        memset(buf, 0, buf_len);
        strcpy(buf, "hello from client");
        rc = send(client_sock, buf, buf_len, 0);
        if (rc == -1) {
            fprintf(stderr, "CLIENT: fail to send: %s\
", strerror(errno));
            exit(1);
        }
        fprintf(stdout, "CLIENT: Sent a message to server.\
");

        fprintf(stdout, "CLIENT: Wait for respond from server...\
");
        memset(buf, 0, buf_len);
        rc = recv(client_sock, buf, buf_len, 0);
        if (rc == -1) {
            fprintf(stderr, "CLIENT: fail to recv: %s\
", strerror(errno));
            exit(1);
        }
        else
            fprintf(stdout, "CLIENT: Message received: %s\
", buf);

        fprintf(stdout, "CLIENT: Done!\
");

        close(client_sock);
        remove(client_sock_path);
        exit(0);
    }

    fprintf(stdout, "====== test finish ======\
");
    return 0;
}

The compilation script build.sh is as follows:

#!/bin/bash

if [ -d build ]; then
    echo "build directory already exists, it does not need to be created again"
else
    mkdir -p build
fi

cd build
cmake..
make

rc=$?
if [[ ${rc} != 0 ]];then
    echo "#### ERROR: please check ####"
    exit ${rc}
fi

echo "==== build finish ===="

The contents of CMakeLists.txt are as follows:

cmake_minimum_required(VERSION 3.22)
project(samples_multi_process)

set(CMAKE_BUILD_TYPE Release) # only works under linux
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O2 -std=c + + 17")

file(GLOB samples ${PROJECT_SOURCE_DIR}/test_*.cpp)
#message(STATUS "samples: ${samples}")

foreach(sample ${samples})
    string(REGEX MATCH "[^/] + $" name ${sample})
    string(REPLACE ".cpp" "" exec_name ${name})
    #message(STATUS "exec name: ${exec_name}")

    add_executable(${exec_name} ${sample})
    target_link_libraries(${exec_name}rt)
endforeach()

The execution result is as follows:

GitHub: https://github.com/fengbingchun/Linux_Code_Test