MFC編 目次

 MFC全般

 

・MFCの開発環境をそろえよう
・MFCをスタティックリンクしたときに出るエラー
・関数追加時に出るエラー
・Windows XPスタイルの外観にする

 文字列操作

 

・CStringの基本1 文字列の連結と追加
・ATL/MFC共有版のCStringについて
・CStringと三項演算子の問題

 DDX/DDV

 

・DDXの基本1
・DDXの基本2
・DDX変数に複数コントロールを割り当てる
・DDX変数を配列にする

 ダイアログ

 

・ダイアログの色変更

 ボタン

 

・ボタンの基本

 チェックボックス

 

・チェックボックスの基本
・プッシュボタンのようなチェックボックス
・チェックボックスの色変更

 エディットボックス

 

・エディットボックスの基本
・エディットボックスの色変更

 コンボボックス

 

・コンボボックスの基本
・コンボボックスに初期データを入れる
・コンボボックスの色変更
・拡張コンボボックス

 リストボックス

 

・リストボックスの基本
・リストボックスの色変更
・チェックリストボックスを作る

 ラジオボタン

 

・ラジオボタンの基本
・ラジオボタンの色変更

 スタティックテキスト

 

・スタティックテキストの内容を動的に変更する
・スタティックテキストに複数行入力する
・スタティックテキストの文字色変更

 リストコントロール

 

・リストコントロールの基本1
・リストコントロールの基本2
・リストコントロールの一行全体を選択する
・リストコントロールを単一行選択にする
・フォーカスが移ったときも選択状態を維持する
・アイテムにユーザデータを付加する
・アイテムにアイコンをつける
・アイテムに状態イメージをつける
・ヘッダ項目にアイコンをつける

 ツリーコントロール

 

・ツリーコントロールの基本

 タブコントロール

 

・タブコントロールの基本1
・タブコントロールの基本2
・タブコントロールをXPスタイルにする

 スライダコントロール

 

・スライダコントロールの基本1
・スライダコントロールの基本2

 スピンコントロール

 

・スピンコントロールの基本

 プログレスバー

 

・プログレスバーの基本

 日時指定コントロール

 

・日時指定コントロールの基本

 月間予定表コントロール

 

・月間予定表コントロールの基本
・月間予定表のプロパティと色変更

 IPアドレスコントロール

 

・IPアドレスコントロールの基本
・IPアドレスコントロールの操作

 ピクチャーコントロール

 

・ピクチャーコントロールの基本

 アニメーションコントロール

 

・アニメーションコントロールの基本

 時刻管理

 

・CTimeとCTimeSpan
・CTimeの引数について

 メニュー

 

・ダイアログにメニューをつける
・ダイアログにポップアップメニューをつける

 ステータスバー

 

・ダイアログにステータスバーをつける
・ステータスバーに文字列を表示する

 プロパティシート

 

・プロパティシートの基本1
・プロパティシートの基本2

 コモンダイアログ

 

・ファイル選択ダイアログ
・フォント選択ダイアログ
・色選択ダイアログ

 ファイル入出力

 

・ファイル入出力の基本
・テキストファイルの入出力
・ファイルの検索、列挙1
・ファイルの検索、列挙2

 ネットワーク

 

・MFCソケット通信の基本 (クライアント編)
・MFCソケット通信の基本 (サーバ編)
・MFC非同期ソケット (クライアント編1)
・MFC非同期ソケット (クライアント編2)
・MFC非同期ソケット(サーバ編1)
・MFC非同期ソケット(サーバ編2)

 デバイスコンテキスト

 

・デバイスコンテキストの基本
・文字列の描画
・ペンを使った描画
・ブラシを使った描画1
・ブラシを使った描画2

 FTPクライアント

 

・FTPクライアントを作る1
・FTPクライアントを作る2
・FTPクライアントを作る3
・FTPクライアントを作る4
・FTPクライアントを作る5

 ドキュメント・ビュー

 

・ドキュメント・ビューの基本
・エディットビューの基本
・リストビューの基本
・ツリービューの基本
・フォームビューの基本

 ダイアログバー

 

・ダイアログバーの基本
・ダイアログにダイアログバーをつける

 

 

トップページへ戻る

MFCソケット通信の基本 (サーバ編)

 前回はMFCを使ってクライアントソケットを作りました。今回はもう一方のサーバ側を作りましょう。MFCソケットの基本的な説明は前回のクライアント編ですでにしているので、今回はサーバに特化した部分に注目していきます。

 では、さっそくプロジェクトを作ります。ダイアログベースで作成し、"Windowsソケット"をチェックします。また、今回も簡単のためUNICODEのサポートはしません。

 ダイアログには、次のようにポート番号の入力用、ログ表示用のエディットボックス、"待ち受け"ボタンを追加しました。エディットボックスにはいつも通りDDX変数、ボタンにはBN_CLICKEDのイベントハンドラを追加しました。(これらの使い方については、ボタンの基本エディットボックスの基本等を見てください。)

 このプログラムは、"待ち受け"ボタンが押されると、指定のポートでクライアントからの接続を待ち続けます。クライアントから接続要求が来たら、接続を確立し、データを送受信し、切断します。

 では、"待ち受け"ボタンが押された時のイベントハンドラを実装します。

// "待ち受け"ボタン押下
void CServerSockDlg::OnBnClickedBtnListen()
{
    CSocket         sock, conSock;
    unsigned int    port = 0;
    CString         sendStr, recvStr;
    int             send, recv, sendSum, recvSum;
    LPCSTR          byteCP = NULL;
    LPSTR           byteP = NULL;
    int             err = 0;
    
    UpdateData();
    
    // (1)ポート取得
    if (!err) if (_stscanf_s(m_xvEditPort, _T("%d"), &port) != 1) err = 1;
    // (2)ソケット作成
    if (!err) if (!sock.Create(port)) err = 1;
    // (3)接続要求を待機
    if (!err) if (!sock.Listen(1)) err = 1;
    // (4)接続受け入れ
    if (!err) if (!sock.Accept(conSock)) err = 1;
    // (5)受信(20バイト固定)
    if (!err)
    {
        byteP = recvStr.GetBuffer(21);
        recvSum = 0;
        while (recvSum < 20)
        {
            recv = conSock.Receive(byteP +recvSum, 20 -recvSum);
            if (recv == SOCKET_ERROR || recv == 0) {err = 1; break;}
            recvSum += recv;
        }
        byteP[20] = '\0';
        recvStr.ReleaseBuffer();
    }
    if (!err)
    {
        m_xvEditLog += _T("Recv : ");
        m_xvEditLog += recvStr +_T("\r\n");
    }
    // (6)送信(20バイト固定)
    if (!err)
    {
        sendStr = _T("Welcome!");
        while (sendStr.GetLength() < 20) sendStr += _T(" ");
        
        sendSum = 0;
        while (sendSum < 20)
        {
            byteCP = static_cast<LPCSTR>(sendStr) +sendSum;
            send = conSock.Send(byteCP, 20 -sendSum);
            if (send == SOCKET_ERROR) {err = 1; break;}
            sendSum += send;
        }
    }
    if (!err)
    {
        m_xvEditLog += _T("Send : ");
        m_xvEditLog += sendStr +_T("\r\n");
    }
    // (7)エラー表示
    if (err)
    {
        LPVOID        lpMsgBuf = NULL;

        ::FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, sock.GetLastError(),
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR)&lpMsgBuf, 0, NULL);

        m_xvEditLog += _T("Error : ");
        m_xvEditLog += static_cast<LPTSTR>(lpMsgBuf);
        LocalFree(lpMsgBuf);
    }
    // (8)切断
    conSock.Close();
    sock.Close();
    UpdateData(FALSE);
    
    return;
}

(1)ポート取得

 サーバは特定のポートでクライアントからの接続を待ちます。ここではDDX変数からポート番号を取得しています。

(2)ソケット作成

 まず、待ち受け用のソケットを作成します。CSocket::Create()関数の第一引数にポート番号を指定します。ここでポート番号を指定しておくと、CAsyncSocket::Bind()を呼ぶ必要はなくなります。

(3)接続要求を待機

 CAsyncSocket::Listen()関数を呼ぶことによって、ソケットが接続を受け入れるモードになります。この時点ではまだクライアントとの接続はされません。また、Listen()の引数は、接続待ちにできるクライアント数を指定します。今回は最も単純な1対1の接続しかしませんので、1に指定しています。

BOOL CAsyncSocket::Listen(int nConnectionBacklog = 5);
説明: 接続要求の待機
引数: nConnectionBacklog:接続待ちのキューを拡張できる最大長。1〜5
戻り値: 正常な場合は0以外、エラーの場合0

(4)接続受け入れ

 CAsyncSocket::Accpet()関数を呼ぶと、接続待ちのクライアントと接続されます。CSocketクラスの場合は、接続待ちのクライアントがない場合、Accept()関数はブロッキングされます。つまり、クライアントからの接続要求が来るまで、関数が制御を返しません。

 接続待ち用のソケットと、クライアントとのコネクションで使うソケットは別です。クライアントとの接続では、コネクションごとに1つのソケットが使われます。Accept()関数の第一引数には、クライアントとの接続で使うソケットの参照を渡します。Accept()が成功すると、このソケットを使ってクライアントと通信できるようになります。

virtual BOOL CAsyncSocket::Accept(CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL);
説明: ソケットの接続受け入れ
引数: rConnectedSocket:新しい接続に使用するCAsyncSocketクラスオブジェクトの参照
戻り値: 正常な場合は、送信したバイト数。すでに接続が閉じている場合は0。エラーの場合SOCKET_ERROR

(5)受信(20バイト固定)
(6)送信(20バイト固定)

 接続が確立されたら、あとは自由にデータの送受信ができます。送受信の方法はクライアントと変わりません。

(7)エラー表示

 ソケットで何らかのエラーが発生した場合は、CAsyncSocket::GetLastError()で最後に発生したエラーのエラーコードが取得できます。ここもクライアントと変わりません。

(8)切断

 通信が終了したらCAsyncSocket::Close()でクライアントとのコネクションを切断します。待ち受け用のソケットを終了するときも、Close()関数を実行します。

 では、ビルドして、実行してみます。前回作ったクライアントも起動して、通信してみましょう。

 まず、クライアント、サーバとも同じポートを指定し、サーバ側で"待ち受け"ボタンを押します。これでサーバが接続待ち状態になります。次にクライアント側はサーバIPを指定します。同一コンピュータの場合は、マシンのIPを指定するか、"localhost"とします。

 クライアントでメッセージを入力し、"送信"を押します。すると、サーバに接続され、メッセージの送受信が行われます。

 

 いろいろ操作してみると、いくつか気になるところが出てくると思います。CSocketクラスはブロッキングされるので、待ち状態になっているときは、ユーザから見るとプログラムがハングアップしているような状態になります。

 今回のように、GUI部分と通信部分を同一スレッド上で行っていると、このような状態になります。これを改善するには、通信部分とGUI部分でスレッドを分けるようにしないといけません。また、CAsyncSocketクラスを使って、イベントドリブン型のプログラム構造にする方法もあります。

 とは言っても、これがMFCソケット通信の基本になります。あとは作りたいものに合わせてアレンジしていけばいいわけです。

 また、今回は1対1の接続でしたが、通常、ソケットのサーバ側は、複数のクライアント接続を同時に受け入れるため、コネクションごとにスレッドを起動してマルチスレッドで動作させます。