这是一篇关于JAVA的聊天室室小软件,用的swing的技术同时也用到了socket。今天发布出来,希望能帮到大家。
开发环境
开发工具:IDEA2021.3.1
JDK版本:JDK8
JDK其他版本会有问题
项目结构
启动后服务的Server。
启动客户端MainLauncher
下载地址:
链接:https://pan.baidu.com/s/1CqRtwb-5Vbj8J-UmrPFi6w
提取码:4ysl
一、运行画面展示
登录界面,选择性别,输入用户名
聊天界面,第一个进来的界面里只有一个人
再登录一个账号。
两个人可以相互通信的界面
二、代码部分
1.客户端启动代码
package com.bjpowernode.client;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
public class MainLauncher extends Application {
private static Stage primaryStageObj;
@Override
public void start(Stage primaryStage) throws Exception {
primaryStageObj = primaryStage;
Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("views/LoginView.fxml"));
primaryStage.initStyle(StageStyle.UNDECORATED);
primaryStage.setTitle("一起来聊天");
primaryStage.getIcons().add(new Image(getClass().getClassLoader().getResource("images/logo.png").toString()));
Scene mainScene = new Scene(root, 350, 420);
mainScene.setRoot(root);
primaryStage.setResizable(false);
primaryStage.setScene(mainScene);
primaryStage.show();
primaryStage.setOnCloseRequest(e -> Platform.exit());
}
public static void main(String[] args) {
launch(args);
}
public static Stage getPrimaryStage() {
return primaryStageObj;
}
}
LoginController类
package com.bjpowernode.client.login;
import com.bjpowernode.client.MainLauncher;
import com.bjpowernode.client.chat.ChatController;
import com.bjpowernode.client.chat.Listener;
import com.bjpowernode.client.util.Drag;
import com.bjpowernode.client.util.ThreadPoolUtil;
import com.trynotifications.util.ResizeHelper;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import javafx.util.Duration;
import java.io.IOException;
import java.net.URL;
import java.util.Random;
import java.util.ResourceBundle;
public class LoginController implements Initializable {
@FXML private ImageView defaultView;
@FXML private ImageView girlView;
@FXML private ImageView boyView;
@FXML public TextField hostnameTextfield;
@FXML private TextField portTextfield;
@FXML private TextField usernameTextfield;
@FXML private ChoiceBox imagePicker;
@FXML private Label selectedPicture;
@FXML private BorderPane borderPane;
public static ChatController chatController;
private Scene scene;
private static LoginController instance;
public LoginController() {
instance = this;
}
public static LoginController getInstance() {
return instance;
}
/**
* 处理点击登录按钮事件
* @throws IOException
*/
public void loginButtonAction() throws IOException {
String hostname = hostnameTextfield.getText();
int port = Integer.parseInt(portTextfield.getText());
String username = usernameTextfield.getText();
String picture = selectedPicture.getText();
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/views/ChatView.fxml"));
Parent window = (Pane) fxmlLoader.load();
chatController = fxmlLoader.getController();
//创建监听器对象
Listener listener = new Listener(hostname, port, username, picture,chatController);
//将监听器加入到线程池
ThreadPoolUtil.poolExecutor.execute(listener);
this.scene = new Scene(window);
}
/**
* 显示界面场景
*/
public void showScene() {
Platform.runLater(() -> {
Stage stage = (Stage) hostnameTextfield.getScene().getWindow();
stage.setResizable(true);
stage.setWidth(1040);
stage.setHeight(620);
stage.setOnCloseRequest((WindowEvent e) -> {
Platform.exit();
System.exit(0);
});
stage.setScene(this.scene);
stage.setMinWidth(800);
stage.setMinHeight(300);
ResizeHelper.addResizeListener(stage);
stage.centerOnScreen();
chatController.setUsernameLabel(usernameTextfield.getText());
chatController.setImageLabel(selectedPicture.getText());
});
}
@Override
public void initialize(URL location, ResourceBundle resources) {
imagePicker.getSelectionModel().selectFirst();
selectedPicture.textProperty().bind(imagePicker.getSelectionModel().selectedItemProperty());
selectedPicture.setVisible(false);
//处理鼠标拖拽界面
new Drag().handleDrag(borderPane);
//处理头像
imagePicker.getSelectionModel().selectedItemProperty().addListener((ChangeListener<String>) (selected, oldPicture, newPicture) -> {
if (!oldPicture.equals(newPicture)) {
//隐藏所有头像
defaultView.setVisible(false);
boyView.setVisible(false);
girlView.setVisible(false);
//展示用户选中的头像
switch (newPicture) {
case "default":
defaultView.setVisible(true);
break;
case "boy":
boyView.setVisible(true);
break;
case "girl":
girlView.setVisible(true);
break;
}
}
});
int numberOfSquares = 30;
while (numberOfSquares > 0){
generateAnimation();
numberOfSquares--;
}
}
/**
* 生成随机动画
*/
public void generateAnimation(){
Random rand = new Random();
int size = rand.nextInt(50) + 1;
int speed = rand.nextInt(10) + 5;
int startXPoint = rand.nextInt(420);
int startYPoint = rand.nextInt(350);
int direction = rand.nextInt(5) + 1;
KeyValue moveXAxis = null;
KeyValue moveYAxis = null;
Rectangle r1 = null;
switch (direction){
case 1 :
// MOVE LEFT TO RIGHT
r1 = new Rectangle(0,startYPoint,size,size);
moveXAxis = new KeyValue(r1.xProperty(), 350 - size);
break;
case 2 :
// MOVE TOP TO BOTTOM
r1 = new Rectangle(startXPoint,0,size,size);
moveYAxis = new KeyValue(r1.yProperty(), 420 - size);
break;
case 3 :
// MOVE LEFT TO RIGHT, TOP TO BOTTOM
r1 = new Rectangle(startXPoint,0,size,size);
moveXAxis = new KeyValue(r1.xProperty(), 350 - size);
moveYAxis = new KeyValue(r1.yProperty(), 420 - size);
break;
case 4 :
// MOVE BOTTOM TO TOP
r1 = new Rectangle(startXPoint,420-size ,size,size);
moveYAxis = new KeyValue(r1.xProperty(), 0);
break;
case 5 :
// MOVE RIGHT TO LEFT
r1 = new Rectangle(420-size,startYPoint,size,size);
moveXAxis = new KeyValue(r1.xProperty(), 0);
break;
case 6 :
//MOVE RIGHT TO LEFT, BOTTOM TO TOP
r1 = new Rectangle(startXPoint,0,size,size);
moveXAxis = new KeyValue(r1.yProperty(), 420 - size);
moveYAxis = new KeyValue(r1.xProperty(), 350 - size);
break;
}
r1.setFill(Color.web("#F89406"));
r1.setOpacity(0.1);
KeyFrame keyFrame = new KeyFrame(Duration.millis(speed * 1000), moveXAxis, moveYAxis);
Timeline timeline = new Timeline();
timeline.setCycleCount(Timeline.INDEFINITE);
timeline.setAutoReverse(true);
timeline.getKeyFrames().add(keyFrame);
timeline.play();
borderPane.getChildren().add(borderPane.getChildren().size()-1,r1);
}
/**
* 关闭
*/
public void closeSystem(){
Platform.exit();
System.exit(0);
}
/**
* 最小化
*/
public void minimizeWindow(){
MainLauncher.getPrimaryStage().setIconified(true);
}
}
ChatController类
package com.bjpowernode.client.chat;
import com.bjpowernode.bean.Message;
import com.bjpowernode.bean.User;
import com.bjpowernode.client.util.Drag;
import com.bjpowernode.client.util.ThreadPoolUtil;
import com.trynotifications.animations.AnimationType;
import com.trynotifications.bean.BubbleSpec;
import com.trynotifications.bean.BubbledLabel;
import com.trynotifications.notification.TrayNotification;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.util.Duration;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class ChatController implements Initializable {
@FXML private TextArea messageBox;
@FXML private Label usernameLabel;//显示的用户名
@FXML private Label onlineCountLabel;//显示在线用户总数
@FXML private ListView userList;//在线用户
@FXML private ImageView userImageView;//头像
@FXML ListView chatPane;
@FXML BorderPane borderPane;
/**
* 处理发送按钮点击事件
* @throws IOException
*/
public void sendButtonAction() throws IOException {
//获取用户输入的消息
String msg = messageBox.getText();
if (!msg.isEmpty()) {
Listener.instance.send(msg);
//清空输入框的消息
messageBox.clear();
}
}
/**
* 显示发送的消息
* @param msg
*/
public synchronized void showMsg(Message msg) {
//别人发送的信息
Task<HBox> othersMessages = new Task<HBox>() {
@Override
public HBox call() {
Image image = new Image(getClass().getClassLoader().getResource("images/" + msg.getPicture().toLowerCase() + ".png").toString());
ImageView profileImage = new ImageView(image);
profileImage.setFitHeight(32);
profileImage.setFitWidth(32);
BubbledLabel bl = new BubbledLabel();
bl.setText(msg.getName() + ": " + msg.getMsg());
bl.setBackground(new Background(new BackgroundFill(Color.WHITE,null, null)));
HBox x = new HBox();
bl.setBubbleSpec(BubbleSpec.FACE_LEFT_CENTER);
x.getChildren().addAll(profileImage, bl);
setOnlineLabel(String.valueOf(msg.getOnlineUsers().size()));
return x;
}
};
othersMessages.setOnSucceeded(event ->
chatPane.getItems().add(othersMessages.getValue())
);
//自己发送的信息
Task<HBox> yourMessages = new Task<HBox>() {
@Override
public HBox call() {
Image image = userImageView.getImage();
ImageView profileImage = new ImageView(image);
profileImage.setFitHeight(32);
profileImage.setFitWidth(32);
BubbledLabel bl = new BubbledLabel();
bl.setText(msg.getMsg());
bl.setBackground(new Background(new BackgroundFill(Color.LIGHTGREEN,
null, null)));
HBox x = new HBox();
x.setMaxWidth(chatPane.getWidth() - 20);
x.setAlignment(Pos.TOP_RIGHT);
bl.setBubbleSpec(BubbleSpec.FACE_RIGHT_CENTER);
x.getChildren().addAll(bl, profileImage);
setOnlineLabel(String.valueOf(msg.getOnlineUsers().size()));
return x;
}
};
yourMessages.setOnSucceeded(event -> chatPane.getItems().add(yourMessages.getValue()));
//将任务交给线程池运行
if (msg.getName().equals(usernameLabel.getText())){
//自己发送的消息
ThreadPoolUtil.poolExecutor.execute(yourMessages);
}else {
//别人发送的消息
ThreadPoolUtil.poolExecutor.execute(othersMessages);
}
}
public void setUsernameLabel(String username) {
this.usernameLabel.setText(username);
}
public void setOnlineLabel(String count) {
Platform.runLater(() -> onlineCountLabel.setText(count));
}
public void setUserList(Message msg) {
Platform.runLater(() -> {
ObservableList<User> users = FXCollections.observableList(msg.getOnlineUsers());
userList.setItems(users);
userList.setCellFactory(new CellRenderer());
setOnlineLabel(String.valueOf(msg.getOnlineUsers().size()));
});
}
/**
* 用户提示
* @param msg
* @param picture
* @param title
* @param sound
*/
public void notify(String msg,String picture,String title,String sound) {
Platform.runLater(() -> {
Image profileImg = new Image(getClass().getClassLoader().getResource("images/" + picture +".png").toString(),50,50,false,false);
TrayNotification tray = new TrayNotification();
tray.setTitle(title);
tray.setMessage(msg);
tray.setRectangleFill(Paint.valueOf("#2C3E50"));
tray.setAnimationType(AnimationType.POPUP);
tray.setImage(profileImg);
tray.showAndDismiss(Duration.seconds(5));
try {
Media hit = new Media(getClass().getClassLoader().getResource(sound).toString());
MediaPlayer mediaPlayer = new MediaPlayer(hit);
mediaPlayer.play();
} catch (Exception e) {
e.printStackTrace();
}
});
}
public void sendMethod(KeyEvent event) throws IOException {
if (event.getCode() == KeyCode.ENTER) {
sendButtonAction();
}
}
@FXML
public void closeApplication() {
Platform.exit();
System.exit(0);
}
@Override
public void initialize(URL location, ResourceBundle resources) {
//处理鼠标拖拽界面
new Drag().handleDrag(borderPane);
//处理按下回车事件
messageBox.addEventFilter(KeyEvent.KEY_PRESSED, ke -> {
if (ke.getCode().equals(KeyCode.ENTER)) {
try {
sendButtonAction();
} catch (IOException e) {
e.printStackTrace();
}
ke.consume();
}
});
}
public void setImageLabel(String selectedPicture) {
String path = "";
switch (selectedPicture) {
case "boy":
path = "images/boy.png";
break;
case "girl":
path = "images/girl.png";
break;
case "default":
path = "images/default.png";
break;
}
this.userImageView.setImage(new Image(getClass().getClassLoader().getResource(path).toString()));
}
}
CellRenderer类
package com.bjpowernode.client.chat;
import com.bjpowernode.bean.User;
import javafx.geometry.Pos;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.util.Callback;
/**
* A Class for Rendering users images / name on the userlist.
*/
class CellRenderer implements Callback<ListView<User>, ListCell<User>> {
@Override
public ListCell<User> call(ListView<User> p) {
ListCell<User> cell = new ListCell<User>() {
@Override
protected void updateItem(User user, boolean bln) {
super.updateItem(user, bln);
setGraphic(null);
setText(null);
if (user != null) {
HBox hBox = new HBox();
Text name = new Text(user.getName());
ImageView statusImageView = new ImageView();
ImageView pictureImageView = new ImageView();
Image image = new Image(getClass().getClassLoader().getResource("images/" + user.getPicture().toLowerCase() + ".png").toString(), 50, 50, true, true);
pictureImageView.setImage(image);
hBox.getChildren().addAll(statusImageView, pictureImageView, name);
hBox.setAlignment(Pos.CENTER_LEFT);
setGraphic(hBox);
}
}
};
return cell;
}
}
Listener类
package com.bjpowernode.client.chat;
import com.bjpowernode.bean.Message;
import com.bjpowernode.bean.MessageType;
import com.bjpowernode.client.login.LoginController;
import java.io.*;
import java.net.Socket;
/*
监听客户端和服务器端的消息
*/
public class Listener implements Runnable {
private String hostname;
private int port;
private String username;
private String picture;
private Socket socket;
private ObjectInputStream ois;
private InputStream inputStream;
private ObjectOutputStream oos;
private OutputStream outputStream;
private ChatController chatController;
public static Listener instance;
public Listener(String hostname, int port, String username, String picture,ChatController chatController) {
this.hostname = hostname;
this.port = port;
this.username = username;
this.picture = picture;
this.chatController = chatController;
instance = this;
}
@Override
public void run() {
//获取socket对象
try {
socket = new Socket(hostname, port);
outputStream = socket.getOutputStream();
oos = new ObjectOutputStream(outputStream);
inputStream = socket.getInputStream();
ois = new ObjectInputStream(inputStream);
connect();
while (socket.isConnected()) {
Message message = (Message)ois.readObject();
if (message != null) {
switch (message.getType()) {
case NOTIFICATION:
LoginController.getInstance().showScene();
chatController.notify(message.getName() + "加入聊天",message.getPicture(),"新朋友加入","sounds/Global.wav");
break;
case ERROR:
chatController.notify(message.getMsg(),message.getPicture(),"出问题了","sounds/system.wav");
break;
case JOINED:
case DISCONNECTED:
chatController.setUserList(message);
break;
case TEXT:
chatController.showMsg(message);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*
发送文字消息
*/
public void send(String msg) throws IOException {
Message message = new Message();
message.setName(username);
message.setType(MessageType.TEXT);
message.setMsg(msg);
message.setPicture(picture);
oos.writeObject(message);
oos.flush();
}
/*
连接
*/
public void connect() throws IOException {
Message message = new Message();
message.setName(username);
message.setType(MessageType.JOINED);
message.setPicture(picture);
message.setMsg("已连接");
oos.writeObject(message);
}
}
Drag类
package com.bjpowernode.client.util;
import com.bjpowernode.client.MainLauncher;
import javafx.scene.Cursor;
import javafx.scene.layout.BorderPane;
/**
* 鼠标拖拽界面的处理
*/
public class Drag {
private double xOffset;
private double yOffset;
public void handleDrag(BorderPane borderPane) {
borderPane.setOnMousePressed(event -> {
xOffset = MainLauncher.getPrimaryStage().getX() - event.getScreenX();
yOffset = MainLauncher.getPrimaryStage().getY() - event.getScreenY();
borderPane.setCursor(Cursor.CLOSED_HAND);
});
borderPane.setOnMouseDragged(event -> {
MainLauncher.getPrimaryStage().setX(event.getScreenX() + xOffset);
MainLauncher.getPrimaryStage().setY(event.getScreenY() + yOffset);
});
borderPane.setOnMouseReleased(event -> {
borderPane.setCursor(Cursor.DEFAULT);
});
}
}
ThreadPoolUtil类
package com.bjpowernode.client.util;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolUtil {
public static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 4, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(2));
}
2.后台启动代码
package com.bjpowernode.server;
import com.bjpowernode.bean.User;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Server {
public static HashMap<String, User> userMap = new HashMap<>();
public static HashSet<ObjectOutputStream> writers = new HashSet<>();
//线程池对象
public static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(16, 32, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(16));
public static void main(String[] args) {
try(ServerSocket listener = new ServerSocket(9001)) {
while (true) {
poolExecutor.execute(new Handler(listener.accept()));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Handler.class类
package com.bjpowernode.server;
import com.bjpowernode.bean.Message;
import com.bjpowernode.bean.MessageType;
import com.bjpowernode.bean.User;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;
/*
服务器端的处理器
*/
public class Handler implements Runnable {
private Socket socket;
private ObjectInputStream ois;
private InputStream inputStream;
private ObjectOutputStream oos;
private OutputStream outputStream;
private User user;
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
inputStream = socket.getInputStream();
ois = new ObjectInputStream(inputStream);
outputStream = socket.getOutputStream();
oos = new ObjectOutputStream(outputStream);
//将新加入的用户的输出流对象放入到set中
Server.writers.add(oos);
//获取客户端第一次登录的消息
Message firstMessage = (Message) ois.readObject();
if (!checkDuplicateUsername(firstMessage)) {
return;
}
sendNotification(firstMessage);
showOnlineUser();
while (socket.isConnected()) {
Message message = (Message) ois.readObject();
switch (message.getType()) {
case TEXT:
write(message);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeConnection();
}
}
/*
校验用户昵称是否重复
要同步
*/
private synchronized boolean checkDuplicateUsername(Message message) throws IOException {
if (!Server.userMap.containsKey(message.getName())) {
user = new User();
user.setName(message.getName());
user.setPicture(message.getPicture());
Server.userMap.put(message.getName(), user);
return true;
} else {
message.setMsg("用户名重复");
message.setType(MessageType.ERROR);
//将消息返回给当前登录者
this.oos.writeObject(message);
return false;
}
}
/*
客户端显示加入群聊消息
*/
private void sendNotification(Message msg) throws IOException {
msg.setMsg("加入群聊");
msg.setType(MessageType.NOTIFICATION);
write(msg);
}
/*
向客户端展示当前在线用户
*/
private void showOnlineUser() throws IOException {
Message msg = new Message();
msg.setMsg("欢迎加入聊天");
msg.setName("SERVER");
msg.setType(MessageType.JOINED);
write(msg);
}
/*
向客户端发送消息
*/
private void write(Message msg) throws IOException {
//设置在线用户
msg.setOnlineUsers(new ArrayList<>(Server.userMap.values()));
for (ObjectOutputStream writer : Server.writers) {
writer.writeObject(msg);
}
}
/*
关闭链接
*/
private void closeConnection() {
try {
//从用户map中移除退出的用户
if (user.getName() != null) {
Server.userMap.remove(user.getName());
}
if (oos != null) {
Server.writers.remove(oos);
}
if (inputStream != null) {
inputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
if (ois != null) {
ois.close();
}
if (oos != null) {
oos.close();
}
//通知其他在线用户,从在线用户列表中移除当前用户信息
Message message = new Message();
message.setMsg("离开聊天");
message.setType(MessageType.DISCONNECTED);
message.setName("SERVER");
write(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.启动方法
先启动后台。找到com/bjpowernode/server/Server.java类,点击图中三角号。
让后台运行起来,如图:
后来启动起来的画面,只是多了一个窗口,并没有其他的变化。
接下来启动客户端,找到com/bjpowernode/client/MainLauncher.java类
点击图中三角号,启动客户端。
启动后,进入登录界面: