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; }