基于JavaFX、Socket、多线程的多客户Server通信小demo(聊天室)
写在最前面:由于网络编程课程需要,自己捣鼓了一个基于JavaFX、Socket、多线程的多客户Server通信小demo。有挺多小bug的,如有不足请谅解~ 若有错误欢迎指正~
运行效果图:
源码:
package com.wqs.server;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
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;
import javafx.stage.WindowEvent;
/**
* 服务端UI
* @author 花大侠
*
*/
public class ServerUI extends Application{
TextArea receivedMsgArea = new TextArea();
TextField ipText = new TextField();
TextField portText = new TextField();
TextArea sendMsgArea = new TextArea();
TextField statusText = new TextField();
Button sendButton = new Button(" Send ");
ObservableList<String> clients = FXCollections.observableArrayList();
ListView<String> clientListView = new ListView<>(clients);
public void start(Stage primaryStage) throws Exception {
//右边 Received Message
GridPane rightPane = new GridPane();
rightPane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
rightPane.setHgap(5.5);
rightPane.setVgap(5.5);
rightPane.add(new Label("Received Message:"), 0, 0);
receivedMsgArea.setWrapText(true);
receivedMsgArea.setEditable(false);
receivedMsgArea.setMaxWidth(350);
receivedMsgArea.setPrefHeight(410);
rightPane.add(receivedMsgArea, 0, 1);
//左边 IPAdress+Port
GridPane leftPane1 = new GridPane();
leftPane1.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane1.setHgap(5.5);
leftPane1.setVgap(5.5);
leftPane1.add(new Label("IPAdress:"), 0, 0);
ipText.setEditable(false);
leftPane1.add(ipText, 1, 0);
leftPane1.add(new Label("Port:"), 0, 1);
portText.setEditable(false);
leftPane1.add(portText, 1, 1);
//左边 Choose Client
GridPane leftPane2 = new GridPane();
leftPane2.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane2.setHgap(5.5);
leftPane2.setVgap(5.5);
leftPane2.add(new Label("Choose Client:"), 0, 0);
clientListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
clientListView.setMaxHeight(80);
clientListView.setMaxWidth(275);
leftPane2.add(clientListView, 0, 1);
//左边 Send Message
leftPane2.add(new Label("Send Message:"), 0, 2);
sendMsgArea.setMaxHeight(150);
sendMsgArea.setMaxWidth(275);
sendMsgArea.setWrapText(true);
leftPane2.add(sendMsgArea, 0, 3);
//左边 Connect Status + button
GridPane leftPane3 = new GridPane();
leftPane3.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane3.setHgap(5.5);
leftPane3.setVgap(5.5);
statusText.setEditable(false);
leftPane3.add(statusText, 0, 0);
leftPane3.add(sendButton, 1, 0);
//组合
VBox vBox = new VBox();
vBox.getChildren().addAll(leftPane1, leftPane2, leftPane3);
HBox hBox = new HBox();
hBox.getChildren().addAll(vBox, rightPane);
Scene scene = new Scene(hBox);
primaryStage.setTitle("server");
primaryStage.setScene(scene);
//关闭UI线程时同时关闭各子线程
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.exit(0);
}
});
primaryStage.show();
statusText.setText("0 Connect success.");
//启动server线程
new Thread(new MyServer(ipText, portText, sendMsgArea, statusText, sendButton, receivedMsgArea, clients, clientListView)).start();
}
}
package com.wqs.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
/**
* 服务端
* @author 花大侠
*
*/
public class MyServer implements Runnable {
//Server端监听的端口号
public static final int PORT = 9999;
//映射表 存放每个socket地址(IP:Port)及其对应的PrintWriter
//为群发消息做准备
Map<String, PrintWriter> map = new HashMap<>();
//存放已连接socket地址(IP:Port),用于clientListView
ObservableList<String> clients;
ListView<String> clientListView;
TextField ipText;
TextField portText;
TextArea sendMsgArea;
TextField statusText;
Button sendButton;
TextArea receivedMsgArea;
public MyServer() {
}
public MyServer(TextField ipText, TextField portText, TextArea sendMsgArea, TextField statusText,
Button sendButton, TextArea receivedMsgArea, ObservableList<String> clients, ListView<String> clientListView) {
super();
this.ipText = ipText;
this.portText = portText;
this.sendMsgArea = sendMsgArea;
this.statusText = statusText;
this.sendButton = sendButton;
this.receivedMsgArea = receivedMsgArea;
this.clients = clients;
this.clientListView = clientListView;
}
/**
* 更新UI界面的IP和Port
*/
public void updateIpAndPort() {
//用于在非UI线程更新UI界面
Platform.runLater(()->{
ipText.setText("127.0.0.1");
portText.setText(String.valueOf(PORT));
});
}
@Override
public void run() {
updateIpAndPort();
ServerSocket server;
Socket socket;
try {
server = new ServerSocket(PORT);
while(true) {
socket = server.accept();
//一个客户端接入就启动一个handler线程去处理
new Thread(new handler(map, socket, sendMsgArea, statusText, sendButton, receivedMsgArea, clients, clientListView)).start();
}
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
package com.wqs.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javafx.application.Platform;
import javafx.collections.ObservableList;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
/**
* 处理线程
* @author 花大侠
*
*/
public class handler implements Runnable {
Socket socket;
TextArea sendMsgArea;
TextField statusText;
Button sendButton;
TextArea receivedMsgArea;
ObservableList<String> clients;
ListView<String> clientListView;
Map<String, PrintWriter> map;
public handler() {
super();
}
public handler(Map<String, PrintWriter> map, Socket socket, TextArea sendMsgArea, TextField statusText, Button sendButton,
TextArea receivedMsgArea, ObservableList<String> clients, ListView<String> clientListView) {
super();
this.map = map;
this.socket = socket;
this.sendMsgArea = sendMsgArea;
this.statusText = statusText;
this.sendButton = sendButton;
this.receivedMsgArea = receivedMsgArea;
this.clients = clients;
this.clientListView = clientListView;
}
/**
* 接入客户端后,更新UI界面
* 1.添加新接入客户端的地址信息
* 2.receivedMsgarea打印成功连接信息
* 3.statusText更新成功连接个数
*/
public void updateForConnect(String remoteSocketAddress) {
Platform.runLater(()->{
clients.add(remoteSocketAddress);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
receivedMsgArea.appendText(String.valueOf(clients.size()) + " Connected from " + remoteSocketAddress + " " + sdf.format(new Date()) + "\n");
statusText.setText(String.valueOf(clients.size()) + " Connect success.");
});
}
/**
* 断开客户端后,更新UI界面
* 1.移除断开客户端的地址信息
* 2.receivedMsgarea打印断开连接信息
* 3.statusText更新成功连接个数
* 4.移除map中对应的remoteSocketAddress
*/
public void updateForDisConnect(String remoteSocketAddress) {
Platform.runLater(()->{
clients.remove(remoteSocketAddress);
statusText.setText(String.valueOf(clients.size()) + " Connect success.");
receivedMsgArea.appendText(remoteSocketAddress + " out of connected.." + "\n");
map.remove(remoteSocketAddress);
});
}
/**
* 单发及群发消息
* 1.为clientListView设置监听器
* 1.1获取已选择的项(IP:Port)
* 1.2从映射表中取出对应printWriter放入printWriters集合
* 2.为sendButton设置鼠标点击事件
* 2.1遍历printWriters集合
* 2.2写入待发送的消息
*/
public void sendMessage() {
Set<PrintWriter> printWriters = new HashSet<>();
clientListView.getSelectionModel().selectedItemProperty().addListener(ov->{
printWriters.clear();
for(String key: clientListView.getSelectionModel().getSelectedItems()) {
printWriters.add(map.get(key));
}
});
sendButton.setOnAction(e->{
for (PrintWriter printWriter : printWriters) {
printWriter.write("127.0.0.1:9999" + " " + sendMsgArea.getText() + "\r\n");
printWriter.flush();
}
});
}
@Override
public void run() {
String remoteSocketAddress = socket.getRemoteSocketAddress().toString().substring(1);
updateForConnect(remoteSocketAddress);
try {
InputStream in = socket.getInputStream();
BufferedReader bReader = new BufferedReader(new InputStreamReader(in));
OutputStream out = socket.getOutputStream();
PrintWriter pWriter = new PrintWriter(out);
map.put(remoteSocketAddress, pWriter);
//发消息
sendMessage();
//收消息
String message;
while(true) {
message = bReader.readLine();
receivedMsgArea.appendText(message + "\n");
}
} catch (IOException e) {
updateForDisConnect(remoteSocketAddress);
}
}
}
package com.wqs.client;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
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;
import javafx.stage.WindowEvent;
/**
* 客户端UI
* @author 花大侠
*
*/
public class ClientUI extends Application{
final TextArea receivedMsgArea = new TextArea();
final TextField ipText = new TextField();
final TextField portText = new TextField();
final TextArea sendMsgArea = new TextArea();
final TextField statusText = new TextField();
final Button sendButton = new Button(" Send ");
@Override
public void start(Stage primaryStage) throws Exception {
//右边 Received Message
GridPane rightPane = new GridPane();
rightPane.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
rightPane.setHgap(5.5);
rightPane.setVgap(5.5);
rightPane.add(new Label("Received Message:"), 0, 0);
receivedMsgArea.setWrapText(true);
receivedMsgArea.setEditable(false);
receivedMsgArea.setMaxWidth(350);
receivedMsgArea.setPrefHeight(410);
rightPane.add(receivedMsgArea, 0, 1);
//左边 IPAdress+Port
GridPane leftPane1 = new GridPane();
leftPane1.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane1.setHgap(5.5);
leftPane1.setVgap(5.5);
leftPane1.add(new Label("IPAdress:"), 0, 0);
ipText.setEditable(false);
leftPane1.add(ipText, 1, 0);
leftPane1.add(new Label("Port:"), 0, 1);
portText.setEditable(false);
leftPane1.add(portText, 1, 1);
//左边 Send Message
GridPane leftPane2 = new GridPane();
leftPane2.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane2.setHgap(5.5);
leftPane2.setVgap(5.5);
leftPane2.add(new Label("Send Message:"), 0, 2);
sendMsgArea.setPrefHeight(250);
sendMsgArea.setMaxWidth(275);
sendMsgArea.setWrapText(true);
leftPane2.add(sendMsgArea, 0, 3);
//左边 Connect Status + button
GridPane leftPane3 = new GridPane();
leftPane3.setPadding(new Insets(11.5, 12.5, 13.5, 14.5));
leftPane3.setHgap(5.5);
leftPane3.setVgap(5.5);
statusText.setEditable(false);
leftPane3.add(statusText, 0, 0);
leftPane3.add(sendButton, 1, 0);
//组合
VBox vBox = new VBox();
vBox.getChildren().addAll(leftPane1, leftPane2, leftPane3);
HBox hBox = new HBox();
hBox.getChildren().addAll(vBox, rightPane);
Scene scene = new Scene(hBox);
primaryStage.setTitle("client");
primaryStage.setScene(scene);
//关闭UI线程时同时关闭各子线程
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {
System.exit(0);
}
});
primaryStage.show();
//启动Client线程
new Thread(new MyClient(receivedMsgArea, ipText, portText, sendMsgArea, statusText, sendButton)).start();
}
}
package com.wqs.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
/**
* 客户端
* @author 花大侠
*
*/
public class MyClient implements Runnable {
TextArea receivedMsgArea;
TextField ipText;
TextField portText;
TextArea sendMsgArea;
TextField statusText;
Button sendButton;
public MyClient() {
super();
}
public MyClient(TextArea receivedMsgArea, TextField ipText, TextField portText, TextArea sendMsgArea,
TextField statusText, Button sendButton) {
super();
this.receivedMsgArea = receivedMsgArea;
this.ipText = ipText;
this.portText = portText;
this.sendMsgArea = sendMsgArea;
this.statusText = statusText;
this.sendButton = sendButton;
}
/**
* 更新UI界面的IP和端口
*/
public void updateIpAndPort(Socket socket) {
Platform.runLater(()->{
ipText.setText(socket.getLocalAddress().toString().substring(1));
portText.setText(String.valueOf(socket.getLocalPort()));
});
}
@Override
public void run() {
try {
Socket socket = new Socket("127.0.0.1", 9999);
updateIpAndPort(socket);
statusText.setText("Success connected..");
receivedMsgArea.appendText("Hello, I am server..." + "\n");
InputStream in = socket.getInputStream();
BufferedReader bReader = new BufferedReader(new InputStreamReader(in));
OutputStream out = socket.getOutputStream();
PrintWriter pWriter = new PrintWriter(out);
//发消息
sendButton.setOnAction(e->{
pWriter.write(socket.getLocalSocketAddress().toString().substring(1) + " " + sendMsgArea.getText() + "\r\n");
pWriter.flush();
});
//收消息
String message;
while(true) {
message = bReader.readLine();
receivedMsgArea.appendText(message + "\n");
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
statusText.setText("Server is closed.");
receivedMsgArea.appendText("Server is closed.");
e.printStackTrace();
}
}
}