ウィンドウでのTCP/IPプログラムだって、DOS窓でWinSockAPI

|

WindowsのGUI(ウィンドウ)プログラムでTCP/IP通信を利用する際、アプリケーションが固まったように見えるブロッキング動作が問題となることが有ります。

ずぼらのめんどくさがり屋には、メインスレッドと並列処理可能なサブスレッドが単純明快で打って付けかと。

試しに、WinSockの通信(データの送受信)部分をそっくり其の儘、サブスレッドに突っ込んだだけのサンプルを作ってみました。

ブロッキング動作をする accept, send, recv がサブスレッド上で動作するので、ウィンドウの最低限の動作(メインスレッドが担当)は、滞り無く行われるはずです。


<注意点>

・WSACleanup APIが呼び出されると、プロセス内の全てのスレッドによって発行されたあらゆる保留中のブロッキングや非同期WinSock呼び出しがキャンセルされます。

・WSACleanup APIが呼び出された時点でオープンされていたソケットはリセットされ、closesocket を呼び出した場合と同じように自動的に解放されます。

・全ての保留中データが確実に送信されるようにするには、アプリケーションは shutdown を使用してコネクションをクローズし、クローズ完了まで待機してから closesocket および WSACleanup を呼び出します。

・サーバを終了した直後に再度サーバを起動しようとすると、bind エラーで終了する事が有ります。この現象は、サーバ側で先にクローズを実行すると発生する事が確認されています。先にクライアント側でクローズを実行する事で、このエラーを回避出来るようです。


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


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

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

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

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

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

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


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

#include <stdio.h>
#include <winsock2.h>
#include <process.h>  // _beginthreadex 関数

#define WINCLASS_NAME "Form.003"
#define ID_BUTTON1 1001

unsigned WINAPI subThread1(void *hCtr)
{
    WSADATA wsaData;
    SOCKET ss, cs;  // ss - server socket, cs - client socket
    SOCKADDR_IN sa, ca; // sa - server address, ca - client address
    int len;
    char str[64];
    char buf[64];

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

    // ソケットの作成
    sa.sin_family = AF_INET;
    if((ss = socket(sa.sin_family, SOCK_STREAM, 0)) == INVALID_SOCKET) {
        printf("socket error : %d\n", WSAGetLastError());
        WSACleanup();
        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(ss, (SOCKADDR *)&sa, sizeof(sa)) == SOCKET_ERROR) {
        printf("bind error : %d\n", WSAGetLastError());
        WSACleanup();
        return(1);
    }

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

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

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

    memset(buf, 0, sizeof(buf));
    if((len = recv(cs, buf, sizeof(buf), 0)) < 0) {
        printf("recv error : %d\n", WSAGetLastError());
        WSACleanup();
        return(1);
    }
//  printf("From Client : len = %d, data = %s\n", len, buf);
    sprintf(str, "len = %d, data = %s", len, buf);
    MessageBox(NULL, str, "クライアント送信データ", MB_OK | MB_ICONINFORMATION);

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

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

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

LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hBtn1;
    HINSTANCE hInstance;
    int wmid, wmevent;

    switch(uMsg) {
    case WM_CREATE :
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;

        if((hBtn1 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 160, 220, 70, 20, hWnd, (HMENU)ID_BUTTON1, hInstance, NULL)) == NULL) {
            printf("ボタン1コントロールを作成出来ませんでした。\n");
            return(FALSE);
        }
        break;
    case WM_COMMAND :
        wmid = LOWORD(wParam);
        wmevent = HIWORD(wParam);
        switch(wmevent) {
        case BN_CLICKED :
            switch(wmid) {
            case ID_BUTTON1 :
                EnableWindow(hBtn1, FALSE);
                SetWindowText(hBtn1, "押下1");
                _beginthreadex(NULL, 0, subThread1, NULL, 0, NULL);
                break;
            default :
                break;
            }
            break;
        default :
            break;
        }
        break;
    case WM_CLOSE :
        if(MessageBox(hWnd, "ウィンドウを閉じます。\nよろしいですか?", "確認", MB_OKCANCEL | MB_ICONWARNING) == IDOK) {
            DestroyWindow(hWnd);
        } else {
            SetWindowText(hBtn1, "ボタン1");
            EnableWindow(hBtn1, TRUE);
        }
        break;
    case WM_DESTROY :
        PostQuitMessage(0);
        break;
    default :
        return DefWindowProc(hWnd , uMsg , wParam , lParam);
    }

    return(0);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wcx;
    MSG msg;
    int ret;

    /* ウィンドウクラスの登録 */
    wcx.cbSize = sizeof(WNDCLASSEX);
    wcx.style = CS_HREDRAW | CS_VREDRAW;
    wcx.lpfnWndProc = WinProc;
    wcx.cbClsExtra = 0;
    wcx.cbWndExtra = 0;
    wcx.hInstance = hInstance;
    wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcx.lpszMenuName = NULL;
    wcx.lpszClassName = WINCLASS_NAME;
    wcx.hIconSm = NULL;

    if(!RegisterClassEx(&wcx)) {
        printf("ウィンドウクラスの登録が出来ませんでした。\n");
        return(0);
    }

    /* 登録したクラスのウィンドウを生成 */
    hWnd = CreateWindowEx(WS_EX_LEFT, WINCLASS_NAME, "ウインドウタイトル3", WS_OVERLAPPEDWINDOW, 50, 50, 400, 300, NULL, NULL, hInstance ,NULL);
    if(hWnd == NULL) {
        printf("ウィンドウが作成出来ませんでした。\n");
        return(FALSE);
    }

    /* ウィンドウの表示 */
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    /* メッセージループ */
    while((ret = GetMessage(&msg, NULL, 0, 0)) > 0 ) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if(ret == -1) {
        printf("メッセージの取得に失敗しました。\n");
        return(FALSE);
    }
    return((int)msg.wParam);
}

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

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

#include <stdio.h>
#include <winsock2.h>
#include <process.h>  // _beginthreadex 関数

#define WINCLASS_NAME "Form.003"
#define ID_BUTTON1 1001

unsigned WINAPI subThread1(void *hCtr)
{
    WSADATA wsaData;
    LPHOSTENT host;
    SOCKET cs;  // cs - client socket
    SOCKADDR_IN sa; // sa - server address
    int i, len;
    char str[64];
    char buf[64];
    char *argv = "localhost";
//  char *argv = "127.0.0.1";

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

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

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

    memset(buf, 0, sizeof(buf));
    if((len = recv(cs, buf, sizeof(buf), 0)) < 0) {
        printf("recv error : %d\n", WSAGetLastError());
        WSACleanup();
        return(1);
    }
//  printf("From Server : len = %d, data = %s\n", len, buf);
    sprintf(str, "len = %d, data = %s", len, buf);
    MessageBox(NULL, str, "サーバ送信データ", MB_OK | MB_ICONINFORMATION);

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

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

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

LRESULT CALLBACK WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HWND hBtn1;
    HINSTANCE hInstance;
    int wmid, wmevent;

    switch(uMsg) {
    case WM_CREATE :
        hInstance = ((LPCREATESTRUCT)lParam)->hInstance;

        if((hBtn1 = CreateWindowEx(WS_EX_LEFT, "BUTTON", "ボタン1", WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON, 160, 220, 70, 20, hWnd, (HMENU)ID_BUTTON1, hInstance, NULL)) == NULL) {
            printf("ボタン1コントロールを作成出来ませんでした。\n");
            return(FALSE);
        }
        break;
    case WM_COMMAND :
        wmid = LOWORD(wParam);
        wmevent = HIWORD(wParam);
        switch(wmevent) {
        case BN_CLICKED :
            switch(wmid) {
            case ID_BUTTON1 :
                EnableWindow(hBtn1, FALSE);
                SetWindowText(hBtn1, "押下1");
                _beginthreadex(NULL, 0, subThread1, NULL, 0, NULL);
                break;
            default :
                break;
            }
            break;
        default :
            break;
        }
        break;
    case WM_CLOSE :
        if(MessageBox(hWnd, "ウィンドウを閉じます。\nよろしいですか?", "確認", MB_OKCANCEL | MB_ICONWARNING) == IDOK) {
            DestroyWindow(hWnd);
        } else {
            SetWindowText(hBtn1, "ボタン1");
            EnableWindow(hBtn1, TRUE);
        }
        break;
    case WM_DESTROY :
        PostQuitMessage(0);
        break;
    default :
        return DefWindowProc(hWnd , uMsg , wParam , lParam);
    }

    return(0);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wcx;
    MSG msg;
    int ret;

    /* ウィンドウクラスの登録 */
    wcx.cbSize = sizeof(WNDCLASSEX);
    wcx.style = CS_HREDRAW | CS_VREDRAW;
    wcx.lpfnWndProc = WinProc;
    wcx.cbClsExtra = 0;
    wcx.cbWndExtra = 0;
    wcx.hInstance = hInstance;
    wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcx.lpszMenuName = NULL;
    wcx.lpszClassName = WINCLASS_NAME;
    wcx.hIconSm = NULL;

    if(!RegisterClassEx(&wcx)) {
        printf("ウィンドウクラスの登録が出来ませんでした。\n");
        return(0);
    }

    /* 登録したクラスのウィンドウを生成 */
    hWnd = CreateWindowEx(WS_EX_LEFT, WINCLASS_NAME, "ウインドウタイトル3", WS_OVERLAPPEDWINDOW, 50, 50, 400, 300, NULL, NULL, hInstance ,NULL);
    if(hWnd == NULL) {
        printf("ウィンドウが作成出来ませんでした。\n");
        return(FALSE);
    }

    /* ウィンドウの表示 */
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    /* メッセージループ */
    while((ret = GetMessage(&msg, NULL, 0, 0)) > 0 ) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    if(ret == -1) {
        printf("メッセージの取得に失敗しました。\n");
        return(FALSE);
    }
    return((int)msg.wParam);
}

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