ChatServer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | package jp.ac.utsunomiya_u.is; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Scanner; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; public class ChatServer { // ChatTaskのリスト private final ArrayList<ChatTask> chatTasks = new ArrayList<>(); public static void main(String[] args) { ChatServer chatServer = new ChatServer(); } /** * コンストラクタ */ public ChatServer() { // Scannerクラスのインスタンス(標準入力System.inからの入力) try (Scanner scanner = new Scanner(System.in)) { System.out.print( "ChatServer (" + getMyIpAddress() + ") > Input server port > " ); // ポート番号入力 int port = scanner.nextInt(); // スレッドプールの生成 ExecutorService executorService = Executors.newCachedThreadPool(); // ServerSocketクラスのインスタンスをポート番号を指定して生成 try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println( "ChatServer (" + getMyIpAddress() + ") > Started and Listening for connections on port " + serverSocket.getLocalPort()); while ( true ) { // ServerSocketに対する要求を待機し,それを受け取ったらSocketクラスのインスタンスからChatTaskを生成 ChatTask chatTask = new ChatTask(serverSocket.accept()); // ChatTaskのインスタンスをリストに保管 chatTasks.add(chatTask); // タスクの実行 executorService.submit(chatTask); } } catch (IOException ex) { Logger.getLogger(ChatServer. class .getName()).log(Level.SEVERE, null , ex); } finally { // スレッドプールの停止 executorService.shutdown(); } } } /** * 自ホストのIPアドレス取得 * * @return 自ホストのIPアドレス */ private static String getMyIpAddress() { try { // 自ホストのIPアドレス取得 return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException ex) { Logger.getLogger(ChatServer. class .getName()).log(Level.SEVERE, null , ex); } return null ; } /** * メッセージの同報通知(サーバ->クライアント) * * @param message メッセージ */ private synchronized void broadcast(String message) { // ChatTashのArrayListの各要素に対して chatTasks.forEach((chatTask) -> { // PrintWriter経由でメッセージ送信 chatTask.getPrintWriter().println(message); }); } private final class ChatTask implements Callable<Void> { // ソケット private Socket socket; // データ送信用Writer(サーバ->クライアント用) private PrintWriter writer = null ; // データ受信用Reader(クライアント->サーバ用) private BufferedReader reader = null ; // ニックネーム private String nickname = null ; /** * コンストラクタ * * @param socket Socketクラスのインスタンス */ ChatTask(Socket socket) { this .socket = socket; try { // Socket経由での書込用PrintWriter生成(サーバ->クライアント用) writer = new PrintWriter(socket.getOutputStream(), true ); // Soket経由での読込用BufferedReader生成(クライアント->サーバ用) reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); // クライアントから接続直後に送られてくるニックネームを取得 nickname = reader.readLine(); // 入室状況の文言作成 String str = "" ; if (chatTasks.size() > 0 ) { for (ChatTask chatTask : chatTasks) { str += nickname + "[" + socket.getRemoteSocketAddress() + "]さん " ; } str += "が入室しています" ; } else { str = "誰も入室していません" ; } // 入室状況の文言送信 writer.println(str); // 入室したことを他のユーザに同報通知 broadcast(nickname + "[" + socket.getRemoteSocketAddress() + "]さんが入室しました" ); System.out.println( "ChatServer (" + getMyIpAddress() + ") > Accepted connection from " + socket.getRemoteSocketAddress() + "[" + nickname + "]" ); } catch (IOException ex) { Logger.getLogger(ChatServer. class .getName()).log(Level.SEVERE, null , ex); } } /** * PrintWriterのゲッタ * * @return PrintWriter */ public PrintWriter getPrintWriter() { return writer; } @Override public Void call() { try { String inputLine; // readerから一行読み込み while ((inputLine = reader.readLine()) != null ) { // 整形文を同報通知 broadcast(nickname + "[" + socket.getRemoteSocketAddress() + "] > " + inputLine); System.out.println( "ChatClient (" + socket.getRemoteSocketAddress() + ") > " + inputLine); } } catch (IOException ex) { Logger.getLogger(ChatServer. class .getName()).log(Level.SEVERE, null , ex); } finally { // 退出した事を同報通知 broadcast(nickname + "[" + socket.getRemoteSocketAddress() + "]さんが退出しました" ); System.out.println( "ChatServer (" + getMyIpAddress() + ") > Terminated connection from " + socket.getRemoteSocketAddress() + "[" + nickname + "]" ); try { // socket, reader, writerのclose if (socket != null ) { socket.close(); } if (reader != null ) { reader.close(); } if (writer != null ) { writer.close(); } } catch (IOException ex) { Logger.getLogger(ChatServer. class .getName()).log(Level.SEVERE, null , ex); } } // ChatTaskのリストから自身を削除 chatTasks.remove( this ); return null ; } } } |
ChatClient.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | package jp.ac.utsunomiya_u.is; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javafx.application.Application; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.ButtonType; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.Stage; public class ChatClient extends Application { // ソケット private Socket socket = null ; // データ送信用Writer private PrintWriter writer = null ; // データ受信用Reader private BufferedReader reader = null ; // 受信タスク private TaskReceiver taskReceiver = null ; // 操作可能コンポーネント private TextField textFieldServerIpAddress = null ; private TextField textFieldServerPortNumber = null ; private TextField textFieldNickname = null ; private Button buttonEnter = null ; private Button buttonExit = null ; private TextArea textAreaView = null ; private TextField textFieldMessage = null ; private Button buttonMessage = null ; @Override public void start(Stage primaryStage) throws Exception { // Stageのタイトル primaryStage.setTitle(getClass().getName()); // Stageのサイズ primaryStage.setWidth( 400 ); primaryStage.setHeight( 300 ); // ルートノード(VBox:単一の垂直列に子をレイアウト) VBox root = new VBox(); // Sceneを介してルートノードをStateに貼付け primaryStage.setScene( new Scene(root)); // Stageの終了ボタンが押下された時の対応 primaryStage.setOnCloseRequest((event) -> { exit(); }); // [第1段] IPアドレス,ポート番号,ハンドル名の設定 // GridPaneレイアウト(行と列の柔軟なグリッド内に子をレイアウト) GridPane gridPaneConfig = new GridPane(); gridPaneConfig.setAlignment(Pos.CENTER); // 各コンポーネントの生成 Label labelIpAddress = new Label( "サーバIPアドレス:" ); textFieldServerIpAddress = new TextField( "" ); Label labelPortNumber = new Label( "サーバポート番号:" ); textFieldServerPortNumber = new TextField( "" ); Label labelNickname = new Label( "ニックネーム:" ); textFieldNickname = new TextField( "" ); buttonEnter = new Button( "入室" ); buttonExit = new Button( "退室" ); // 各コンポーネントの配置 GridPane.setConstraints(labelIpAddress, 0 , 0 ); GridPane.setConstraints(textFieldServerIpAddress, 1 , 0 ); GridPane.setConstraints(labelPortNumber, 0 , 1 ); GridPane.setConstraints(textFieldServerPortNumber, 1 , 1 ); GridPane.setConstraints(labelNickname, 0 , 2 ); GridPane.setConstraints(textFieldNickname, 1 , 2 ); GridPane.setConstraints(buttonEnter, 2 , 1 ); GridPane.setConstraints(buttonExit, 3 , 1 ); // GridPaneに各コンポーネント追加 gridPaneConfig.getChildren().addAll(labelIpAddress, textFieldServerIpAddress, labelPortNumber, textFieldServerPortNumber, labelNickname, textFieldNickname, buttonEnter, buttonExit); // buttonEnterボタンが押下された時の動作 buttonEnter.setOnAction((ActionEvent event) -> { try { // textFiledServerIpAddressテキストフィールドに入力された文字列からサーバIPアドレスを指定 InetAddress serverInetAddress = InetAddress.getByName(textFieldServerIpAddress.getText()); // textFieldServerPortNumberテキストフィールドに入力された文字列からサーバポート番号を指定 int serverPortNumber = Integer.valueOf(textFieldServerPortNumber.getText()); // textFiledNicknameテキストフィールドに入力された文字列からニックネームを指定 String nickname = textFieldNickname.getText(); // ニックネームの長さが0なら if (nickname.length() == 0 ) { new Alert(Alert.AlertType.ERROR, "ハンドル名が空です" , ButtonType.OK).show(); } else { try { // Socket生成 socket = new Socket(serverInetAddress, serverPortNumber); // Socket経由での書込用PrintWriter生成(クライアント->サーバ用) writer = new PrintWriter(socket.getOutputStream(), true ); // Soket経由での読込用BufferedReader生成(サーバ->クライアント用) reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); // 接続直後にサーバにニックネームを通知 writer.println(nickname); // コンポーネットの表示切替 setComponents( true ); // スレッドプールをSingleThreadで生成 ExecutorService executorService = Executors.newSingleThreadExecutor(); // TaskReceiver生成 taskReceiver = new TaskReceiver(); // タスク実行 executorService.submit(taskReceiver); // スレッドプールを停止 executorService.shutdown(); } catch (IOException ex) { // ダイアログ(エラー用)生成 new Alert(Alert.AlertType.ERROR, "サーバに接続出来ません" , ButtonType.OK).show(); } } } catch (UnknownHostException ex) { new Alert(Alert.AlertType.ERROR, "IPアドレスが不正です" , ButtonType.OK).show(); } catch (NumberFormatException ex) { new Alert(Alert.AlertType.ERROR, "ポート番号が不正です" , ButtonType.OK).show(); } }); // [第2段] チャット内容の表示ビュー // TextArea生成 textAreaView = new TextArea(); // 表示用なので,書込不可 textAreaView.setEditable( false ); // [第3段] メッセージ入力 // HBoxレイアウト(単一の水平行に子をレイアウト) HBox hBoxMessage = new HBox(); hBoxMessage.setAlignment(Pos.CENTER); hBoxMessage.setSpacing( 10 ); // 各コンポーネントの生成 textFieldMessage = new TextField(); buttonMessage = new Button( "送信" ); // HBoxレイアウトに各コンポーネントを貼付け hBoxMessage.getChildren().addAll(textFieldMessage, buttonMessage); // buttonMessageボタンが押下された時の動作 buttonMessage.setOnAction((ActionEvent event) -> { // textFieldMessageテキストフィールドに入力された文字列を取得 String inputLine = textFieldMessage.getText(); // 入力文字列長が0より大きい if (inputLine.length() > 0 ) { // PrintWriterに書込(クライアント->サーバ) writer.println(inputLine); } // textFiledMessageテキストフィールドをクリア textFieldMessage.clear(); }); // コンポーネントの表示切替(初期化) setComponents( false ); // 3段のレイアウトをルートノードに貼付け root.getChildren().addAll(gridPaneConfig, textAreaView, hBoxMessage); // Stageの表示 primaryStage.show(); } /** * ”退室”ボタンが押下された時の処理 */ private void exit() { // タスクをキャンセル taskReceiver.cancel(); try { // SocketとReaderとWriterをclose if (socket != null ) { socket.close(); } if (reader != null ) { reader.close(); } if (writer != null ) { writer.close(); } } catch (IOException ex) { new Alert(Alert.AlertType.ERROR, "ソケットを閉じることが出来ません" , ButtonType.OK).show(); } } /** * 各コンポーネントの変更可否 * * @param connected 接続時を意味するフラグ */ private void setComponents( boolean connected) { // サーバ接続時に無効 textFieldServerIpAddress.setDisable(connected); textFieldServerPortNumber.setDisable(connected); textFieldNickname.setDisable(connected); buttonEnter.setDisable(connected); // サーバ接続時に有効 buttonExit.setDisable(!connected); textAreaView.setDisable(!connected); textFieldMessage.setDisable(!connected); buttonMessage.setDisable(!connected); } /** * サーバからのメッセージを受信するためのタスク */ private class TaskReceiver extends Task<Void> { @Override protected Void call() throws Exception { String inputLine; // Readerから読み込んだ一行をTextAreaに追記 while ((inputLine = reader.readLine()) != null ) { textAreaView.appendText(inputLine + "\n" ); } return null ; } } public static void main(String[] args) { launch(args); } } |