テキストチャット

簡単な Chat プログラム

さっきは、一方向でデータ通信してみましたが、今度は、双方向に通信してみましょう。

作成するのは、簡単なチャットです。

  • ChatServer が、指定ポートで待っている
  • ChatClient が、ホスト名とポート番号を指定して接続を行う
  • 接続完了後は、クライアント、サーバが交互に一行ずつメッセージを交換する。
  • どちらかが exit という入力を入れたら終了

動作イメージは、こんな感じ

    server 側                             client 側
     [起動]
Server waitting on serverSocket..
                                                [起動]
                                  Connecting to localhost/....
Connection established           Connection established
                                  Client Input> clientMsg0 [入力]
Client msg: clientMsg0   [受信]
Server Input> serverMsg0 [入力]
                                  Server msg: serverMsg0 [受信]
                                  Client Input> client3  [入力]
Client msg: client3 [受信]
Server Input> exit  [入力]
normal termination                Server msg: exit  [受信]
                                  normal termination

まずは、動かしてみて、イメージを掴んでください。

動きを把握したところで、接続確立・交互に通信のあたりを見ていきましょう。

接続

connection を張るまでは、先ほどの例と差はありません。 違うのは、今回は双方向通信なので、Server, Client 両側で、Input/OutputStream を両方開いている点ですね。

サーバ側

ServerSocket serverSocket = new ServerSocket(portNum);		
System.out.println("Server waitting on serverSocket " + serverSocket);
Socket socket = serverSocket.accept(); /* 接続待ち */		
System.out.println("Connection established");	/* 接続完了 */
try {
	receiveAndSend(new Scanner(socket.getInputStream()),
			new PrintWriter(socket.getOutputStream()));
} finally {
	socket.close();
	serverSocket.close();
}
  • 接続開始〜確立
  • socket から Input/OutputStream を開いて、処理を receiveAndSend(in,out)に任せる
    • 今回は、テキスト形式で送受信するため、in, out の処理は以下のクラスを用いる
      • Scanner in: 一行単位読み込み(nextLine())用
      • java.io.PrintWriter out: 一行単位書き込み(println())用
    • receiveAndSend() の中身は後で紹介
  • 最後に、socket のclose()

クライアント側

/* IP address の取得 */
InetAddress host = InetAddress.getByName(hostName);
/* Socket の生成 */
System.out.println("Connecting to " + host + ":" + portNum);
Socket socket = new Socket(host, portNum);
System.out.println("Connection established");
try {
    sendAndReceive(new Scanner(socket.getInputStream()),
            new PrintWriter(socket.getOutputStream()));
} finally {
    socket.close();
}
  • 接続開始〜確立
  • socket から Input/OutputStream を開いて、処理を receiveAndSend(in,out)に任せる
    • in, out の形式は、サーバ側と同じ
    • sendAndReceive() の中身は後で紹介
  • 最後に socket を close()

交互に送受信

サーバ(受信・送信を交互に)

while (true) {
    String recvMsg = netIn.nextLine(); // ネット受信
    System.out.println("Client says: " + recvMsg); //表示
    if (recvMsg.equals("exit"))       // exit なら終了へ
        break;

    System.out.print("Server Input> "); 
    System.out.flush();
    String userMsg = userIn.nextLine(); // ユーザ入力
    netOut.println(userMsg);          // ネット送信
    netOut.flush();
    if (userMsg.equals("exit"))      // exit なら終了へ
        break;
}
System.out.println("normal termination");

主なI/O

  • netIn: Scanner, クライアントからの受信
  • netOut: PrintWriter, クライアントへの送信
  • userIn: Scanner, ユーザ入力の取り込み用

プログラム見てもらえば、交互に送受信しているだけですね。 注意点としては、送信時に flush() している点です。これは、送信したつもりでバッファに残っていたりすると、

  • 送信側:送ったつもり(実はバッファに溜まっている)
  • 受信側:受け取り待ち

とかなって、処理が進まなくなってしまうからです。(java.io.PrintWriter の自動フラッシュ機能をON にしている場合は、println()flush()もしてくれます。自動フラッシュを ON にするコンストラクタもあるので、そっちを使ってもらっても構いません。)

クライアント(受信・送信を交互に)

受信と送信の順序が逆になっている以外は、処理の流れはまったく同じです。

while (true) {
    System.out.print("Client Input> ");
    System.out.flush();
    String userMsg = userIn.nextLine(); // ユーザ入力
    netOut.println(userMsg);            // ネット送信
    netOut.flush();
    if (userMsg.equals("exit"))        // exit なら終了へ
        break;

    String recvMsg = netIn.nextLine(); // ネット受信
    System.out.println("Server says: " + recvMsg); //表示
    if (recvMsg.equals("exit"))        // exit なら終了へ
        break;
}