基于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();
		}
	}

}

猜你喜欢

转载自blog.csdn.net/wqsbenren/article/details/89063274