実用的なMFC用、Winsock通信ゲームコードサンプル
MFCを使ったソケットでは、接続待ちや、データ待ちでプログラムがハング状態になってしまいます。そこで、その部分をスレッドとして動かす必要が生じます。以下のコードは、まず接続待ちスレッドを起動し、接続したら接続待ちスレッドはデータ待ちスレッドを起動して終了します。あとは、データ待ちスレッドがデータの交信をするので、そのデータを使用します。
また、そのとき、データを受信したのをどうやってプログラムが知るか、という問題ですが、簡単なのはスレッドがデータを受信したときにフラグをたてて、タイマーでそれを監視する方法です。次にPostMessage
を使う方法です。
以下のコードは、スレッドまでのサンプルです。
起動後、手動でサーバ用のスレッドを開始します。もうひとつ立ち上げて同じくサーバ用スレッドを立ち上げます。
あとは、どちらからでもデータを送信すれば相手が受信します。これは同一マシン上で動作できるようにポートをそれぞれ別に指定できるようになっていますが、実際に使う場合はポート番号は同じでよいでしょう。
//----- データ待ちスレッド
void SockTread(void *sock)
{
int result;
// ソケットを渡される
SOCKET clSock=(SOCKET)sock;
while(1)
{
//----- 受信する
result = recv(
clSock,
received,
128,
0);
//----- 0を受信したらスレッドを終了する
if(result==0) break;
//----- 受信エラーならスレッドを終了する
else if(result == SOCKET_ERROR) break;
else
//----- うまく受信できた場合は返信する
{
//あまり意味は無いが、文字列をコピーして送信のサンプル
char bufx[128];
char *p="サンプルストリング2000−11−4";
memcpy(bufx,p,37);
result = send(
clSock,
bufx,
128,
0);
//----- 送信に失敗したらスレッドを終了
if (result==sizeof(double)) break;
f_recv=1; // Timer に受信したことを知らせる
}
}
//----- データ転送を禁止する
result = shutdown(clSock, SD_BOTH);
//----- データ転送を禁止できたら
if (result == 0)
//----- ソケットをクローズして終わり
result = closesocket(clSock);
}
// 接続待ちスレッド
void WaitCon(void *ch)
{
while(1)
{
int adlen = sizeof(clAddress);
clSocket=accept // 待機状態にする
(svSocket,
(struct sockaddr *) &clAddress,
&adlen);
if (clSocket == INVALID_SOCKET)
{
//待機状態になれなかったので終了
break;
}
//----- 接続用のスレッドを作成する
DWORD thread;
HANDLE threadHandle =
CreateThread(0,0,
(LPTHREAD_START_ROUTINE) SockTread,
(void*)clSocket, 0, &thread);
if (threadHandle == NULL)
{
//スレッドが作成できなかったので終了
closesocket(clSocket);
}
}
}
//----- サーバに接続する サーバが立ち上がっていなければエラーで終了する
void CGamecl2View::OnButton1()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
WSADATA Data;
if(Conon==1) return;
//----- クライアント DLLをロードする
int result = WSAStartup (MAKEWORD(1,1),&Data);
if(result !=0)
{
m_List1.AddString("Client : Winsock DLL load error!");
return;
}
//----- DLLのロードに成功した
//----- ソケットを制作する
svAddress.sin_family = AF_INET; // 定型いつも同じ
// 接続先のポートを設定
char p[129];
m_Edit3.GetWindowText(p, 128);
int portnum=atoi(p);
if(portnum==0) portnum=9005;
svAddress.sin_port = htons(portnum); // ポートをセットする
// 接続先のアドレスを設定
m_Edit2.GetWindowText(p, 128);
u_long svaddr = inet_addr(p); // アドレスをセットする
memcpy(&svAddress.sin_addr, &svaddr, sizeof(u_long));
svSocket = socket(AF_INET, SOCK_STREAM, 0);
if (svSocket == INVALID_SOCKET)
{
m_List1.AddString("Client Socket error!!");
WSACleanup();
exit(-1);
}
//----- ソケットの制作に成功した
//----- 接続する
result = connect(svSocket,
(const struct sockaddr *) & svAddress,
sizeof(svAddress));
if(result == SOCKET_ERROR)
{
m_List1.AddString("Client connect error");
closesocket(svSocket);
result = WSACleanup();
return;
}
//----- 接続できた
m_List1.AddString("Connect ok");
Conon=1;
return;
}
char buf[128];
char res[128];
void CGamecl2View::OnButton2()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
int x=0;
strcpy(buf, "これを送りました");
int result = send(svSocket,
(char*)&buf, sizeof(buf),0);
if (result != sizeof(buf))
{
m_List1.AddString("Client Send error");
return;
} else {
result = recv(svSocket, (char*)&res,
sizeof(res),0);
if (result ==0)
{
// 送信が終わった場合
m_List1.AddString("Client clsock recv");
} else {
// 受信したバイト数の場合
m_List1.AddString(res); // 返信を表示
result = send(svSocket,
(char*)&buf, 0,0); // 通信を終了する
}
}
}
void CGamecl2View::OnButton3()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
WSADATA Data;
int result = WSAStartup (MAKEWORD(1,1), &Data);
m_List1.AddString(" int result = WSAStartup (MAKEWORD(1,1), &Data);");
if (WSAStartup(MAKEWORD(1,1), &Data)!=0)
{
m_List1.AddString("Server : Winsock DLL ロードに失敗しました");
return;
}
//----- WinsockDLLの初期化に成功した -----------------------
m_List1.AddString("Server : Winsock DLL ロードしました");
//----- ソケットを作る
memset(&svAddress, // svAddress を初期化する
0 , // 設定する文字
sizeof(SOCKADDR_IN)); // 文字数
svAddress.sin_family = AF_INET; // 定型いつも同じ
// 自分のポートを設定する
char p[129];
m_Edit1.GetWindowText(p, 128);
int portnum=atoi(p);
if(portnum==0) portnum=9005; // ポートの指定が無ければ9005に
svAddress.sin_port = htons(portnum);// ポートを指定する
// svAddress.sin_port = htons(9005);// ポートを指定する
svAddress.sin_addr.s_addr=htonl(INADDR_ANY);
svSocket = socket(AF_INET, // 定型いつも同じ
SOCK_STREAM, // tcp/ipの場合はこれ
0); // 最後は0にする
if (svSocket == INVALID_SOCKET)
{
m_List1.AddString("Server Socket make error");
return;
}
//----- ソケットを作ることができた
m_List1.AddString("Server ソケットを作りました");
//----- ソケットのアドレスを指定する
result = bind( svSocket,
(const struct sockaddr *) &svAddress,
sizeof(svAddress));
if (result == SOCKET_ERROR)
{
m_List1.AddString("Server Socket アドレス指定が失敗しました");
return;
}
//----- ソケットのアドレスを指定することができた
m_List1.AddString("Server Socket アドレス指定が成功しました");
//----- ソケットを接続待ちにする
result = listen(svSocket, 1);
if (result == SOCKET_ERROR)
{
m_List1.AddString("listenが失敗しました");
closesocket(svSocket);
result = WSACleanup();
return;
}
//----- ソケットのアドレスを指定することができた
m_List1.AddString("listenが成功しました");
//----- 接続待ち
//データ待ち用スレッドを起動して帰る
_beginthread( WaitCon, // 動かすスレッドの本体
0, // スレッドのスタック。システムに任せる場合は0
this); // スレッドに渡すパラメータ。ここでは親を識別するためのポインタ
return;
}
void CGamecl2View::OnButton4()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
//-----WinsockDLLの後処理-----------------------------------
if (clSocket == INVALID_SOCKET)
closesocket(clSocket);
if (svSocket == INVALID_SOCKET)
closesocket(svSocket);
int result = WSACleanup();
}
void CGamecl2View::OnTimer(UINT nIDEvent)
{
// TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
// ここにスレッドを監視するコードを書く。以下はその例
switch(Gmode){
case 0: // 相手からデータを待っている場合の処理
if(f_recv==1){
// 相手からデータが送られてきたので処理をする
m_List1.AddString(received);
// 状態を変更する
f_recv = 0; // 受け取った状態のクリア
// Gmode = 1; // こちらのターンに変更
}
break;
}
CFormView::OnTimer(nIDEvent);
}
void CGamecl2View::OnButton5()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
Gmode=0; // データ待ちフラグをセット
SetTimer(1,100,NULL); // データ受信監視用のタイマを起動する
}