D-1 数あてゲーム

出題者が頭に思い浮かべた数字を回答者があてる、数あてゲームという遊びがあります。そのやり取りは次のようになります。

出題者 「0から99までの数字から1つ思い浮かべました。さあどうぞ。」
解答者 「では、36。」
出題者 「36は大きすぎます。」
解答者 「それなら、21。」
出題者 「21は小さすぎます。」
解答者 「28はどう?」
出題者 「28は大きすぎます。」
解答者 「26は?」
出題者 「26で正解です。」

ここでは、2台のコンピュータを使って、この数あてゲームができるプログラムを作りましょう。つまり、ネットワークを使ってコンピュータ間で通信させるネットワークプログラミング (の初歩) を学習します。

プログラムは、クライアント (解答者) と サーバ (出題者) の2つになります。この章では、クライアントプログラムとサーバプログラムを同時に作っていくため、可能であれば2台のコンピュータに プログラミングすると楽です。ただし、ネットワークに関しては思わぬトラブルが起きることがあるので、具体的には教員あるいは指導員の指示に従ってください。

クライアントの動作 サーバの動作
  「待機」ボタンで待機状態になり、この時に正解も設定する
「接続」ボタンで接続する  
「送信」ボタンで答 (例えば「36」) を送信する  
  答 (ここでは「36」) を受信し、当たり外れの判定をし、判定結果 (ここでは「36は大きすぎます。」) を送り返す
送り返された判定結果を表示する  
「送信」ボタンで答 (例えば「21」) を送信する  
  答 (ここでは「21」) を受信し、当たり外れの判定をし、判定結果 (ここでは「21は小さすぎます。」) を送り返す
送り返された判定結果を表示する  
〔以下同様〕 〔以下同様〕

【練習問題】

クライアントからサーバへ接続する

最初はクライアントプログラムから作ります。クライアントプログラムは、

「接続先」を指定して「接続」ボタンを押すと
無事に接続できたなら「接続に成功しました」と表示し、
送信内容 (ここでは「36」) を入力して「送信」ボタンを押すとその内容がサーバに送信され
サーバから返ってきたメッセージが下のメモに表示される、というプログラムです。
なお、サーバから返ってきたメッセージは全て残るようにします。

まずは、フォームに次のようにコンポーネントを配置して下さい。なお、は「クライアントソケット」と呼ばれるコンポーネントであり、クライアントとして (つまりサービスを利用する側として) ネットワークに接続する処理を引き受けてくれます。コンポーネントパレットの中の Internet ページにあります。

コンポーネント Name プロパティ
「接続先」エディット EditServerName
「ポート番号」スピンエディット SpinEditPort
「接続」ボタン ButtonConnect
「切断」ボタン ButtonDisconnect
クライアントソケット ClientSocket1
「送信内容」エディット EditSend
「送信」ボタン ButtonSend
下のメモ MemoLog

最初は、「接続」ボタンのイベントハンドラを書きます。クライアントソケットを利用して他のコンピュータに接続するには、

  1. 接続先のコンピュータ名を Host プロパティに設定する
  2. 接続先のポート番号 (と呼ばれるもの) を Port プロパティに設定する
  3. Open メソッドを呼び出す

という手順が必要です。プログラムは次のようになります。

// 「接続」ボタン
procedure TForm1.ButtonConnectClick(Sender: TObject);
begin
  ClientSocket1.Host := EditServerName.Text; // 接続先コンピュータ名を設定
  ClientSocket1.Port := SpinEditPort.Value; // 接続先ポートを設定
  ClientSocket1.Open; // 接続
end;

「接続」ボタンのイベントハンドラを書いたなら、「切断」ボタンのイベントハンドラも必要です(さもないと、接続しっぱなしで切断することが出来ないプログラムになってしまいます)。 次のように書いて下さい。

// 「切断」ボタン
procedure TForm1.ButtonDisconnectClick(Sender: TObject);
begin
  ClientSocket1.Close; // 切断
end;

クライアントプログラムがここまで出来たら、ユニットファイルおよびプロジェクトファイルを、それぞれ「kazu_client_uni」「kazu_client」という名前で保存し、今度は「ファイル」→「アプリケーションの新規作成」でサーバプログラムを作りましょう。

フォームに次のようにコンポーネントを配置して下さい。なお、は「サーバソケット」と呼ばれるコンポーネントであり、サーバとして (つまりサービスを提供する側として) ネットワークに接続する処理を引き受けてくれます。コンポーネントパレットの中の Internet ページにあります。

コンポーネント Name プロパティ
「ポート番号」スピンエディット SpinEditPort
「待機」ボタン ButtonOnLine
「待機終了」ボタン ButtonOffLine
「正解」スピンエディット SpinEditTrueAnswer
サーバソケット ServerSocket1
「受信文字列」エディット EditReceived

最初は「待機」ボタンのイベントハンドラを書きます。ここでは、正解を設定して待機状態になるだけではなく、「受信文字列」エディットに「(待機状態です)」と表示するようにしましょう。

// 「待機」ボタン
procedure TForm1.ButtonOnLineClick(Sender: TObject);
begin
  Randomize; // 乱数の初期化
  SpinEditTrueAnswer.Value := Random(100); // 正解を設定する
  ServerSocket1.Port := SpinEditPort.Value; // ポート番号を設定する
  ServerSocket1.Open; // 待機状態にする
  EditReceived.Text := '(待機状態です)';
end;

次は「待機終了」ボタンのイベントハンドラです。

// 「待機終了」ボタン
procedure TForm1.ButtonOffLineClick(Sender: TObject);
begin
  ServerSocket1.Close; // 待機状態を終了する
  EditReceived.Text := '(待機状態を終了しました)';
end;

クライアントから接続を受け付けると、OnClientConnect というイベントが発生します。この OnClientConnect イベントのイベントハンドラを次のように書きましょう。

// 接続を受けたら
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  EditReceived.Text := '(接続を受け付けました)';
end;

逆に、クライアントから切断されると OnClientDisconnect というイベントが発生します。このイベントハンドラは次のようにして下さい。

// 切断されたら
procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  EditReceived.Text := '(切断されました)';
end;

ここまでで、クライアント・サーバ間の接続・切断が行えるようになりました。次の手順で、このプログラムが正しく動作するか確認して下さい。

  1. サーバプログラムを起動して下さい。
  2. 「待機」ボタンを押して下さい。「(待機状態です)」と表示されましたか?
  3. 別のコンピュータでクライアントプログラムを起動して下さい。
  4. 接続先のコンピュータ名、つまりサーバとなるコンピュータ名を[接続先]欄に指定してください。コンピュータ名については、教員あるいは指導員の指示に従ってください。
  5. 「接続」ボタンを押して下さい。サーバ側で「(接続を受け付けました)」と表示されましたか?
  6. クライアントの「切断」ボタンを押して下さい。サーバ側で「(切断されました)」と表示されましたか?
  7. サーバの「待機終了」ボタンを押して下さい。「(待機状態を終了しました)」と表示されましたか?

クライアントからサーバへメッセージを送る

クライアントの「送信」ボタンのイベントハンドラを次のようにして下さい。

// 「送信」ボタン
procedure TForm1.ButtonSendClick(Sender: TObject);
begin
  ClientSocket1.Socket.SendText(EditSend.Text); // メッセージを送る
  EditSend.Text := ''; // メッセージ欄を空にする
end;

クライアントからメッセージが送られてくると、サーバソケットの OnClientRead というイベントが発生します。この時の受信したメッセージは次のように取り出すことが出来ます。

// メッセージを受信したら
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  // 受信したメッセージを表示する
  EditReceived.Text := ServerSocket1.Socket.Connections[0].ReceiveText;
end;

これで、クライアントから送られてきたメッセージをサーバが表示できるようになりました。実行して確認して下さい。

サーバからクライアントへメッセージを返す

サーバからクライアントへメッセージを返すには、SendText というメソッドを次のように使います (下線部が変更部分です)。ここでは、「○○ (受信した文字列) を受信しました」というメッセージを返しています。

// メッセージを受信したら
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  // 受信したメッセージを表示する
  EditReceived.Text := ServerSocket1.Socket.Connections[0].ReceiveText;
  // メッセージを返す
  Socket.SendText(EditReceived.Text + 'を受信しました');
end;

クライアント側では、サーバからメッセージを受信するとクライアントソケットの OnRead というイベントが発生します。OnRead イベントのイベントハンドラを次のように書いて下さい。

// メッセージが返ってきたら
procedure TForm1.ClientSocket1Read(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  // 受信したメッセージを下のメモに追加
  MemoLog.Lines.Add(Socket.ReceiveText);
end;

これで、サーバから返ってきたメッセージも表示できるようになりました。「○○を受信しました」というメッセージがちゃんと返ってくるかどうか、確認して下さい。

【基礎課題 D-1】

このサーバプログラムは、数あての判定を行っていません。クライアントから送られてきた数値を判定してその判定結果をクライアントに返すよう、サーバプログラムを変更して下さい。

ここまでのプログラムをダウンロード(D)