UbuntuのTerminalでUnix Domain Socketを使ってIPCするC言語実装の最小構成サンプル


今回のサンプルを実装した元々の目的はAndroid NativeでのSocketを使ったIPCの概要をざっくり知ることでした.

TL;DR (長い三行で)

実装はServer側とClient側の2種類,.c 2ファイル.
.cをgccでCompileして実行Fileを2つ出力.
それぞれ別Terminal(別Process)上で実行し,文字列メッセージを送受信する.

Socketの種類

Socket自体は通信方式やProtocolの実装は規定していないようで,今回のような同一Host内のIPCでも,他Host間のTCP/IP等の通信も可能になっているようです.
今回は同一Host内のIPCをSocketをつかって実装します.

Build方法

gccを使って.cを実行Fileにcompileします.
今回のサンプルは実装が超シンプルなので,何のOption指定もしなくても試すことはできます.
(gccとかに詳しい人には怒られそうですが...)

# Current Dirのserver.cというファイルをCompileしてserverという実行Fileを出力
$ gcc -o server server.c

# 出力した実行Fileを実行
$ ./server

Server側実装

Server側は起動後,接続を待ち続け,接続が来たらデータを読み取り,終わったらまた接続を待つ,という処理になります.
Server側はずっと動き続けるものになるので,終了させるときはctrl+cとかで強制終了するか,Clientからの終了コマンドを定義するか,が必要です.

socket // Socket自身のFile Descriptor作成
↓
sockadddr_un // Socket接続待ちのAddress (Socket File) を指定
↓
bind // SocketをSocket Fileに接続
↓
listen // Socketを受信待ちSocketに指定
↓
accept // Clientからの接続を受け付け
↓
read // ClientからのDataを受信
↓
close // Clientから受信したFile DescriptorをClose
↓
close // Socket自身のFile DescriptorをClose
server.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

void on_error(char* msg, int socket_fd) {
    if (socket_fd != -1) {
        close(socket_fd);
    }
    printf("TraceLog: server: %s\n", msg);
}

int main(void) {
    printf("TraceLog: server: main() : E\n");

    int err = 0;
    int socket_fd = -1;

    // Create server socket file discriptor.
    socket_fd = socket(
            AF_UNIX, // Local connection.
            SOCK_STREAM, // Bi-directional access, bite stream.
            0); // Default protocol.
    if (socket_fd == -1) {
        on_error("socket_fd != 0", socket_fd);
        return -1;
    }

    // Struct for socket address.
    struct sockaddr_un addr = { 0 }; // 0 init.
    addr.sun_family = AF_UNIX; // Fixed.
    strcpy(addr.sun_path, "socket_file"); // Common socket file.

    // Remove old socket. If file exists, bind will be failed.
    remove(addr.sun_path);

    // Bind.
    err = bind(
            socket_fd, // Bind target socket.
            (struct sockaddr*) &addr, // Socket address.
            sizeof(struct sockaddr_un)); // Address size.
    if (err != 0) {
        on_error("bind failed.", socket_fd);
        return -1;
    }

    // Mark passive socket.
    err = listen(
            socket_fd, // Target socket.
            1); // Max connection count.
    if (err != 0) {
        on_error("listen failed.", socket_fd);
        return -1;
    }

    // Wait for connection and receive.
    while(1) {
        // Wait for client connection.
        int fd = accept(
                socket_fd, // Target socket.
                NULL, // Client address.
                NULL); // Client address size.
        if (fd == -1) {
            on_error("accept failed.", socket_fd);
            return -1;
        }

        // Receive.
        char buffer[256];
        int size = read(
                fd, // Accepted fd.
                buffer, // Receive buffer.
                sizeof(buffer) - 1); // Receive size. Last byte is null char.
        if (size == -1) {
            on_error("read failed.", socket_fd);
            return -1;
        }

        // Output received message.
        buffer[size] = '\0';
        printf("TraceLog: server: Received Msg = %s\n", buffer);

        // Close client fd.
        err = close(fd); // Closed client connection fd.
        if (err != 0) {
            on_error("close fd failed.", socket_fd);
            return -1;
        }

    } // while(true)

    // Close server socket.
    close(socket_fd);

    printf("## server main() : X\n");
    return 0;
}

Client側実装

Client側の処理は接続先のSocket(Socket File)に対して接続,接続完了したらデータ送信,の流れです.

socket // Socket自身のFile Descriptor作成
↓
sockadddr_un // Socket接続先のAddress (Socket File) を指定
↓
connect // SocketをSocket Fileに接続している別のSocketに接続
↓
write // 接続先SocketにDataを送信
↓
close // Socket自身のFile DescriptorをClose
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>

#define MESSAGE "HELLO WORLD !"

void on_error(char* msg, int socket_fd) {
    if (socket_fd != -1) {
        close(socket_fd);
    }
    printf("TraceLog: client: %s\n", msg);
}

int main(void) {
    printf("TraceLog: client: main() : E\n");

    int err = 0;
    int socket_fd = -1;

    // Create client socket.
    socket_fd = socket(
            AF_UNIX, // Local connection.
            SOCK_STREAM, // Bi-directional access, bite stream.
            0); // Default protocol.
    if (socket_fd == -1) {
        on_error("socket_fd != 0", socket_fd);
        return -1;
    }

    // Struct for socket address.
    struct sockaddr_un addr = { 0 }; // 0 init.
    addr.sun_family = AF_UNIX; // Fixed.
    strcpy(addr.sun_path, "socket_file"); // Common socket file.

    // Connect.
    err = connect(
            socket_fd, // Bind target socket.
            (struct sockaddr*) &addr, // Socket address.
            sizeof(struct sockaddr_un)); // Address size.
    if (err != 0) {
        on_error("connect failed.", socket_fd);
        return -1;
    }

    // Send message.
    err = write(
            socket_fd, // Target socket.
            MESSAGE, // Message.
            strlen(MESSAGE)); // Message size.
    if (err == -1) {
        on_error("write failed.", socket_fd);
        return -1;
    }

    // Close client socket.
    close(socket_fd);

    printf("TraceLog: client: main() : X\n");
    return 0;
}

動作確認

  1. Server用Terminalで./serverを打ちServerを起動,接続待ち状態にする
  2. Client用Terminalで./clientを打ちClientを起動,メッセージ送信後,自動で終了
  3. Server用TerminalにClientからのメッセージが表示されていることを確認

おわり