Server Program を Windows で作成する

セッションを開設して Client に現在時刻を送信する Server Program を Windows で作成します。
Server では「ノンブロッキング方式」を使ってタイマ割り込みで Client からの接続を監視します。

ブロッキングとは、関数の実行が終了するまで、制御が返ってこないことを言います。
例えば recv や accept は、データが受信できるまで制御が戻ってきません。
Windows のプログラムでは、これでは困るのでノンブロッキング方式を使います。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プロジェクトの設定

  1. このプログラムは、私が作成した Wsock Object Class を使用します。
    新規プロジェクトを作成して、次のファイルをプロジェクトのフォルダーに格納して下さい。
    ファイル 説明
    Main.cpp メインプログラムファイル
    Wsock.h Wsock Object Class のヘッダファイル
    Wsock.lib Wsock Object Class のライブラリ
  2. ページ先頭の画像を参考に DialogBox を作成して下さい。
    BOX キャプション ID
    DialogBox TCP Server IDD_DIALOG1
    EditControl PORT番号 IDC_PORT
    Button 進行メッセージ IDC_MSG
    Button Start UP IDC_STARTUP
    Button Listen IDC_LISTEN
    Button 終了 IDCANCEL
  3. このプログラムは Client Program を Windows で作成する と対になっています。
    Server 側のプログラムを起動してセッションを開設してから Client 側のプログラムを起動して下さい。
  4. Client 側の受信ボタンをクリックすると Server から日付と現在時刻が送られてきます。

プログラムの説明

  1. SendMsg() 関数のオーバーライドの説明です。
    SendMsg() 関数は Wsock Object Class で virtual 関数として定義されています。
    この関数は、タイマ割り込みで起動して Client からの接続を監視する CheckSrv() 関数から呼び出されます。
    このままでは何時も "HELLO" を送り返すだけです。
        virtual int SendMsg(char *com);
    
        //★ メッセージを送信する virtual 関数
        int Wsock::SendMsg(char *com)
        {   int     n;
    
            // メッセージを送信します
            n = send(sockw,"HELLO",5,0);
            if (n < 1)
            {   strcpy(szMSG,"send に失敗しました");
                return -1;
            }
            strcpy(szMSG,"Client に送信しました");
            return 0;
        }
        
  2. 今回のように「日付と現在時刻」を送るには SendMsg() をオーバーライドしなければなりません。
    そこで Wsock Object Class を継承した TimeSvr Object Class を作成して、その中で SendMsg() をオーバーライドします。
    手始めに Wsock Object Class をそのまま使って、"HELLO" を返すプログラムから初めて下さい。
    "HELLO" を返すだけならば Time Server Object Class の定義は一切不要です。
    Object Class の継承は イメージ画像(花札.bmp)を入力して、一枚のカードを表示する を参照して下さい。
        //★ Time Server Object Class
        class TimeSvr : public Wsock
        {
            int     SendMsg(char *com);
        };
    
        //★ COMMAND を解析してメッセージを送信
        //   日付と現在時刻を編集して送信する
        int  TimeSvr::SendMsg(char *com)
        {   SYSTEMTIME  st;
            int         n;
            char        msg[80];
    
            GetLocalTime(&st);
            wsprintf(msg,"Current Time  %04d/%02d/%02d %02d:%02d:%02d",
                     st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
    
            // メッセージを送信します
            n = send(sockw,msg,strlen(msg),0);
            if (n < 1)
            {   strcpy(szMSG,"send に失敗しました");
                return -1;
            }
            strcpy(szMSG,"Client に送信しました");
            return 0;
        }
        
  3. TimeSvr *App は Time Server Object Class の定義です。
    PORT でポートアドレスを定義します。
        TimeSvr     *App= NULL;     //TimeSvr Object Class 関数
        int         PORT= 12345;
        char        buf[1024];
        
  4. WinMain() では DialogBox を表示するだけです。
  5. WM_INITDIALOG で Time Server Object Class を生成します。
    EditBox に PORT の規定値を表示します 。
        case WM_INITDIALOG:
            App= new TimeSvr();
            wsprintf(buf,"%d",PORT);
            SetDlgItemText(hDlg,IDC_PORT,buf);
            break;
        
  6. Start UP ボタンのクリックで Winsock の初期設定を行います。
    この操作は、最初に一度だけ実行します。
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {   case IDC_STARTUP:
                    KillTimer(hDlg, ID_TIMER);
                    App->Startup();
                    SetDlgItemText(hDlg,IDC_MSG,App->szMSG);
                    break;
        
  7. Listen ボタンがクリックされると、タイマ割り込みで Client からの接続の監視を始めます。
    App->StartSrv(PORT) の実行結果が進行メッセージに表示されます。
    Server では、何時 Client が接続してきても応答出来るように監視しなけれななりません。
                case IDC_LISTEN:
                    PORT= GetDlgItemInt(hDlg,IDC_PORT,NULL,FALSE);
                    rc= App->StartSrv(PORT);
                    SetDlgItemText(hDlg,IDC_MSG,App->szMSG);
                    if (rc==0)  SetTimer(hDlg, ID_TIMER, 300, NULL);
                    break;
        
  8. タイマ割り込みで Client からの接続を監視します。
    App->CheckSrv(buf,1024) で Client から送られてきた COMMAND(Message) を受信します。
    この値はパラメータとして TimeSvr::SendMsg(char *com) に渡されます。
    TimeSvr::SendMsg(char *com) では *com を解析して該当するメッセージを送信するのですが、 今回は何を言ってきても現在時刻を返すだけです。
            case WM_TIMER:
                KillTimer(hDlg, ID_TIMER);
                rc= App->CheckSrv(buf,1024);
                SetDlgItemText(hDlg,IDC_MSG,App->szMSG);
                SetTimer(hDlg, ID_TIMER, 300, NULL);
                break;
        
  9. WM_CLOSE では Wsock Object Class を開放して下さい。
            case WM_CLOSE:
                KillTimer(hDlg, ID_TIMER);
                SAFE_DELETE(App);
                EndDialog(hDlg, TRUE); 
                return TRUE;
        

【補足】

ノンブロッキングで接続確認の待機を行うソースコードです。
    u_long  val=1;

    // ソケットをバインド
    if (bind(sock0,(struct sockaddr *)&addr,sizeof(addr)) != 0)
    {   strcpy(szMSG,"ソケットのバインドに失敗しました");
        return -1;
    }

    // ノンブロッキングに設定しています。
    ioctlsocket(sock0, FIONBIO, &val);

    // TCP クライアントからの接続要求を待てる状態にする
    if (listen(sock0, 5) != 0)
    {   strcpy(szMSG,"接続要求待ちに失敗しました");
        return -1;
    }

超初心者のプログラム入門(Win32API C++)