Linuxシステムコール、Unixドメイン ソケット

UNIX / Linux システムコール・プログラミング

プロセス間通信(IPC)、ソケット通信/Unixドメイン ソケット

Unixドメイン ソケットとは?

Unixドメイン ソケットは、同一ノード内(1つのCPUで動作する1つのオペレーティング・システム内)で動作するプロセス間で、双方向の通信手段を提供します。
また、ソケットはクライアント/サーバー モデルで構成されており、1つのサーバーが複数のクライアントからの通信を受け入れる構造をとります。

まず、以下にサーバー側のサンプルコードを示します。

unix-dimain-server.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

// Make server socket.
//
int makeServer(char* sockName) {

  int                sfd;
  int                ret;
  struct sockaddr_un name;

  // Unlink, last running remove the socket.
  unlink(sockName);

  // Create server socket.
  if((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    perror("ERROR:Server Socket()");
    return -1;
  }

  // Bind socket
  memset(&name, 0, sizeof(struct sockaddr_un));
  name.sun_family = AF_UNIX;
  strncpy(name.sun_path, sockName, sizeof(name.sun_path) - 1);
  if ((ret = bind(sfd,
                  (const struct sockaddr *)&name,
                  sizeof(name.sun_family) + 
                  strlen(name.sun_path))) < 0) {
    perror("ERROR:Server bind()");
    return -1;
  }

  // Listen, prepair for accept()
  if((ret = listen(sfd, 10)) < 0) {
    perror("ERROR:Server listen()");
    return -1;
  }

  return sfd;
}
// Make data socket for client.
//
int acceptClient(int sfd) {

  int dfd;

  if((dfd = accept(sfd, NULL, NULL)) < 0) {
    perror("ERROR:Server accept()");
    return -1;
  }

  return dfd;
}

// This main()
//
int main(int argc, char** argv) {

  int  sfd, dfd;
  int  ret;
  char buf[256];

  if((sfd = makeServer(argv[1])) < 0) {
    return -1;
  }

  if((dfd = acceptClient(sfd)) < 0) {
    close(sfd);    // Close server socket.
    return -1;
  }

  // Main loop
  while(1) {
    if((ret = read(dfd, buf, sizeof(buf))) < 0) {
      perror("ERROR:Data read()");
      return -1;
    }

    printf("%s", buf);

    if(strncmp(buf, "Quit", 4) == 0) {
      printf("Received Quit\n");
      break;
    }

    // Send to client "NeXT".
    if((ret = write(dfd, "NeXT", 4)) < 0) {
      perror("ERROR:Data write()");
      return -1;
    }
  }

  // Send "QUIT" for client.
  write(dfd, "QUIT", 4);

  // Close data socket.
  close(dfd);

  // Close server socket.
  close(sfd);

  return 0;
}


サーバー側のコードについて説明します。

まず、Unixドメイン ソケットをsocket()システムコールを使って生成します。
  socket(AF_UNIX, SOCK_STREAM, 0)
    AF_UNIX、生成するソケットはUnixドメイン ソケットである
    SOCK_STREAM、バイトストリームを指定
    0、SOCK_STREAM固有のプロトコルなので0を指定

  socket()は、ソケットへのファイルディスクリプタを返します。

次にbind()システムコールを使ってソケットに名前(ファイルパス)を割り当てます。bind()には、sockaddr_un構造体で名前を設定しますが、このコードでは起動時に引数で”名前”を与えています。
  bind( sfd, (const struct sockaddr *)&name,
     sizeof(name.sun_family) + strlen(name.sun_path))
    sfd、socket()で得られたファイルディスクリプタ
    name、名前の構造体ポインタ
    サイズ

bind()によってファイルシステム上にファイルが作成されます。このコードでは、同じ名前のソケットファイル名が残っていた場合、unlink()システムコールで削除しています。

次に、このコードがサーバーとして動作するために作成したソケットをlisten()システムコールを使って、受け入れソケットにします。
  listen(sfd, 10) 
    sfd、ソケットファイルディスクリプタ
    backlog、同時に受け入れ可能なクライアント数

これで、クライアントの受け入れ準備ができました。
今度は、accept()システムコールを使ってクライアントからの要求を待ちます。
  accept(sfd, NULL, NULL) 
    sfd、受け入れソケットファイルディスクリプタ
    第2、第3引数はクライアント情報を受け取る場合に使用

クライアントからの接続を待ち、接続が完了するとクライアント間ソケットのファイルディスクリプタが帰ります。

※受け入れソケットと、クライアントと通信するソケットは別

以降、このクライアント間ソケットを利用して双方向の通信が可能になります。
このコードでは、クライアントからのデータを待って表示を行い、クライアントからのデータが"Quit"である場合は終了します。それ以外の場合は"NeXT"をクライアントに送って次のデータを待ちます。
終了時は、クライアントに"QUIT"を送り、クライアント側にも終了を促し、クライアント間のソケット、クライアントの受け入れソケットを閉じて終了します。

クライアントの受け入れソケットと通信ソケットの構造になっているのは複数のクライアントからの要求を受け入れるためです。
このサンプルコードではクライアントとの接続は1つだけしか行っていませんが、複数のクライアントからの要求を処理するようなサーバーとしての動作を可能としています。

それでは、今度はクライアント側のコードについて解説します。

unix-domain-client.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

int main(int argc, char** argv) {

  int                dfd;
  int                ret;
  struct sockaddr_un name;
  char               buf[256];
  char               rcv[256];

  // Create socket
  if((dfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
    perror("ERROR:Client socket()");
    return -1;
  }

  // Connect server
  memset(&name, 0, sizeof(struct sockaddr_un));
  name.sun_family = AF_UNIX;
  strncpy(name.sun_path, argv[1], sizeof(name.sun_path) - 1);
  if((ret = connect(dfd, (const struct sockaddr*)&name,
                    strlen(name.sun_path) + sizeof(name.sun_family))) < 0) {
    perror("ERROR:Client connect()");
    return -1;
  }

  while(1) {
    // User input to send data.
    printf("#");
    memset(buf, 0, sizeof(buf));
    fflush(stdin);
    fgets(buf, 256, stdin);

    // Send to server.
    if((ret = write(dfd, buf, strlen(buf) + 1)) < 0) {
      perror("ERROR:Client read()");
      close(dfd);
      return -1;
    }

    // Reveive data from server.
    memset(rcv, 0, sizeof(rcv));
    if((ret = read(dfd, rcv, sizeof(rcv) - 1)) < 0) {
        perror("ERROR:Client read()");
        close(dfd);
        return -1;
    }
    printf("%s", rcv);

    if(strncmp(rcv, "QUIT", 4) == 0) {
      printf("\nReceived QUIT\n");
      break;
    }
  }

  // Close data socket.
  close(dfd);

  return 0;
}

クライアント側のプログラムは、サーバー側を起動したあとで実行します。
クライアント側のプログラムも起動時の引数で、ソケットの名前を指定してください。
このときのソケットの名前はサーバーを起動したときに引数で指定したものと同じ名前です。

まず、Unixドメイン ソケットをsocket()システムコールを使って生成します。
  socket(AF_UNIX, SOCK_STREAM, 0)
    AF_UNIX、生成するソケットはUnixドメイン ソケットである
    SOCK_STREAM、バイトストリームを指定
    0、SOCK_STREAM固有のプロトコルなので0を指定

  socket()は、ソケットへのファイルディスクリプタを返します。
この部分はサーバー側と同じです。

次にconnect()システムコールを使って、サーバーに接続を行います。
  connect(dfd, (const struct sockaddr*)&name,
      strlen(name.sun_path) + sizeof(name.sun_family))

    dfd、socket()で得られたファイルディスクリプタ
    name、接続するソケットの名前の構造体ポインタ
    サイズ

この時点でサーバー側がaccept()で待っている状態であれば、サーバー側で受けれられて通信が可能となります。

このコードではユーザーからの入力をサーバに送り、サーバーからの応答を待ちます。サーバーからの応答が"QUIT"であったとき(ユーザーが"Quit"を入力したとき)はソケットのファイルディスクリプタを閉じて終了します。
それ以外の応答である場合は、ユーザー入力を続けます。


今回は、同一ノード内でのソケット通信でしたが、次回はノード間で通信を可能にするINETドメイン ソケット通信について解説します。

 → INETドメイン ソケット に続く



Have a Happy Hucking!!


Lightning Brains

コメント

このブログの人気の投稿

Linuxシステムコール、共有メモリの使い方

Linuxシステムコール、メッセージキューの使い方

Linuxシステムコール、セマフォの使い方