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ソケット通信の基本 (クライアント編)

 今回はソケット通信を扱います。ソケット通信はコンピュータ(の中のプロセス)間でデータを送受信するための基本となる仕組みです。異なるOS間であっても、同一コンピュータ内のプロセス間でも通信ができます。ソケットについての細かい説明は省略しますが、ここでは、MFCのソケットクラスを使って、最も簡単にデータ通信をするプログラムを作成してみます。

 実装を始める前に、まず必要最低限の知識を説明しておきましょう。ソケットにはサーバとクライアントがあります。通常は1つのサーバプロセスに対して、複数のクライアントがコネクションを張るような形になります。

 一度コネクションが確立されると、あとはサーバ・クライアント間で自由にデータのやり取りができます。ソケットが提供するのはデータ送受信の仕組みだけで、データの中身については何も決まりはありません。

 これは、データ送受信の順番や、データ長、文字コードなど、通信に関する様々な決まりは、あらかじめサーバ・クライアント間で決めておかなければいけないことを意味します。

 データの大きさについても決まりはなく、何GBのデータでも送れます。ですが、当然のことながら一度にすべて送信できるわけではなく、受信側も一度にすべて受信できるわけではありません。そのときのネットワークの負荷や、マシンの負荷の都合で少しずつ断続的に送られてくることもあります。

 MFCでは、このようなパケットを送受信する場合、2つの実装方法があります。一つは、次のデータが送られてくるまでずっと待ち続ける方法。もう一つは分割されたデータが送られてくるたびにコールバック関数が呼び出され、受信する方法です。

 前者は"ブロッキング"と呼ばれます。この方法は、Receive()関数を呼ぶと、データが到着するまで関数が制御を返しません。後者は"非ブロッキング"と呼ばれます。この方法ではReceive()関数は必ずすぐに制御を返します。データが届いていなければエラーを返します。

 このため、MFCのソケットクラスは2種類あり、非ブロッキングのほうはCAsyncSocket、ブロッキングのほうはCSocketというクラスを使います。とは言っても、CSocketはCAsyncSocketの派生クラスです。大きな違いは、ブロックするかしないかということだけです。Receive()やSend()でデータを送受信することに違いはありません。

 この2つで言うと、CSocketの方が高度にソケットを抽象化しているので、プログラムを単純化できます。プログラム構造的に言うと、CAsyncSocketはイベントドリブン型、CSocketは逐次処理型とも言えます。初めてMFCのソケットを使うのであれば、まずはCSocketを使って通信してみるのが一番手っ取り早いです。

 ということで、ここではCSocketクラスを使った最も単純な通信プログラムを作ります。

 では、具体的な作業に入りましょう。いつも通りダイアログベースでプログラムを作りますが、ソケットを使うときは、MFCアプリケーションウィザードの"高度な機能"で"Windowsソケット"をチェックします。こうすると、必要なヘッダのインクルードと、ソケットの初期化処理が追加されます。

 また、ここでは説明を簡単にするため、UNICODEには対応しません。プロジェクト設定が"UNICODEを使用する"になっていたら、"マルチバイト文字セットを使用する"か"設定なし"に変更してください。

 サーバのIPとポート、送信メッセージと通信ログ用のエディットボックス、送信ボタンを追加しました。エディットボックスにはいつも通りDDX変数、ボタンにはBN_CLICKEDのイベントハンドラを追加しました。(これらの使い方については、ボタンの基本エディットボックスの基本等を見てください。)

 このプログラムがやることは、"送信"ボタンが押されたら、指定したサーバ・ポートに接続してメッセージを送信し、返信を受け取り、ログを表示するだけです。

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

// "送信"ボタン押下
void CClientSockDlg::OnBnClickedBtnSend()
{
    CSocket         sock;
    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 (!sock.Create()) err = 1;
    // (2)ポート取得
    if (!err) if (_stscanf_s(m_xvEditPort, _T("%d"), &port) != 1) err = 1;
    // (3)接続
    if (!err) if (!sock.Connect(m_xvEditIP, port)) err = 1;
    // (4)送信(20バイト固定)
    if (!err)
    {
        sendStr = m_xvEditMes;
        while (sendStr.GetLength() < 20) sendStr += _T(" ");
        sendStr = sendStr.Left(20);
        
        sendSum = 0;
        while (sendSum < 20)
        {
            byteCP = static_cast<LPCSTR>(sendStr) +sendSum;
            send = sock.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");
    }
    // (5)受信(20バイト固定)
    if (!err)
    {
        byteP = recvStr.GetBuffer(21);
        recvSum = 0;
        while (recvSum < 20)
        {
            recv = sock.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)エラー表示
    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);
        UpdateData(FALSE);
        LocalFree(lpMsgBuf);
    }
    // (7)切断
    sock.Close();
    UpdateData(FALSE);
    
    return;
}

 ちょっと長いですが、この関数が処理のすべてです。では、順番に見ていきましょう。

(1)ソケット作成

 CSocketクラスのインスタンスを作り、CSocket::Create()関数を使い、ソケットを作ります。本当はいくつか引数がありますが、デフォルト値で特に問題はありません。

(2)ポート取得

 ソケットとは特に関係ありませんが、CString型のDDX変数からポート番号を取得しています。CStringクラスには文字列から数値に変換する関数はありませんので、ランタイム関数を使っています。

 ちなみに、ここで使っている_stscanf_s()という関数は、Visual Studio 2005で追加されたセキュリティ強化版の関数です。

(3)接続

 CAsyncSocket::Connect()で接続先のサーバ名(またはIPアドレス)とポート番号を指定して、サーバに接続します。

(4)送信(20バイト固定)

 接続が成功したら、サーバにデータを送信します。ここで送信しているということは、サーバ側はこの時点で受信していないといけないということになります。このように、送受信の順番は、あらかじめクライアント・サーバ間の"通信仕様"として決めておかないといけません。

 さらに、ここでは文字列を20バイトの固定長データにして送信しています。これも通信仕様として決めておかないといけません。もしデータ長が決められていなければ、受信側は何バイト受信すればいいのかわかりません。データが途切れたら、そこがデータの終わりなのか、それとも回線が混んでいるから途切れているだけなのか、判断ができなくなります。

 送信は、CAsyncSocket::Send()関数を使います。この関数はデータを送信しますが、重要なことは、「一度に全部送信できるとは限らない」ということです。巨大なデータの場合は、大抵何回かに分けて送られます。そのため、Send()関数は、実際に送信したバイト数の合計が、送信したいバイト数に達するまで、繰り返して呼ばないといけません。

virtual int CAsyncSocket::Send(const void* lpBuf, int nBufLen, int nFlags = 0);
説明: ソケットにデータを送信する
引数: lpBuf:送信するデータ
nBufLen:送信するデータのバイト数
nFlags:呼び出し方法
戻り値: 正常な場合は、送信したバイト数。エラーの場合SOCKET_ERROR

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

 送信が終わったら、サーバからの返答を受信します。受信に関しても送信と同じで、目的のバイト数を受信し終わるまで、CAsyncSocket::Receive()関数を繰り返して呼びます。

virtual int CAsyncSocket::Receive(void* lpBuf, int nBufLen, int nFlags = 0);
説明: ソケットからデータを受信する
引数: lpBuf:受信するデータバッファ
nBufLen:受信するデータのバイト数
nFlags:呼び出し方法
戻り値: 正常な場合は、送信したバイト数。すでに接続が閉じている場合は0。エラーの場合SOCKET_ERROR

 今回は簡単のためにデータ長を固定長としましたが、可変長データを送るときは、まずデータ長自体をデータとして送って、そのあとに実際のデータを送る必要があります。こうすると、受信する方は最初にデータ長を受け取るので、そのあと何バイト受信すればいいのかがわかるようになります。

(6)エラー表示

 ソケットに関して、何らかのエラーが発生したときは、CAsyncSocket::GetLastError()を呼び出すと、最後に発生したエラーのエラーコードが取得できます。さらにAPIのFormatMessage()関数を使うと、エラーコードに対応するメッセージを取得できます。

(7)切断

 CAsyncSocket::Close()関数を呼び出すとソケットが切断されます。このClose()関数は、CAsyncSocketクラスのデストラクタでも呼び出されるので、今回のようにCSocketオブジェクトをローカル変数にとった場合は、明示的に呼び出さなくても、関数から抜けた時点で自動的に呼び出されます。

 また、より安全に切断する場合は、CAsyncSocket::ShutDown()を実行してから、Close()を実行します。ShutDown()関数は、ソケットに対してそれ以降の送信、受信、または両方を禁止します。今回は、一回ずつデータをやり取りしたらそれで終わりなので、特にShotDown()は使用していません。

 では、ビルドして実行してみます。当然のことながら、クライアントだけでは何もできません。特定のコンピュータの特定のポートにデータを送信しようとするとどうなるか、試してみましょう。

 次回はCSocketクラスを使ってサーバソケットを作成します。