ホーム > デベロッパ > BREW プログラミング入門 > TCP / IP ネットワークプログラミング > - 2 / 3 -

TCP / IP ネットワークプログラミング - 2 / 3 -

TCP / IP ネットワークプログラミングの処理フロー

本アプリの処理フローです。ボックスに記された TCPConnect, TCPDNSConnect, TCPConnected, TCPWrite, TCPRead, Draw はサンプルコードの関数に対応します。

IP アドレスの取得

通信先の ホスト名 から IP アドレスを取得するために、DNS サーバに問い合わせます。

TCPConnect 関数は、最初に CALLBACK_Init マクロ関数で 第 2 引数の TCPDNSConnect をコールバック関数として pApp->callback という AEECallback 構造体を初期化します。

次に INETMGR_GetHostByName 関数を使って DNS サーバからIP アドレスを取得し、AEEDNSResult 構造体の変数 pApp->dnsresult に格納します。

#define ACCESS_URL    "www.google.com"    //どの URL でも構いません

//  ホスト名からIPアドレスを調べる
static void TCPConnect(SocketApp* pApp)
{
    CALLBACK_Init(&pApp->callback, TCPDNSConnect, pApp);
    INETMGR_GetHostByName(pApp->netmgr, &pApp->dnsresult,
                               ACCESS_URL, &pApp->callback);
    return;
}

AEEDNSResult 構造体は次のように定義されす。AEEDNSResult 構造体の変数 pApp->dnsresult では、nResult フィールドには取得した IP アドレスの個数、addrs[] フィールドには取得した IP アドレスのリストが保持されます。

typedef struct
{
    int     nResult;
    INAddr  addrs[AEEDNSMAXADDRS];
} AEEDNSResult;

ソケット作成・接続

IP アドレスを取得すると、コールバック関数 TCPDNSConnect に処理が移ります。

TCPDNSConnect 関数では、INETMGR_OpenSocket 関数を使って ISocket のインスタンスを生成し、ソケットを開き、ホストへ接続します。

少々時間のかかるホスト接続の処理 TCPConnected はコールバック関数として登録します。

// ホストに接続する。
static void TCPDNSConnect(void* p)
{
    SocketApp* pApp = (SocketApp*)p;
    int nErr = pApp->dnsresult.nResult;

    if (nErr > AEEDNSMAXADDRS) {
        // IP アドレスの取得に失敗。nErr の値によって原因がわかる。
        DBGPRINTF("** DNS Lookup Failed: Error %d\n", nErr);
    }
    else {
        pApp->socket = INETMGR_OpenSocket(pApp->netmgr, AEE_SOCK_STREAM);
        if (pApp->socket == NULL) {
            DBGPRINTF("** OpenSocket Failed: Error %d\n",
                INETMGR_GetLastError(pApp->netmgr));
        }
        else {
            // pApp->dnsresult.addrs[0] に取得した IP アドレスが保持される。
            // HTONS は、ポート番号を通信用に変換するマクロ関数。
            nErr = ISOCKET_Connect(pApp->socket, pApp->dnsresult.addrs[0],
                                   HTONS(80), TCPConnected, pApp);
            if (nErr != AEE_NET_SUCCESS) {
                DBGPRINTF("** Connect Failed: Error %d\n",
                    ISOCKET_GetLastError(pApp->socket));
                ISOCKET_Cancel(pApp->socket, NULL, NULL);
                ISOCKET_Release(pApp->socket);
                pApp->socket = NULL;
            }
            else {
                DBGPRINTF("** connecting...\n");
            }
        }
    }
    return;
}

メッセージの送信

TCPConnected 関数でホスト接続したら、TCPWrite 関数でメッセージを送信します。

// メッセージの送信を開始する。
static void TCPConnected(void* p, int nErr)
{
    SocketApp* pApp = (SocketApp*)p;

    if ((nErr == AEE_NET_SUCCESS) || (nErr == AEE_NET_EISCONN)) {
        // エラーがなければメッセージを送信 (TCPWrite 関数)
        pApp->index = 0;
        TCPWrite(pApp);
    }
    else {
        DBGPRINTF("** Connect Failed: Error %d\n", nErr);
        ISOCKET_Cancel(pApp->socket, NULL, NULL);
        ISOCKET_Release(pApp->socket);
        pApp->socket = NULL;
    }
    return;
}

TCPWrite 関数は、"GET / HTTP/1.0¥n¥n" というリクエストを HTTP サーバに送信します。

その後、TCPRead 関数はトップページを受信します。

ソケットプログラミングでは、送受信するメッセージの大きさが予測できません。

そのため、TCPWrite 関数と TCPRead 関数では、ぞれぞれ自分自身である TCPWrite と TCPRead をコールバック関数として登録し、完了するまでメッセージの送受信を繰り返します。

// メッセージを送信、完了すれば受信を開始する。
static void TCPWrite(SocketApp* pApp)
{
    int nSent;
    char message[] = "GET / HTTP/1.0\n\n";

    // メッセージの送信。返値は実際に送信したバイト数 (もしくはエラーコード)
    // 第三引数はバッファのサイズ
    nSent = ISOCKET_Write(pApp->socket, (byte *)message + pApp->index,
                 (uint16)sizeof(message) - pApp->index);
    if (nSent == AEE_NET_ERROR) {
        DBGPRINTF("** Write Failed: Error %d\n",
                  ISOCKET_GetLastError(pApp->socket));
        ISOCKET_Cancel(pApp->socket, NULL, NULL);
        ISOCKET_Release(pApp->socket);
        pApp->socket = NULL;
    }
    else {
        // nSent が AEE_NET_WOULDBLOCK の場合は、メッセージが送信できなかった。
        if (nSent != AEE_NET_WOULDBLOCK) {
            pApp->index += nSent;
            if (pApp->index >= sizeof(message)) {
                // 送信終了
                DBGPRINTF("** writing complete...\n");
                // 受信開始
                TCPRead(pApp);
                return;
            }
        }
        DBGPRINTF("** writing...\n");
        // 送信が完了していないときは、
        // 再びコールバック関数(この関数自身)を登録する。
        ISOCKET_Writeable(pApp->socket, (PFNNOTIFY)TCPWrite, pApp);
    }
    return;
}

メッセージの受信

全てのメッセージの受信が完了したら、ISOCKET_Release 関数を呼び出してソケットを閉じます。

最後に、Draw 関数を呼び出して受信したメッセージを画面に表示します。

// メッセージを受信、完了すればリソースを開放し画面に描画する。
static void TCPRead(SocketApp * pApp)
{
    char buffer[0x7FFF];
    int nRead;

    // メッセージの受信。返値は実際に受信したバイト数 (もしくはエラーコード)
    // 第三引数はバッファのサイズ
    nRead = ISOCKET_Read(pApp->socket, (byte*)buffer, 0x7FFF - 1);
    if (nRead == AEE_NET_ERROR) {
        DBGPRINTF("** Read Failed: Error %d\n",
                  ISOCKET_GetLastError(pApp->socket));
        ISOCKET_Cancel(pApp->socket, NULL, NULL);
        ISOCKET_Release(pApp->socket);
        pApp->socket = NULL;
    }
    else {
        // nRead が AEE_NET_WOULDBLOCK の場合は、メッセージが受信できなかった。
        if (nRead != AEE_NET_WOULDBLOCK) {
            if (nRead == 0) {
                // 受信終了
                DBGPRINTF("** reading Complete...\n");
                ISOCKET_Release(pApp->socket);
                pApp->socket = NULL;
                return;
            }
            else {
                // バッファの終端に '\0' を代入。
                buffer[nRead] = 0;
                // 文字列の画面描画
                COMMON_WriteString(&pApp->common, buffer);
                COMMON_Draw(&pApp->common);
            }
        }
        DBGPRINTF("** reading...\n");
        // 受信が完了していないときは、再びコールバック関数(この関数自身)を登録
        ISOCKET_Readable(pApp->socket, (PFNNOTIFY)TCPRead, pApp);
    }
    return;
}