UNIXドメインのプロセス間通信プログラムの例


いきなり、ネットワークを使って離れたマシン間で通信を行う (INETドメインのソケットを使う)ようなプログラムを書くのはちょっと難しいので、 まずは一つのマシンの中で起動されているプロセス同士で通信を 行う(UNIXドメインのソケットを使う)方法を説明します。

では、早速プロセス間通信を行うプログラムを書いてみましょう。 プロセス間通信を行うには当然、メッセージを送信する側と受信する側の二つのプログラム を書く必要があります。

まず最初は簡単なサーバークライアント型のプログラムを作ってみましょう。 クライアント側ではユーザーからのキー入力を受け付け、入力された文字列をサーバーに 送ります。サーバーは、受け取った文字列のアルファベットの部分を大文字に変換して クライアントに送り返します。

ソケットを使って通信を行うには、ソケットを作ったり、ソケットを使ってメッセージを 送信したりするための特別な関数が必要です。プログラムの中で使われている 関数をまとめると次のようになります。

ちょっと数が多いですが、これらの関数を覚えてしまえば、あとはどんなプログラムにでも 応用できますので頑張って下さい。 では、これらの関数について一つずつ説明していきましょう。

■ ソケットの生成(socket)
ソケット通信を行なうにはまず、通信を行なうすべてのプロセスが それぞれ socket() システムコールによりソケットを用意する必要があります。
	int socket(int domain, int type, int protocol)
domainには、sys/socket.hで定義されている値を使います。 今回は、UNIXドメインのソケットを使うので、PF_UNIXを指定します。 INETドメインのソケットを使う場合は、PF_INETを指定します。
typeは同じくsys/socket.hで定義されている、SOCK_DGRAMまたはSOCK_STREAMを 指定します。 ここではとりあえず、SOCK_STREAMとしておきます。詳しくはあとで説明します。
protocolは通常は0にしておけば問題ないです。
socket() はソケットディスクリプタを返し、この値が bind()、listen()、 accept() の引数に用いられます。

■ ソケットアドレスの指定(bind)
socket()システムコールで作成したソケットに対して名前を割り当てます。 ここで付けられた名前が、クライアントが接続要求の際に指定する 名前となります。
	int  bind(int s, struct sockaddr *name, int namelen)
sには、socket()の返り値を渡します。
nameは、sys/socket.hで定義されているsockaddr構造体へのポインタに なっていますが、実際にはsocket()の引数のdomainによって、内容が異なります。 sokcet()の引数をPF_UNIXにした時は、sockadd_un構造体を使います。
      struct  sockaddr_un {
              u_char  sun_len;                /* sockaddr len including null */
              u_char  sun_family;             /* AF_UNIX */
              char    sun_path[104];          /* path name (gag) */
      };
sun\_familyには、PF\_UNIXを指定します。sun\_pathはソケットのパス名です。
bindの第3引数は構造体の大きさで、
	 sizeof(name.sun\_family) + strlen(name.sun\_path)
を指定します。

■ 接続の準備(listen)
サーバーはlisten()システムコールにより、接続のための準備を行ないます。
	int    listen(int s, int backlog)
sは、socketの返り値です。backlogは接続要求をいくつ受け付けるかを指定します。 最大値は通常5です。

■ 接続要求(connect)
次に、クライアントがサーバに対して connect() システムコールにより 接続要求を出し、サーバは accept() システムコールにより接続を受け入れます。
	int  connect(int s, struct sockaddr *name, int namelen)
引数は、bind()のものと同じです。

■ 接続(accept)
サーバー側では、クライアントのconnect要求をacceptによって受け入れます。
	int  accept(int s, struct sockaddr *addr, int *addrlen)
sは、listenの時と同じでsocket()の返り値を渡します。 nameには空の構造体のポインタを渡します。accept()が成功すると、システムが 自動的に書き込んでくれます。
接続要求が完了すると accept()はクライアントと接続された新しいソケット ディスクリプタを返します。
サーバはクライアントとの通信にこの新しいディスクリプタを用い、 socket()コールの時に返された古いディスクリプタはもう必要なくなります。

■ 通信(write,read)
接続が完了した後は、write()、read() システムコールにより通信を行なう ことができます。
	ssize_t   write(int d, const void *buf, size_t nbytes)
sはソケットディスクリプタ。bufは送信したいデータです。
	ssize_t   read(int d, void *buf, size_t nbytes)
sはソケットディスクリプタ。bufは受信したデータを置くバッファです。

◆実行方法

  1. まず、kterm等のウィンドウを二つ開きます。

  2. 片方のウィンドウで、
        # ./server
    
    として先にサーバーを起動します。

  3. 次にもう一方のウィンドウで、
        # ./client
    
    として、クライアントを起動します。

  4. クライアントのウィンドウで文字を打ち込むと、大文字に変換されて表示されます。