実用的な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); // データ受信監視用のタイマを起動する
 }