TCP/IPプログラムのにほへも、DOS窓でWinSockAPI

|

TCP/IP通信と言うと、サーバ・クライアントシステムを思い浮かべますが、行き成り外部とデータの遣り取りをするのは危なっかしい気がしますので、テスト環境にも利用される(?)同一パソコン内のプロセス間通信(ソケット通信)を試してみる事に。

試しに、WinSockAPIを使って、内部プロセス間通信(データの送受信)を行うだけのサンプルを作ってみました。

sa.sin_addr.s_addr = ((IN_ADDR *)host->h_addr_list[i])->s_addr;

sa.sin_addr.s_addr = (*(IN_ADDR *)host->h_addr_list[i]).s_addr;

上の2つの代入文は、どちらも同じ値を代入します。分かり易い(ピンときた)方を選ぶと良いかも。

因みに、非常に難解なTCP/IP通信をぎゅっと短く平たく(間違いを恐れず)大雑把に言うと、通信する相手<パソコン>をIPアドレスを使って特定する役割を担うのがIP、通信する相手の種類<アプリケーション>をポート番号を使って指定する役割を担うのがTCP、2つ合わせて正確無比な通信をインターネット上で実現しています。


<注意点>

・WinSockAPIの accept, send, recv は、通常ブロッキング動作をします。Windows ではノンブロッキング動作にすることも出来るようですが、select を使った監視処理の方がお勧め(?)のようです。

・ブロッキング動作は利用者にすれば、アプリケーションが固まってしまったように見えます。

・プログラムはWinSockの利用を終了する時、WSACleanup APIを呼び出して、WinSockがプログラムのために確保した全てのリソースを解放しなければなりません。

・プログラムは WSAStartup APIの呼び出し成功数に応じて、WSACleanup APIを呼び出さなければなりません。最後の WSACleanup API呼び出しのみが実際のリソース解放を行います。それ以外は単に、WS2_32.DLL が持つ内部参照カウント数を減算するだけです。

・重要:sa.sin_addr.s_addr = INADDR_ANY; の設定は外部に無造作に通信窓を開けるため、セキュリティーホールに成り得ます。実際、IPアドレス 204.205.206.207 という得体の知れない(多分偽装?)相手から、接続要求を受け取る羽目に。安易に INADDR_ANY を利用すると、きっと痛い目に合う事に・・・。


【SOCKADDR構造体】

SOCKADDR構造体のメンバー
メンバー名説明
sa_family unsigned short アドレスファミリを表す定数 *1
sa_data char [14] sa_family の固有情報を表すための空き領域

 *1:通常はAF_INET(IPv4インターネットプロトコル)


【SOCKADDR_IN構造体】

SOCKADDR_IN構造体のメンバー
メンバー名説明
sin_family short アドレスファミリを表す定数 *1
sin_port unsigned short ポート番号
sin_addr IN_ADDR IPアドレス
sin_zero char [8] 値0で初期化される空き領域

 *1:通常はAF_INET(IPv4インターネットプロトコル)


お試し環境
  WindowsXP 32bit Edition
  Visual C++ 2008


/*---- サーバの作成 --------------------- コマンドライン -------------------*/

D:\vc2008\dllchk>cl server.c ws2_32.lib

/*----------------------------------------------------------------------------*/

/*---- クライアントの作成 ------------------ コマンドライン ----------------*/

D:\vc2008\dllchk>cl client.c ws2_32.lib

/*----------------------------------------------------------------------------*/


/*---- client の引数がホスト名----------------- お試し結果 -----------------*/

D:\vc2008\dllchk>server
接続要求受付:クライアントアドレス = 127.0.0.1、ポート = 2401
From Client : len = 23, data = Let's try Communication

D:\vc2008\dllchk>client localhost
ホスト名でのサーバ接続完了
From Server : len = 33, data = Wellcome to WinSock Communication

/*----------------------------------------------------------------------------*/

/*---- client の引数がアドレス----------------- お試し結果 -----------------*/

D:\vc2008\dllchk>server
接続要求受付:クライアントアドレス = 127.0.0.1、ポート = 2417
From Client : len = 23, data = Let's try Communication

D:\vc2008\dllchk>client 127.0.0.1
アドレスでのサーバ接続完了
From Server : len = 33, data = Wellcome to WinSock Communication

/*----------------------------------------------------------------------------*/


/*---- server.c ----------------------- お試しソース -----------------------*/

#include <stdio.h>
#include <winsock2.h>

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    SOCKET s, c;  // s - server socket, c - client socket
    SOCKADDR_IN sa, ca; // sa - server address, ca - client address
    int len;
    char buf[64];

    // WinSock の初期化
    if(WSAStartup(WINSOCK_VERSION, &wsaData) != 0) {
        printf("WSAStartup error\n");
        return(1);
    }

    // ソケットの作成
    sa.sin_family = AF_INET;
    if((s = socket(sa.sin_family, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        printf("socket error : %d\n", WSAGetLastError());
        return(1);
    }

    // ソケットに名前を付ける
    sa.sin_port = htons(54321);
//  sa.sin_addr.s_addr = INADDR_ANY;
    sa.sin_addr.s_addr = inet_addr("127.0.0.1");
    if(bind(s, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR) {
        printf("bind error : %d\n", WSAGetLastError());
        return(1);
    }

    // 接続要求待ち
    if(listen(s, 1) == SOCKET_ERROR) {
        printf("listen error : %d\n", WSAGetLastError());
        return(1);
    }

    // 接続要求受付
    len = sizeof(ca);
    if((c = accept(s, (SOCKADDR *)&ca, &len)) == INVALID_SOCKET) {
        printf("accept error : %d\n", WSAGetLastError());
        return(1);
    } else {
        printf("接続要求受付:クライアントアドレス = %s、ポート = %d\n", inet_ntoa(ca.sin_addr), ntohs(ca.sin_port));
    }

    if(send(c, "Wellcome to WinSock Communication", 33, 0) < 1) {
        printf("send error : %d\n", WSAGetLastError());
        return(1);
    }

    memset(buf, 0, sizeof(buf));
    if((len = recv(c, buf, sizeof(buf), 0)) < 0) {
        printf("recv error : %d\n", WSAGetLastError());
        return(1);
    }
    printf("From Client : len = %d, data = %s\n", len, buf);

    // WinSock 切断
    shutdown(s, SD_BOTH);

    // ソケットの破棄
    closesocket(s);
    closesocket(c);

    // WinSock のリソース解放
    WSACleanup();
    return(0);
}

/*----------------------------------------------------------------------------*/

/*---- client.c ----------------------- お試しソース -----------------------*/

#include <stdio.h>
#include <winsock2.h>

int main(int argc, char *argv[])
{
    WSADATA wsaData;
    LPHOSTENT host;
    SOCKET c;  // c - client socket
    SOCKADDR_IN sa; // sa - server address
    int i, len;
    char buf[64];

    if(argc != 2) {
        printf("Usage : %s dest\n", argv[0]);
        return(1);
    }

    // WinSock の初期化
    if(WSAStartup(WINSOCK_VERSION, &wsaData) != 0) {
        printf("WSAStartup Error\n");
        return(1);
    }

    if((c = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        printf("socket error : %d\n", WSAGetLastError());
        return(1);
    }

    sa.sin_family = AF_INET;
    sa.sin_port = htons(54321);
    sa.sin_addr.s_addr = inet_addr(argv[1]);
    if(sa.sin_addr.s_addr == INADDR_NONE) { // 入力がホスト名なら
        if((host = gethostbyname(argv[1])) == NULL) {
            printf("ホスト名の取得に失敗しました : %s\n", argv[1]);
            return(1);
        }
        // サーバへの接続要求
        for(i = 0; (IN_ADDR *)host->h_addr_list[i] != NULL; i++) {
//          sa.sin_addr.s_addr = ((IN_ADDR *)host->h_addr_list[i])->s_addr;
            sa.sin_addr.s_addr = (*(IN_ADDR *)host->h_addr_list[i]).s_addr;
            if(connect(c, (SOCKADDR *)&sa, sizeof(sa)) == 0) {
                break;
            }
        }
        if((IN_ADDR *)host->h_addr_list[i] == NULL) {
            printf("connect1 error : %d\n", WSAGetLastError());
            return(1);
        }
        printf("ホスト名でのサーバ接続完了\n");
    } else { // 入力がドット表記のアドレスなら
        // サーバへの接続要求
        if(connect(c, (SOCKADDR *)&sa, sizeof(sa)) != 0) {
            printf("connect2 error : %d\n", WSAGetLastError());
            return(1);
        }
        printf("アドレスでのサーバ接続完了\n");
    }

    memset(buf, 0, sizeof(buf));
    if((len = recv(c, buf, sizeof(buf), 0)) < 0) {
        printf("recv error : %d\n", WSAGetLastError());
        return(1);
    }
    printf("From Server : len = %d, data = %s\n", len, buf);

    if(send(c, "Let's try Communication", 23, 0) < 1) {
        printf("send error : %d\n", WSAGetLastError());
        return(1);
    }

    // ソケットの破棄
    closesocket(c);

    // WinSock のリソース解放
    WSACleanup();
    return(0);
}

/*----------------------------------------------------------------------------*/
/*============================================================================*/