Linuxシステムコールの勉強(その16)

Linuxシステムコール

Linuxシステムコール

前回はこちら


今回からソケット通信についてまとめます。ちなみにこの本はソケット通信の章が最後なのでこのシリーズもここでひとまず終わりです。というわけで張り切ってまとめます。


ソケット通信はパイプを使用したプロセス間通信を拡張したものです。接続を受け付ける側(サーバ)と接続を要求する側(クライアント)がそれぞれソケットを作成して、「プロトコル」と「通信方式」を決めて通信を行います。


まずはサーバ側の処理の流れを図にしてみました。



listen用のソケットをsocket()で作成し、bind()でソケットに名前を付けます。そしてlisten()で接続用のソケットキューを作成し、accept()でクライアントからの接続を受け付けます。accept()を実行したタイミングで通信用のソケットが作成されて、以降はそちらのソケットで通信を行います。
# listen用のソケットはあくまで待ち受け用のソケットです


accept()以降は通常のファイル操作同様にread()/write()でデータの読み書きを実施出来ます。使い終わったらファイル同様にディスクリプタを指定してソケットをクローズします。
これがサーバ側の処理の流れです。


続いてクライアント側の流れも図にしてみました。

サーバ同様に通信を行うためのソケットをsocket()で作成します。それをconnect()で接続して、あとはread()/write()で読み書き。それが終わったらこれまたサーバ側と同様にディスクリプタを指定してソケットをクローズします。
これがクライアントの処理の流れです。


ひとまず出てきたシステムコールについてもう少し詳しくまとめます。

/*** ソケットを作成する ***/
// #include <sys/types.h>
// #include <sys/socket.h>
// 
// 第一引数 domain   - ソケットが適用される範囲(ドメイン)を指定[後述]
// 第二引数 type     - 通信方式[後述]
// 第三引数 protocol - 第一引数で指定したドメインで有効なプロトコルファミリを指定[通常は0固定]
//
// 返却値
//  成功時 :  有効なファイルディスクリプタ
//  失敗時 : -1
int socket(int domain, int type, int protocol);


/*** ソケットに名前を付ける ***/
// #include <sys/types.h>
// #include <sys/socket.h>
// 
// 第一引数 sockfd   - 処理対象となるファイルディスクリプタ(socket()の返却値)
// 第二引数 sockaddr - アドレス[後述]
// 第三引数 addrlen  - 第二引数のサイズ
//
// 返却値
//  成功時 :  0
//  失敗時 : -1
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);


/*** ソケットキューの作成 ***/
// #include <sys/socket.h>
// 
// 第一引数 sockfd  - 処理対象となるファイルディスクリプタ(socket()の返却値)
// 第二引数 backlog - 同時接続受付数
//
// 返却値
//  成功時 :  0
//  失敗時 : -1
int listen(int sockfd, int backlog);


/*** 接続の受付を開始 ***/
// #include <sys/types.h>
// #include <sys/socket.h>
// 
// 第一引数 sockfd   - 処理対象となるファイルディスクリプタ(socket()の返却値)
// 第二引数 sockaddr - 受け取るアドレス情報[後述]
// 第三引数 addrlen  - 第二引数のサイズ
//
// 返却値
//  成功時 :  0
//  失敗時 : -1
int accept(int sockfd, struct sockaddr *my_addr, int *addrlen);



/*** 接続の要求を開始 ***/
// #include <sys/types.h>
// #include <sys/socket.h>
// 
// 第一引数 sockfd   - 処理対象となるファイルディスクリプタ(socket()の返却値)
// 第二引数 sockaddr - アドレス情報[後述]
// 第三引数 addrlen  - 第二引数のサイズ
//
// 返却値
//  成功時 :  0
//  失敗時 : -1
int connect(int sockfd, struct sockaddr *my_addr, int addrlen);


最初にsocket()の第一、第二引数について補足。


socket()の第一引数のドメインは通信に使用するソケットの範囲を指定(言い換えれば使うプロトコルファミリーを指定)。

指定出来る値 プロトコル 説明
AF_UNIX UNIX内部用プロトコル 同一マシン内で通信を実施する
AF_INET ARPAインターネットプロトコル 同一マシン or ネットワーク接続された別のマシン間で通信する
AF_ISO ISOプロトコル   ? 
AF_NS Xeroxネットワークシステムプロトコル   ? 
AF_IMPLINK IMP"host at IMP"リンク層   ? 


第二引数の通信方式はどういった方法で通信するのかという指定。

指定出来る値 通信方式の特色
SOCK_STREAM バイトストリームソケット
信頼性が高い
データの送信順序が保証される
つまりTCP
SOC_DGRAM データグラムソケット
信頼性が低い
データが受信される順序は保証されない
つまりUDP


次にbind()で付けられるソケットの名前について補足します。
bind()で付けられるソケットの名前が何になるのかは、socket()を呼び出すときに使用したドメインによって異なります。

ドメイン ソケットの名前
UNIXドメイン(AF_UNIX) ファイル名
INETドメイン(AF_INET) ポート番号


最後にbind()やaccess(),connect()で使用しているアドレス情報について説明します。各システムコールで使用している構造体は

struct sockaddr


ですが、実際には指定するドメインによって使用する構造体は異なります。

ドメイン 構造体の型
UNIXドメイン(AF_UNIX) struct sockaddr_un
INETドメイン(AF_INET) struct sockaddr_in


さて、では実際に struct sockaddr_un のメンバーについて調べてみます。

itotto@itotto >cat /usr/include/sys/un.h

...
struct sockaddr_un
  {
    __SOCKADDR_COMMON (sun_);
    char sun_path[108];         /* Path name.  */
  };
...

itotto@itotto >find /usr/include -name "*.h" -exec grep -H __SOCKADDR_COMMON {} \;

/usr/include/bits/sockaddr.h:#define    __SOCKADDR_COMMON(sa_prefix)  sa_family_t sa_prefix##family

itotto@itotto >cat /usr/include/bits/sockaddr.h
...
typedef unsigned short int sa_family_t;
...

itotto@itotto >

##familyのあたりがちょっと分からないのですが、何となく雰囲気としての定義は分かりました。これらの構造体をセットすればいいようです。


というわけで、これでシステムコールの学習はひとまず終了です。
本当はここまでのまとめとして、システムコールだけで作られたアプリケーションを作れたらよかったのですが、ひさしくLinux自体さわっていないのと、仕事でもプログラムをたくさんしていてあまり気乗りしないので、とりあえずはこれで終わりにしたいと思います。

もしプログラムを書く気になったらまた追記します。