質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.78%

java チャットアプリ

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 5,577

score 27

javaでチャットアプリを作ろうと思い,CUIでチャットサーバーをjavaFXを使用してチャットクライアントを作成しました.チャットサーバーの方はメッセージを受信,送信を送るものをスレッドで表現しています.GUIの方のソースコードではconnectボタンをクリックするとソケットの接続とメッセージの受信を行い,中央のテキストエリアにサーバーから送られたメッセージを表示するスレッドと送信ボタンをクリックするとテキストフィールドに入力されている文字をサーバーに送信するスレッドを作成してそれらをイベント発生時に開始するようなものを作成しました.
しかし,問題が二つ発生してしまいました

  • サーバーに送信できない
  • sendボタンをクリックしてしまうとテキストフィールドに文字が入力できなくなる
    このうちのサーバーに送信する問題は解決できたのですが下の問題が解決できません
    原因がわからないので教えてください
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class ChatServer {

    static int port = 1000;
    public static void main(String[] args) {
        try{
            @SuppressWarnings("resource")
            ServerSocket server = new ServerSocket(port);
            Socket socket = null;
            System.out.println("開始します");
            while(true){
                try{
                socket = server.accept();//接続要求を待つ
                //スレッド生成
                ChatServerThread thread = new ChatServerThread(socket);
                thread.start();
            }catch (IOException e){
                }
            }
        }catch(IOException e){
            System.out.println(e);
        }

    }

}

チャットサーバースレッド
import java.io.*;
import java.net.*;
import java.util.*;

public class ChatServerThread extends Thread{
    static int port = 1000;
    static Vector<ChatServerThread> threads;//動作中のスレッドの集合
    Socket socket;//クライアントに対するソケット
    String nickname = null;

    public ChatServerThread(Socket s){
        super();
        socket = s;
        if(threads == null)
            threads = new Vector<ChatServerThread>();//Threadsの初期化
        threads.add(this);//自分自身を追加
    }

    public String getNickname(){
        return this.nickname;
    }

    public void run(){
        try{
            System.out.println("Connect");
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            OutputStream os = socket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF("Connect");//接続したのを送信する
            while(true){
                try{
                String message = dis.readUTF();
                System.out.println(message);
                if(message == null){
                    socket.close();
                    threads.remove(this);//スレッドを削除
                    return;//スレッド消滅
                }
                if(message.startsWith("NICK:")){
                    StringTokenizer st = new StringTokenizer(message,":");
                    st.nextToken();//NICKの後のトークンをニックネームとする
                    nickname = st.nextToken();
                    dos.writeUTF("Hello"+nickname+"! You entered.\r\n");
                    dos.flush();
                    }
                else{
                    if(nickname != null){//ニックネームが登録されている場合
                        chat(message);
                    }else{//ニックネームが登録されてない場合のエラー
                        dos.writeUTF("Error! あなたはニックネームを入力していない");
                        dos.flush();
                        }
                    }
                } catch(IOException e){
                    e.getMessage();
                    socket.close();
                    threads.remove(this);
                    return;
                    }    
                }    
            }catch(IOException e){
                System.out.println(e);
            }
        }

    public void chat(String message){
        for(int i = 0;i < threads.size();i++){
            ChatServerThread thread = (ChatServerThread)threads.get(i);
            if(thread.isAlive()){
                thread.talkone(this,message);
            }
        }
        System.err.println(getNickname() + ":" + message);
    }
    public void talkone(ChatServerThread talker,String message){
        try{
            OutputStream os = socket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
            String nick = talker.getNickname();
            if(talker == this){
                dos.writeUTF("[" + nick + "]" + message +"\r\n");
            }else {
                dos.writeUTF("<" + nick + ">" + message + "\r\n");
            }
            dos.flush();
        }catch(IOException e){
            System.out.println(e);
        }
    }
}

```チャットクライアント  
package application;  



import java.io.IOException;  

import java.net.InetSocketAddress;  
import java.net.Socket;  

import javafx.event.ActionEvent;  
import javafx.fxml.FXML;  
import javafx.scene.control.Button;  
import javafx.scene.control.TextArea;  
import javafx.scene.control.TextField;  



public class SampleController {  
    @FXML TextField inputAdress;  
    @FXML TextField inputPort;  
    @FXML TextArea displayMessage;  
    @FXML TextField sendMessage;  
    @FXML Button buttonConnect;  
    @FXML Button buttonDisconnect;  
    @FXML Button buttonSendMessage;  

    private NetConnectTask taskConnect;  
    private NetSendMessageTask taskSendMessage;  
     Socket socket;  

    @FXML protected void handleButtonConnectAction(ActionEvent event){  
        String adress = inputAdress.getText();  
        int port = Integer.parseInt(inputPort.getText());  
        socket = new Socket();  
        // 指定されたホスト名(IPアドレス)とポート番号でサーバに接続する  
        try{  
        socket.connect(new InetSocketAddress(adress, port));  
        }catch(IOException e){  
            displayMessage.setText(e.getMessage());  
        }  
        taskConnect = new NetConnectTask(socket);  
        displayMessage.textProperty().bind(taskConnect.messageProperty());  

        Thread thread = new Thread(taskConnect);  
        thread.setDaemon(true);  
        thread.start();  
    }  
    @FXML protected void handleButtonDisconnectAction(ActionEvent event){  
        taskConnect.cancel();  
        taskSendMessage.cancel();  
        sendMessage.textProperty().unbind();  
        displayMessage.textProperty().unbind();  
    }  
    @FXML protected synchronized void handleButtonSendMessageAction(ActionEvent event) {  

        taskSendMessage = new NetSendMessageTask(socket,sendMessage.getText());  
        sendMessage.textProperty().bind(taskSendMessage.messageProperty());  

        Thread thread = new Thread(taskSendMessage);  
        thread.setDaemon(true);  
        thread.start();  
    }  

}
package application;  

import java.io.DataInputStream;  
import java.io.InputStream;  
import java.net.Socket;  

import javafx.concurrent.Task;  

public class NetConnectTask extends Task<Void> {  
    private Socket socket;  


    protected NetConnectTask(Socket socket){  
        this.socket = socket;  
    }  

    @Override  
    protected Void call() throws Exception {  

        // 接続されたソケットの入力ストリームを取得し,データ入力ストリームを連結  
        InputStream is = socket.getInputStream();  
        DataInputStream dis = new DataInputStream(is);  
        while(socket != null){  
        if(isCancelled()){  
            socket.close();  
            break;  
        }      
        // データの受信  
        String message = dis.readUTF();  
        updateMessage(message+"\n");  


        }  
        return null;  
    }  
}
package application;  


import java.io.*;  
import java.net.Socket;  

import javafx.beans.property.SimpleStringProperty;  
import javafx.beans.property.StringProperty;  
import javafx.concurrent.Task;  

public class NetSendMessageTask extends Task<Void>{  
        private Socket socket;  
        private StringProperty message = new SimpleStringProperty();  


        protected NetSendMessageTask(Socket socket,String message){  
            this.socket = socket;  
            this.message.set(message);  
        }  


        @Override  
        protected Void call() throws Exception {  

            // 接続されたソケットの出力ストリームを取得し,データ出力ストリームを連結  
            OutputStream os = socket.getOutputStream();  
            DataOutputStream dos = new DataOutputStream(os);  
            while(socket != null){  
            if(isCancelled()){  
                socket.close();  
                break;  
            }      
            // データの送信  

            dos.writeUTF(message.toString());  
            updateMessage("");  


            }  
            return null;  
        }  
    }
<?xml version="1.0" encoding="UTF-8"?>  

<?import javafx.scene.control.*?>  
<?import java.lang.*?>  
<?import javafx.scene.layout.*?>  
<?import javafx.scene.layout.GridPane?>  


<GridPane xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="application.SampleController">  
   <columnConstraints>  
      <ColumnConstraints />  
      <ColumnConstraints />  
      <ColumnConstraints />  
      <ColumnConstraints />  
   </columnConstraints>  
   <rowConstraints>  
      <RowConstraints />  
      <RowConstraints />  
      <RowConstraints />  
      <RowConstraints />  
   </rowConstraints>  
   <children>  
      <Label prefHeight="17.0" prefWidth="54.0" text="IPadress:" />  
      <TextField fx:id="inputAdress" GridPane.columnIndex="1" />  
      <Label prefHeight="17.0" prefWidth="53.0" text="Port:" GridPane.rowIndex="1" />  
      <TextArea fx:id="displayMessage" prefHeight="200.0" prefWidth="187.0" GridPane.columnSpan="4" GridPane.rowIndex="2" />  
      <TextField fx:id="inputPort" GridPane.columnIndex="1" GridPane.rowIndex="1" />  
      <TextField fx:id="sendMessage" GridPane.columnSpan="3" GridPane.rowIndex="3" />  
      <Button fx:id="buttonConnect" mnemonicParsing="false" onAction="#handleButtonConnectAction" text="connect" GridPane.columnIndex="2" GridPane.rowIndex="1" />  
      <Button fx:id="buttonDisconnect" mnemonicParsing="false" onAction="#handleButtonDisconnectAction" text="disconnect" GridPane.columnIndex="3" GridPane.rowIndex="1" />  
      <Button fx:id="buttonSendMessage" mnemonicParsing="false" onAction="#handleButtonSendMessageAction" prefHeight="25.0" prefWidth="73.0" text="Send" GridPane.columnIndex="3" GridPane.rowIndex="3" />  
   </children>  
</GridPane>
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

1番目の問題について。

NetSendMessageTaskクラスの「データの送信」コメントの次の行で、getMessageメソッドを使用していますが、このメソッドはJavaFXアプリケーションスレッドからしかアクセスできない仕組みになっています。
試しに、getMessageメソッドの行をtry-catchで囲んでみてください。
Task must only be used from the FX Application Threadという例外メッセージが表示されるはずです。

Futureを返すタスク内での例外は、内部でcatchしない場合はFutureから取得する必要があります。
ご提示のコードでは、内部でcatchしておらず、Futureのハンドリングもしていないので、例外が出力されないのです。

詳しくは、以下の質問を参照してください。

ScheduledThreadPoolの中で文字列フォーマットのエラーがスローされず処理がブロックされてしまう(23233)|teratail
https://teratail.com/questions/23233

...

さて、テキストフィールドの値をTaskに渡すには、どのような方法が適切かがちょっと分からないのですが、
NetSendMessageTaskクラスにテキストフィールドのStringPropertyを渡してしまって、そこからメッセージを取得すれば一応動作します。


(追記)

2番目の問題について。

意図しない処理になるかもしれませんが、とりあえずは以下をやってみてください。

  • NetSendMessageTaskcallメソッドのwhileifに変更(応答を待っていないので無限ループになる)
  • NetSendMessageTaskcallメソッドのupdateMessage("");messageProp.set("");に変更(messagePropはテキストフィールドのStringProperty
  • handleButtonSendMessageActionsendMessage.textProperty().bind(taskSendMessage.messageProperty());を削除(使っていないので)

なぜタスクとテキストフィールドのバインドが上手く行かないのかは、勉強不足のため分かりませんでした。

...

ループで処理するとしたら、サーバーとはデータをキャッチボールのようにやりとりしないといけないので、ソケットの入力と出力を別々のタスクにせず、できれば、メッセージを送ったらサーバーの応答を待つようなロジックにしたほうが良いと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/12/31 09:17 編集

    できない原因はわかりました,ありがとうございます
    メッセージを送信することはできたのですが,一回メッセージを送信した後だと文字が入力できない問題が解決できないのですがこれはどうしたらよいのでしょうか

    キャンセル

  • 2016/01/01 14:01

    もうひとつの問題について、回答欄に追記しましたのでご確認ください。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る