LoginSignup
0
0

More than 5 years have passed since last update.

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

Posted at

今回のサンプルを実装した元々の目的は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からのメッセージが表示されていることを確認

おわり

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0