UNIXのソケット通信(C言語)



C言語によるClient Server型UNIX Network Programming 入門
1  socketシステムコール
2  bindシステムコール
3  listenシステムコール
4  acceptシステムコール
5  connectシステムコール
6  closeシステムコール
7  その他のシステムコール
8  バイト順序変換
9  プログラム例

プロトコルメモ
1  プロトコルPOP3(Post Office Protocol)
2  プロトコルSMTP(Send Mail Transfer Protocol)

-------------------------------------------------------
1  socketシステムコール

動作:
ソケットを作る

定義:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

引数の意味:
domain     通信領域を指定[1]
type       通信形式[2]
protocol   使用プロトコル[3]

引数に指定される値:
[1]domain
    AF_INET  インタネット(INET)ドメインの2ホスト間プロセス通信。
             ARPAインターネットプロトコル(UNIXネットワークソケット)。
    AF_UNIX  UNIXドメインの1ホスト内プロセス通信。ファイルシステムソケット。
    AF_ISO   ISO標準プロトコル
    AF_NS    Xerox Network Systemsプロトコル
[2]type
    SOCK_STREAM  順次双方向バイトストリーム。コネクション型。信頼性が高い。
    SOCK_DGRAM   データグラム。コネクションレス型。信頼性が低い。UDPで提供。
    SOCK_ROW     直接IPを用いた通信を行なう
[3]protocol
    0            自動設定(AF_INET&SOCK_RAWでIPを直接扱いたい場合も含む)
    IPPROTO_TCP  TCP/IP(AF_INET&SOCK_STREAMの場合。0も可。)
    IPPROTO_UDP  UDP/IP(AF_INET&SOCK_DGRAMの場合。0も可。)
    IPPROTO_RAW  ICMP(pingコマンドなど。AF_INET&SOCK_RAWでICMPソケットを作りたい場合)

ソケットのアドレス:
AF_INETの時使用する、sockaddr_in構造体の内部を以下に示す。
-----
#include<netinet/in.h>
struct sockaddr_in{
  short sin_family;          /*ドメイン(常にAF_INET)*/
  u_short sin_port;          /*ポート番号*/
  struct in_addr sin_addr;   /*IPアドレス*/
  char sin_zero[8];          /*ハディング*/
};
struct in_addr{
  u_long s_addr;
};
-----
short は short int、u_short は unsigned short int、u_long は unsigned long intである。
AF_UNIXの時使用する、sockaddr_un構造体の内部を以下に示す。
-----
#include<sys/un.h>
struct sockaddr_un{
  sa_family_t sun_family;   /*ドメインネーム(常にAF_UNIX)*/
  char sun_path[108];       /*パスネーム*/
}
-----
sys/un.hより、sa_family_tの型はshortである。
pathnameの長さはUNIX_MAX_PATHで定義されており、108文字である。

戻り値:
ソケット記述子   成功
-1               失敗


-------------------------------------------------------
2  bindシステムコール

bindシステムコールの動作:
ソケットに名前(ポート番号 & IPアドレス)を付ける。

定義:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int socket, const struct sockaddr *address, size_t address_len);

引数の意味:
socket        ソケット記述子
*address      プロトコル対応のアドレス構造体へのポインタ。
              パラメータaddressで指定したアドレスを割り当てることができる。
              アドレスの長さと形式はアドレスファミリにより異なるので、
              汎用アドレス型(struct sockaddr *)にキャストする必要がある。
address_len   アドレス構造体のサイズ

戻り値:
 0   成功
-1   失敗

戻り値が失敗(-1)の場合のerrnoの値:
EBADF           ファイルディスクリプタが無効である。
ENOTSOCK        ファイルディスクリプタがソケットを参照していない。
EINVAL          ファイルディスクリプタがすでに名前のついたソケットを参照している。
EADDRNOTAVAIL   アドレスを利用できない。
EADDRINUSE      アドレスにはすでにソケットがバインドされている。
上の値に加え、AF_INETソケットではさらに次の値をとることもある。
EACCESS                 指定されたファイルシステム名を作成するためのパーミッションがない。
ENOTDIR, ENAMETOOLONG   ファイル名の指定が適切でない。


-------------------------------------------------------
3  listenシステムコール

動作:
コネクションの確立時にクライアント側からの接続要求を待つため、サーバ側で使う。
つまり、保留中の要求を格納するキューをサーバ側で作る。

定義:
#include<sys/socket.h>
int listen(int socket, int backlog);

引数の意味:
socket    ソケット記述子
backlog   キューの長さを設定。5が一般的。

戻り値:
 0   成功
-1   失敗

戻り値が失敗(-1)の場合のerrnoの値:
EBADF
EINVAL
ENOTSOCK
(すべてbindと同様の意味を持つ)


-------------------------------------------------------
4  acceptシステムコール

動作:
コネクションの確立時に該当するソケットへの接続を待つため、サーバ側で使う。

定義:
#include<sys/socket.h>
int accept(int socket, struct sockaddr *address, size_t *address_len);

引数の意味:
socket         ソケット記述子
*address       呼び出し側のクライアントのアドレスポインタ(sockaddr構造体に格納されているもの)。
               クライアントのアドレスが要らないときはnullポインタでも可。
*address_len   アドレス構造体の長さ

戻り値:
新しいソケットファイルディスクリプタ   成功(保留中のクライアント接続がある場合)
-1                                     失敗

戻り値が失敗(-1)の場合のerrnoの値:
(bindやlistenと同じものに以下を加える)
EWOULDBLOCK   O_NUNBLOCKが指定されていて保留中の接続が無い場合に発生する。

O_NUNBLOCKを指定するには:
ソケットキューに接続が無い場合、クライアントが接続してくるまでサーバはacceptでブロックされる。
ただしこの動作は、fcntl関数を使ってソケットのファイルディスクリプタに
O_NONBLOCKフラグを指定することで変更することができる。
具体的には以下のようにする。
-----
int flag = fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK|flags);
-----


-------------------------------------------------------
5  connectシステムコール

動作:
コネクションの確立時にサーバ側へ接続確立要求を出すため、クライアント側で使う。

定義:
#include<sys/socket.h>
int connect(int socket, const struct sockaddr *address, size_t address_len);

引数の意味:
(上記同様。)

帰り値:
 0   成功
-1   失敗

戻り値が失敗(-1)の場合のerrnoの値:
EBADF          socketに指定されたファイルディスクリプタが無効である。
EALREADY       指定されたソケットで接続がすでに進行中である。
ETIMEDOUT      接続タイムアウトが発生した。
ECONNREFUSED   要求された接続がサーバによって拒否された。
例外は、
EINTR          connectの呼び出しがハンドラで処理されるシグナルによって割り込まれた場合、
               呼び出しは失敗するが接続の試みは異常終了せず、非同期で接続がセットアップされる。
EINPROGRESS    該当するファイルディスクリプタにO_NONBLOCKを指定し、
               接続がすぐにセットアップできなかった場合EINPROGRESSが設定され、
               非同期で接続がセットアップされる。


-------------------------------------------------------
6  closeシステムコール

意味:
終了処理。(ただし、すぐに切断するためデータが失われる事がある。)


-------------------------------------------------------
7  その他のシステムコール
read、writeシステムコール        tcpを利用したコネクション型のデータ転送時に使用。
sendto、recvfromシステムコール   udpを利用したコネクションレス型のデータ転送時に使用。
                                 メッセージを送るたびに相手のソケットアドレスを指定できる。
send、recvシステムコール         コネクション型の時は帯域外データを扱う事ができる。
                                 つまり緊急用である。
                                 コネクションレス型の時はメッセージごとに宛先指定を省略できる。
shutdownシステムコール           終了処理。(ただし、バッファリングされていたデータは転送可能。)
selectシステムコール             入出力の多重化。複数のソケットの状態を調べる事ができる。


-------------------------------------------------------
8  バイト順序変換
データのメモリアドレスへの格納順序はコンピュータによって異なっており、
2つの方式がある。それは、モトローラ680x0等のCPUで使われているリトル
エンディアン方式と、インテル80x86等のCPUで使われているビッグエンディ
アン方式である。前者は2バイトならn+1, nのアドレス順で並ぶのに対し、後
者はn, n+1のアドレス順で並ぶ。
TCP/IPプロトコルのヘッダはビッグエンディアン方式で処理される。ホスト
の形式からビッグエンディアン方式に変換するライブラリを以下に記述する。
定義:
#include <sys/types.h>
#include <netinet/in.h>
#include <inttypes.h>
u_long  htonl(u_long host);    整数(long)をホスト形式からネットワーク形式のバイト順序へ変換。
u_short htons(u_short host);   整数(short)をホスト形式からネットワーク形式のバイト順序へ変換。
u_long  ntohl(u_long net);     整数(long)をネットワーク形式からホスト形式のバイト順序へ変換。
u_short ntohs(u_short net);    整数(short)をネットワーク形式からホスト形式のバイト順序へ変換。
ただし、u_long は unsigned long int や uint32_t と同じで、u_shortは
unsigned short int や uint16_t と同じ。


-------------------------------------------------------
9  プログラム例
TCP/IPを使ったCプログラムの例を下に添付します。
サーバが、クライアントから送られたキャラクタコードの値を一つ増やして返すプログラムです。
プログラムソースは、inet_server.cとinet_client.cの2ファイルです。
Unix系用です。(Windows系OSが提供するWinSOCKは、システムコールが違う。)
IPアドレスにloopback(127.0.0.1)を用いているので、一つのPCで実験が可能です。

socketシステムコールで、socket(AF_INET, SOCKSTREAM, IPPROTO_TCP)
と宣言することにより、TCPを使って2つのPC間でクライアントサーバ型のプ
ログラムを作ることができます。
ちなみに、IPPROTO_TCPはTCP/IPを使うという意味です。UDP/IPを使うなら、
ここをIPPROTO_UDPに変えて、sendto、recvfromシステムコールで通信して
ください。

使用例 ( > 以降が入力部分 )
inet_server.c、inet_client.cの2つのファイルを作り、

> gcc -lsocket -lnsl -o inet_server inet_server.c
> gcc -lsocket -lnsl -o inet_client inet_client.c
> ./inet_server &
> ./inet_client a
server waiting
char from server = b
----- inet_server.c
/* gcc -lsocket -lnsl -o inet_server inet_server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define ERROR(x) {\
                  fprintf(stderr, "server - ");\
                  perror(x);\
                  exit(1);\
                 }

int main()
{

    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;

    /*サーバ用ソケット作成*/
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    /*ソケットに名前をつける(bind)*/
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    server_address.sin_port = 9374;
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *) &server_address, server_len);

    /*接続キューを作成しクライアントからの接続を待つ*/
    listen(server_sockfd, 5);
    while(1)
    {
        char ch;
        printf("server waiting\n");

        /*接続を受け入れる*/
        client_sockfd =
            accept(server_sockfd, (struct sockaddr *)&client_address, &client_len);

        /*client_sockfdを介してクライアントに対する読み書きができるようになる*/
        read(client_sockfd, &ch, 1);
        ch++;
        write(client_sockfd, &ch, 1);
        close(client_sockfd);
    }
}
----- inet_client.c
/* gcc -lsocket -lnsl -o inet_client inet_client.c */
#include  <sys/types.h>
#include  <sys/socket.h>
#include  <stdio.h>
#include  <netinet/in.h>
#include  <arpa/inet.h>
#include  <unistd.h>

#define ERROR(x) {\
                  fprintf(stderr, "client - ");\
                  perror(x);\
                  exit(1);\
                 }

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

    int sockfd, len, result;
    struct sockaddr_in address;
    char ch = **(++argv);  /* argv[1][0]でも可能 */

    /*クライアント用ソケット作成*/
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    /*サーバ側と同じ名前でソケットの名前を指定*/
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr("127.0.0.1");
    address.sin_port = 9374;
    len = sizeof(address);

    /*クライアントのソケットとサーバのソケットの接続*/
    result = connect(sockfd, (struct sockaddr *)&address, len);
    if(result == -1) ERROR("oops : client1");

    /*sockfdを介して読み書きができるようにする*/
    write(sockfd, &ch, 1);
    read(sockfd, &ch, 1);
    printf("char from server = %c\n", ch);
    close(sockfd);
    exit(0);
}
--------------------------------------------------------
[プロトコルメモ]

1  プロトコルPOP3(Post Office Protocol)

TCP(トランスポート層)におけるPOP3ポート番号:110番

コマンド
USER ユーザ名を送る
PASS パスワードを送る
APOP ユーザ名とパスワードを暗号化して送る
QUIT 接続を切る
STAT メールボックスのサマリーを表示する
RETR メッセージを取り出す
DELE メッセージを削除する
NOOP 何もしない(No operation)
RSET 接続をリセットする
TOP  メッセージの最初のn行だけを取り出す
UIDL メッセージのユニークな識別子を取り出す

POP3の正確な仕様

http://www.imasy.or.jp/archives/rfc/rfc1939.txt


2  プロトコルSMTP(Send Mail Transfer Protocol)

TCPにおけるSMTPポート番号:25番

コマンド
HELO メッセージの送信者を示す
MAIL メールのトランザクションを初期化する
RCPT メールメッセージの受信者を示す
DATA データ転送の開始を示す
QUIT セッションの終り
RSET トランザクションをリセットする

SMTPの正確な仕様

http://www.imasy.or.jp/archives/rfc/rfc1891.txt


-------------------------------------------------------
[RFC(Requests For Comment)メモ]

http://www.imasy.or.jp/~masaka/rfc-jp/

RFC821        SMTP) Simple Mail Transfer Protocol
RFC822        SMTP) Standard for the format of ARPA Internet text messages
:SMTPメールファイルのヘッダ形式について。Xで始まるフィールドは拡張されたフィールド。
RFC918        POP1
RFC937        POP2
RFC974        Mail routing and the domain system
RFC1034       DNS) Domain names - concepts and facilities
RFC1035       DNS) Domain names - implementation and specification
RFC1225       POP3) Post Office Protocol: Version 3
RFC1341,1342  MIME
:UNIXメールでマルチメディアデータを扱うための仕様
RFC1466       キャラクタセットISO-2022-JP:日本語を扱うインターネットメッセージのためのドラフト(1993.06)
RFC1521       MIME) MIME (Multipurpose Internet Mail Extension Part One: Machanisms for Specifying and Describing the Format ofInternet Message Bodies
:MIMEのメッセージボディ
RFC1522       MIME) MIME (Multipurpose Internet Mail Extension Part Two: Message Header Extensions for Non-ASCII Text
:MIMEのメッセージヘッダ
RFC1725       POP3


-------------------------------------------------------
[メモ(参考文献3)]

MAPI
  - マイクロソフトが提唱した電子メールAPI

/usr/lib/sendmail の機能
  - SMTPサーバ
  - メールの送信機能
  - メールのルーティング機能

SMTP(p.64)
  - 送信クライアントと受信サーバ
  - 双方向通信
  - ASCII文字列をコマンドとしてやりとり
  - 通信路として何を用いるかは特定されていないが、TCPを用いるのが一般的。
  - TCPの場合、受信側のTCPのポートは25番で接続。

POP3(p.70)
  - トランスポート層としてTCPを用い、110番ポートを使用。

メールヘッダ詳細(p71,72)

改行
  - UNIXメール(Local)             LFコード(0x0A)
  - インターネットメール(Global)  CR-LFコード(0x0D-0X0A)

MIMEバイナリテキスト変換方式とアルゴリズム(p.112,113)
  - Quated Printable
  - BASE64


お勉強のトップへ