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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

1回答

7751閲覧

java チャットアプリ

kt3302y

総合スコア27

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2015/12/30 13:30

編集2015/12/31 00:46

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>

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

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/30 15:42

編集2015/12/31 03:07
argius

総合スコア9390

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kt3302y

2015/12/31 00:23 編集

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

2016/01/01 05:01

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問