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とかに詳しい人には怒られそうですが．．．)

```bash
# 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
```


```c: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
```

```c: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からのメッセージが表示されていることを確認


# おわり