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