チャットシステム

  1. 概要
    1. 目的
      チャットは文字を用いた「おしゃべり」で、特定の参加者から送信された文字列は、サーバーを経由してすべての参加者に配られます。

    2. ネットワークの基礎
      1. IP
        ネットワークのインタフェースカードはIPとよぶ4バイトの数字で指定されます。各バイトは0..255の数値で表現されますから、たとえば、192.168.0.1のように指定できます。
         127.0.0.1は自分自身を示す特殊なIPです。自分1台でネットワークの試験をするとき利用すると便利です。サーバーとクライアントを同じマシンで立ち上げた場合、サーバーのアドレスとして 127.0.0.1 を指定することができます。
         他のマシンをサーバーとして利用する場合、サーバーのマシンでコマンド
         >ipconfig /all
        とすると、IPを知ることができます。LANにファイヤーウオールが設置されていると、許可したポート以外のパケットは破棄され接続できません。注意してください。

      2. ポート番号とは
        IPだけではネットワークのアプリケーションを区別できません。ネットワークのアプリケーションはポート番号で識別されます。たとえば、80番はhttpプロトコル(web)ですし、smtp(メールの送信)は25番を利用します。現在50000までのポートは予約の対象になっているので、テスト用に自由に利用できるのは、これ以後のポート番号になります。
         ポート番号にも 宛先と送り主ポート番号があります。クライアントからの宛先ポート番号が、アプリケーション(この場合チャット)のポート番号になります。

      3. ポート番号の取得
         サーバーに接続するにはサーバーのIPとポート番号を知る必要があります。
         クライアントの「宛先ポート番号」はサーバーのポート番号とします。「送り主ポート番号」は、OSから未利用のポート番号を受け取りポート番号として利用します。同じPCで複数のクライアントを動作させると、異なるポート番号を利用することになります。
         サーバーはクライアントからのメッセージにある「送り主ポート番号」を受け取り、それを「宛先ポート番号」として返信します。「送り主ポート番号」はアプリケーション(この場合チャット)のポート番号になります。したがって、あらかじめ、クライアント側の「送り主ポート番号」を知る必要はありません。

      4. サーバー&クライアントシステム
        チャットシステムはサーバーとクライアントから構成されます。チャットを行うには、まず、サーバーを立ち上げておきます。
         参加者はchatクライアントを用いサーバーに接続します。サーバーから名前の問い合わせがありますから、自分の名前を入力します。これで、クライアントがサーバーに登録されます。以後、メッセージを書き込むと、メッセージはサーバーに送られます。サーバーはクライアントからメッセージを受け取ると、すべての登録済みにクライアントに名前をつけてメッセージを送ります。
        クライアントから、”quit”メッセージを送ると、サーバーはそのクライアントを送信者リストから削除します。

  2. サーバー&クライアントシステム

    1. サーバー&クライアントシステム
      チャットシステムはサーバーとクライアントから構成されます。チャットを行うには、まず、サーバーを立ち上げておきます。
       参加者はchatクライアントを用いサーバーに接続します。サーバーから名前の問い合わせがありますから、自分の名前を入力します。これで、クライアントがサーバーに登録されます。以後、メッセージを書き込むと、メッセージはサーバーに送られます。サーバーはクライアントからメッセージを受け取ると、すべての登録済みにクライアントに名前をつけてメッセージを送ります。
      クライアントから、”quit”メッセージを送ると、サーバーはそのクライアントを送信者リストから削除します。

    2. プログラム手法
      1. アプレットからの実行
         アプレットでは、セキュリティの配慮のため、起動したwebサーバーしか接続できません。したがって、このプログラムは main を有するアプリケーションで作成する必要があります。

      2. ソケット
        ネットワークの送受信はソケットを通して行います。ソケットからの送受信は、ファイルと同じメソッドを利用します。クライアントはサーバー(宛先)のIPとポートを指定してソケットを生成します。送り主のIPとポートはOSから自動取得します。
         サーバーはまず、ポートを指定して、クライアントからの接続を待つサーバーソケットを生成します。サーバーソケットに接続があると、以後クライアントと送受信を行う専用のソケットを作成し、それを、クライアントとの処理を行うスレッドに引き渡します。サーバーソケットは最初の接続だけに利用します。

      3. 受信とスレッド
        受信要求を出してもメッセージが到着していない場合、受信要求をしたスレッドはメッセージが到着するまで休止状態に入ります。したがって、受信と並行して送信やボタン処理をしたい場合、受信を別のスレッドで行う必要があります。
      4. サーバー処理
        ここでは、サーバーはポート番号6000でクライアントの接続を待ち受けます。クライアントからの接続があると、各クライアントへのサービスを行うスレッドと送受信を行うソケットを生成し、ソケットを接続者リスト(connections)に追加します。
         スレッドは、クライアントに名前を問い合わせ、返信された名前をnameに記録し、新規接続者の名前と合計人数を知らせるメッセージを送ります。以後、メッセージを受信すると、名前を付加して接続者リストに含まれる参加者全員にメッセージを転送します。 "quit"メッセージを受信すると、ソケットを接続者リストから削除し、終了メッセージを送ります。

      5. クライアント処理
        クライアントは、サーバーのIPとポート番号6000を指定して、サーバーに接続します。サーバーが動作していないと、エラーで終了します。クライアントはサーバーからのメッセージを受信するとそれを表示するとともに、鍵盤からの文字列を行単位にサーバーに送ります。

  3. サーバープロジェクト:ChatServer

    1. 主要変数
      static ServerSocket serverSocket; クライアント接続用ソケット
      static Vector connections; 接続者の一覧

    2. プロジェクトの作成法
      サーバーはアプレットでなくmain()を含むアプリケーションで構成します。Jbuilderでアプリケーションを作成すると、フレームウインドウを自動生成します。ここでは、Jbuilderではプロジェクトのみを作成し、別に作成したJavaのソース(ChatServer.java)をプロジェクトに追加します。

    3. ChatServerクラス

      サーバー機能を処理します。
      1. main(String[] arg){
        引数にポート番号の指定があればそれを読み込みます。ない場合、6000を規定値とします。次に、サーバーソケットを開きます。
         serverSocket = new ServerSocket(port);
        作成に失敗したら終了します。
        serverSocket.accept();でクライアントからの接続要求を待ちます。要求がきたら、クライアントと送受信するソケットcsを受け取り、addConnectionで接続リストに追加します。
         Socket cs = serverSocket.accept();
         addConnection(cs);
         受け取ったcsソケットを渡して、clientProc(cs)クラスのスレッドを生成し、実行を開始します。このソケットの宛先のIPとポートは、接続したクライアントのそれになり、送り主のIPとポートはサーバーのIP
        とポート番号(6000)になります。
         Thread ct = new Thread(new clientProc(cs));
         ct.start();
        サーバーソケットは新規のクライアントからの接続のみを処理し、接続済みのクライアントからのメッセージは、スレッドが受け取り処理をします。

      2. void sendAll(String s)
        接続リストconnectionsに記録された各ソケットに対しメッセージsを送ります。
        1. public static void sendAll(String s){
          connectionsはベクタクラスで、elements()でその要素(ソケット)を取り出します。ソケットから、getOutputStream()で、ストリームクラスを取り出し、PrintWriterクラスのインスタンスpwを生成します。pwに対しては、System.outと同様にStringの出力(送信)が可能です。flush()は送信バッファに残っているデータを吐き出すメソッドで、これをしないと、バッファが一杯にばるまでデータが送信されません。

          if (connections != null){// コネクションがあれば実行します
          for (Enumeration e = connections.elements();e.hasMoreElements();)
              {
          try {
          PrintWriter pw =new
                   PrintWriter(((Socket)e.nextElement()).getOutputStream());
          pw.println(s);
          pw.flush();
          }catch (IOException ex){}
          }
          }
          System.out.println(s);
          }

      3. addConnection(Socket s)、deleteConnection(Socket s)
        接続リストconectionsにsを追加する/削除する

      1. clientblock implements Runnable
        1. 概要
          このスレッドクラスは接続する各クライアントに対して生成されます。各スレッドは受信処理:in.readLine()を実行すると、対応するクライアントからの受信があるまでsleep(休止)状態になります。

        2. 変数
          BufferedReader in; //読み取り用ストリーム
          PrintWriter out; //書き込み用ストリーム
          ChatServer server = null ; //

        3. RUN()
          nameがnullなら、"お名前は?: "を送信し、受信文字列をnameに記録します。
          そうでなければ、"quit"を受信するまで、以下を繰り返します。
           文字列を受信し
           それを、すべてのクライアントに送信する
          "quit"を受信したら、そのクライアントを接続者リストから削除し、ソケットをcloseします。
          文字列を受信するまで、このスレッドはsleep状態に入ります。

    4. 実行
      1. 実行
        実行すると、以下のようなメッセージが表示されます。クライアントからの接続がないと、何も表示されません。接続があると、最初にサーバー側のポートと接続してきたクライアントのポート番号を表示します。
         main:localPort:6000
         main:port1092
        以後は、各スレッドでの表示です。

         mitoさんが参加しました。合計 1名です
         mito> hello
         mitoさんが終りました。合計 0名です

      2. 重複起動
        サーバーを起動したまま、重複してサーバーを起動すると、
         java.net.BindException: Address in use: JVM_Bind
        のエラーになります。6000番のポートはすでに利用されているからです。

      3. 注意
        名前を問い合わせ中に、クライアントが終了すると、"quit"の名前が送信されたと解釈し、ログアウト処理をしません。

  4. クライアントプロジェクト:ChatCL

    1. 概要
      クライアントは使用するポート番号の違いはありますが、telnetと同様な処理をします。まず、送受信を行うrcvMessageクラスのインスタンスrmを作成し、IPとポート番号を指定してサーバーに接続します(rm.openConnection())。サーバーからのメッセージがあれば、それをjtextArea1に表示します。また、送信ボタンが押されれば、jtextField1の文字列をサーバーに送ります。
       終了ボタンが押されると、"quit"メッセージをサーバーに送り、ログアウト(終了)します。

    2. プロジェクトの作成
      1. プロジェクトの作成
        Jbuilderのアプリケーションで作成します。main()をもつApplicationクラスと、フレームをもつFrameクラスが自動生成されます。

      2. クラス
        1. Application1
          1. Application1()
            フレームを表示するFrameクラスのインスタンスを作成し、画面中央に表示します。

          2. main()
            setLookAndFeel();で表示方法を指定するだけです。必要な処理は、frameのインスタンスで実行します。

        2. Frame1クラス
          1. 設計
            GUIでフレームにコンポーネントを配置します。全体のContentPaneのLayoutルールをnullにし、上から、IPを入力するtextFieldと接続ボタン、送信文字を入力するtextFieldと送信・終了ボタン、一番下に受信文字を累積表示するtextAreaを配置します。



          2. メソッド
            1. initRcvMessage(ActionEvent e)
              接続ボタンの押し下げに対応するメソッドです。
               rm = new rcvMessage(this,textField1IP.getText(),6000);
              で、rcvMessageクラスのインスタンスを作成します。このとき、コンストラクタにIPとポート番号をわたします。また、
               rm.openConnection();
              で、サーバーと接続するソケットと入出力ストリームを作成します。

            2. openConnection()
              host:IPと、portでソケットを作成します。また、送信と受信を行うストリームを作成します。
                cltSocket = new Socket(host, port);
               lineOutput = new PrintWriter(cltSocket.getOutputStream());
               lineInput = new BufferedReader(new
               InputStreamReader(cltSocket.getInputStream()));

            3. jSendButton1_actionPerformed()
              送信ボタンの処理です。
              jTextField1の文字列を読み
               rm.lineOutput
              のストリームを利用して送信します。

            4. jquitButton1_actionPerformed()
              終了処理をします。文字列"quit"を送信し、ストリームをcloseします。rm.rstop();は受信のスレッドを停止するための処理です。

        3. rcvMessage implements Runnableクラス
          主に受信処理を行うスレッドです。
          1. rcvMessage(Frame1 rframe,String host, int port):
            コンストラクタで、Frameのインスタンス、IP、ポート番号を受け取ります。
             this.host = host;
            の左辺は引数のhost、右辺はこのクラスの変数です。

          2. openConnection()
            サーバーと接続するソケットを作成します。
             cltSocket = new Socket(host, port);
            ソケットを作成するときに、サーバーからの応答がないと
              Connection refused: connect
            のエラーが発生します。このソケット生成でサーバーの接続処理:serverSocket.accept(); が実行されます。ソケットから入力と出力のストリームを生成します。
             lineOutput = new PrintWriter(cltSocket.getOutputStream());
             lineInput = new BufferedReader(new
             InputStreamReader(cltSocket.getInputStream()));
            どちらも、行単位の入出力処理を行います。
             最後に、受信用のスレッドをstartさせます。

          3. Run()
            lineInput.readLine();で一行を入力し、logに追加して表示します。

    3. 実行
      単独では、実行できません(接続に失敗します)。サーバーを立ち上げてから、接続します。127.0.0.1に接続すると、同じPC上で起動したサーバーに接続できます。接続に成功するとお名前は? のメッセージを受信しますから、名前を入力し送信ボタンを押します。サーバーからのメッセージが表示されます。
       一行入力し送信ボタンを押すと、サーバー経由でメッセージを受信できます。もう一つクライアントを実行し送信すると、別のクライアントのメッセージも表示されます。


    4. 実行
      1. 起動
        コマンドラインから実行する方法もありますが、ここでは、サーバーとクライアントをJbuilderから起動して実行します。

      2. プロジェクトを開く
        ファイルメニューの「プロジェクトを開く」で、双方のプロジェクトを開きます。二つのプロジェクトは、「表示メニュ」の「プロジェクト」をチェックしておけば、プロジェクトバーで切り替えできます。
        1. プロジェクタバー
          ▼マークをクリックしてプロジェクトを切り替えます。


      3. 実行
        1. タブ表示
          Jbuilderの下のタブに実行中のアプリケーションが表示されます。上のstopとplayのアイコンでプログラムの実行、停止ができます。タブを右クリックすると、タブおよび実行プロセスの削除ができます。chatServerを選択すると、serverのメッセージを見ることができます。



        2. チャットの実行方法
          1. サーバーの起動
            サーバーに切り替え実行します。CharServerのタブが現れます。サーバーのIPが不明の場合、サーバー側のPCで ipconfigコマンド(NT系)またはwinipcfg(98系)を実行します。同じPCで実験する場合、IPを127.0.0.1としてサーバーに接続できます。127.0.0.1はlocalhostとも呼ばれ、自分のIPを示す特殊なIPです。

          2. クライアントの起動
            クライアントに切り替え、実行をします。ウインドウが現れます。「お名前」が表示されたら、接続成功です。名前を入力し送信ボタンを押します。
             続けて、別のクライアントを起動するには、プロジェクトバーをクライアントに切り替えたまま、実行をします。二つ目のクライアントが起動します。接続ボタンを押し、別の名前で接続します。一方のクライアントの文が、双方のクライアントに表示されます。chatServerを選択して、serverのメッセージを見ることができます。



  5. ダウンロード
    このプロジェクトをダウンロードできます。次の行をクリックして、chat.exeファイルを適当なフォルダに保存します。
    ダウンロード開始

    このファイルは自己解凍型の圧縮ファイルです。このファイルを実行すると指定したフォルダに必要なファイルが生成されます。

トップに戻る