|
MFC非同期ソケット (クライアント編1)
前回はMFCのCSocketクラスを使って、同期ソケットを作りましたが、今回はCAsyncSocketクラスを使って非同期ソケットを作ってみます。
非同期ソケットのほうが、より高度なプログラムになります。初めてMFCのソケットを使うのであれば、まずMFCソケット通信の基本
(クライアント編)、MFCソケット通信の基本 (サーバ編)を見てください。
非同期ソケットでは、接続、送信、受信などのネットワークのイベントをコールバック関数で処理します。これらのイベントを処理するために、CAsyncSocketクラスを継承して、OnConnect()、OnSend()、OnReceive()などの関数をオーバーライドします。
今回もプロジェクトをダイアログベースで作成します。MFCアプリケーションウィザードで"Windowsソケット"をチェックします。また、今回も簡単のためUNICODEのサポートはしません。
ダイアログは次のようなものを作りました。"接続"ボタンが押されたら、指定したサーバ・ポートに接続します。"送信"ボタンが押されたら、メッセージを送信し、返信を受け取り、ログを表示します。

エディットボックスにはいつも通りDDX変数、ボタンにはBN_CLICKEDのイベントハンドラを追加しました。(これらの使い方については、ボタンの基本、エディットボックスの基本等を見てください。)
次はCAsyncSocketの派生クラスを作ります。クラスビューから追加→クラスを選択します。
基本クラスにCAsyncSocketを選択し、クラス名を入力し、完了を押します。

次は、必要なコールバック関数をオーバーライドします。作成したクラスのプロパティから、箱?のアイコンをクリックし、オーバーライドする関数を選択します。ここではOnConnect()、OnSend()、OnReceive()の3つを選択しました。

さて、同期ソケットの場合は、ダイアログクラスの関数内ですべて処理しましたが、非同期ソケットの場合は、ダイアログクラスとソケットクラスの両方で連携して処理しなければなりません。クラス間の関係や処理の振り分けなど、どのように作るべきか悩むところです。
管理人の場合は次のような構造にします。ソケットクラスはダイアログクラスのメンバに持ちます(包含)。ダイアログデータの読み書きは、やはりダイアログクラス内で行いたいので、ダイアログクラスにOnConnect()、OnSend()、OnReceive()関数を追加し、ソケットクラスのコールバックからは、この関数を呼び出してもらいます。
包含関係になっているため、ソケットクラスからはダイアログクラスにはアクセスできないので、Create()関数をオーバーライドして、ダイアログクラスのthisポインタを渡すようにします。データ処理やUIの処理をダイアログクラスに集中させた形になります。
必ずこのようにしないといけないということではありません。あくまで一例だと思ってください。
では、上のクラス間の関係の通りに関数を追加していきましょう。
まず、ダイアログクラスにコールバック関数を追加します。ダイアログクラスを右クリックし、追加→関数の追加を選択します。

OnConnect()、OnSend()、OnReceive()をそれぞれ追加します。これらの関数はクラス外から呼ばれるのでpublicにします。これらの関数はソケットクラスでオーバーライドしたOnConnect()、OnSend()、OnReceive()からそのまま呼び出します。なので、引数や戻り値もソケットクラスと同じにしておきます。戻り値はvoid、引数はint型のnErrorCodeをそれぞれ追加しておきます。

次は、ダイアログクラスのメンバにソケットクラスを追加します。ダイアログクラスを右クリックし、追加→変数の追加を選択します。

変数の種類に、CAsyncSocketクラスを継承して作成したソケットクラス名を入力し、変数名を入力し、完了を押します。

次はソケットクラスのCreate()をオーバーライドします。ここでは引数にダイアログクラスのポインタを渡すようにします。これはソケットクラスのOnConnect()、OnSend()、OnReceive()からダイアログクラスのOnConnect()、OnSend()、OnReceive()を呼び出す際に使用します。
ソケットクラスを右クリックし、追加→関数の追加を選択します。引数にはダイアログクラスのポインタを追加しておきます。アクセス指定はpublic、戻り値はもともとのCreate()関数と同様にBOOLにします。

さて、このままビルドすると少々困った問題が起こります。それは、
・ダイアログクラスのメンバにソケットクラスの変数を追加している
・ソケットクラスのCreate()関数に引数でダイアログクラスのポインタを追加している
という状態なので、ダイアログクラスとソケットクラスのどちらの定義を先に記述しても、ビルド時にどちらかが未定義となってしまうからです。通常、コンパイラはソースの先頭から順に解析していくので、このような問題が起こります。
このような場合は、回避策としてクラスの前方宣言を使います。次のようにダイアログクラスの定義の前にソケットクラスの前方宣言を追加します。
class CAsyncClientDlg;
class CClientASock : public CAsyncSocket
{
public:
CClientASock() : m_dlgP(NULL) {}
virtual ~CClientASock(){}
virtual void OnReceive(int nErrorCode);
virtual void OnSend(int nErrorCode);
virtual void OnConnect(int nErrorCode);
BOOL Create(CAsyncClientDlg *dlgP);
private:
CAsyncClientDlg *m_dlgP;
};
|
今回はここで一区切りにしましょう。次回は送受信の仕方を解説し、実際にコードの編集をします。
|