小白一只,刚看完梁勇老师的《Java语言程序设计后》,写了一个简单的聊天室,采用单服务器-多客户端的方式,能使多个用户同时对话,聊天记录被保存在本地文件中,可从客户端搜索聊天记录。界面用的是 javafx 工具。
我为了方便,将客户端地址设为“localhost”,让服务器和客户端程序都在自己电脑上运行了。
初始运行服务器的结果,显示了服务器开始运行的时间
运行3个客户端后,服务器显示了三个用户进入的时间
客户端左侧为聊天面板 + 消息输入框,右侧为 聊天记录搜索面板 + 聊天记录搜索框
测试几条输入后, 搜索“hello”的聊天记录(前面的编号是聊天记录的序号)
搜索“world”的聊天记录
(由于三个客户端都在自己电脑上运行,所以每条聊天记录被写入文档3遍)
每个客户将消息传给服务器。服务器每收到一条消息,就将该消息发送给当前所有用户。
一个面板显示服务器开始运行的时间,及各用户进入的时间。
一个主线程创建服务器套接字,并不断地监听端口。当用户加入时,为每个用户单独使用一个线程管理(使用线程池),在服务器面板显示用户加入的时间, 并将该用户加入【消息传送列表】。
管理单个用户的线程:不断地监听该用户,如果收到该用户传来的消息,则将该消息发送给【消息传送列表】中的所有用户。如果该用户退出,则将该用户从【消息传送列表】中移除。
“发送”按钮触发器: 点击发送按钮后,如果输入消息非空, 则将该消息发送给服务器,并将消息输入框的清空。
一个线程不停地监听,如果收到从服务器传来的消息,则将该消息打印到聊天面板,并写入本地聊天记录文件。
单独设计聊天记录搜索界面:如果聊天记录搜索框的输入内容非空, 则点击“搜索”按钮后,触发搜索函数,将本地文件中搜索到的聊天记录打印在聊天记录搜索面板。 将这个搜索界面加入到主界面。
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.ScrollPane;
import javafx.stage.Stage;
public class ChatWeEasyServer extends Application {
private static final int NUMBER_OF_THREAD = 40;
private TextArea ta = new TextArea();
private int UserNo = 0;
private int UserNum = 0;
// List of current users
private List currentUsers
= new ArrayList<>();
// Thread pool
private ExecutorService executor
= Executors.newFixedThreadPool(NUMBER_OF_THREAD);
@Override
public void start(Stage primaryStage) {
// UI
ta.setWrapText(true);
ta.setEditable(false);
ta.setPrefRowCount(60);
Scene scene = new Scene(new ScrollPane(ta), 600, 700);
primaryStage.setTitle("ChatWeEasyServer");
primaryStage.setScene(scene);
primaryStage.show();
// Running the Server
new Thread(() -> {
try{
ServerSocket serverSocket = new ServerSocket(8000);
ta.appendText("ChatWeEasySever started at "
+ new Date() + "\n");
// Monitor
while(true){
// A user come in
Socket socket = serverSocket.accept();
UserNo++;
UserNum++;
// Print user's information
Platform.runLater(() -> {
ta.appendText("User " + UserNo + " enters at "
+ new Date() + '\n' );
ta.appendText("Now total users number: "
+ UserNum + "\n\n");
});
// Create a thread for each user coming in
executor.execute(new UserHandler(socket));
}
}
catch(Exception e){
e.printStackTrace();
}
}).start();
}
// Run for users respectively
private class UserHandler implements Runnable {
private Socket socket;
public UserHandler(Socket socket){
this.socket = socket;
}
@Override
public void run() {
DataOutputStream temp = null;
try {
DataOutputStream toUser =
new DataOutputStream(socket.getOutputStream());
DataInputStream fromUser =
new DataInputStream(socket.getInputStream());
temp = toUser;
// Add new user to current users list
addNewUser(toUser);
// Monitor
while(true) {
// Sever send message to every user in current users list
String message = fromUser.readUTF();
sendMessage(message);
}
}
catch (Exception ex) {
ex.printStackTrace();
}
finally{
// Remove the user quit
removeOldUser(temp);
UserNum--;
if(socket != null){
try{
socket.close();
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
}
}
// Manage current users
// Add new user to current users list
private synchronized void addNewUser(DataOutputStream newUser){
currentUsers.add(newUser);
}
// Remove user quit
private synchronized void removeOldUser(DataOutputStream oldUser){
currentUsers.remove(oldUser);
}
// Send message to every user in current users list
private synchronized void sendMessage(String message){
for(DataOutputStream users:currentUsers){
try {
users.writeUTF(message);
}
catch(IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
import java.io.*;
import java.net.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.Button;
public class ChatWeEasy extends Application {
DataOutputStream toServer = null;
DataInputStream fromServer = null;
// Chat area
private TextArea taChat = new TextArea();
// Send area
private TextArea taSend = new TextArea();
// "Send" button
private Button btSend = new Button("Send");
@Override
public void start(Stage primaryStage) {
// UI
taChat.setWrapText(true);
taChat.setEditable(false);
taChat.setPrefColumnCount(60);
taChat.setPrefRowCount(25);
taSend.setWrapText(true);
taSend.setEditable(true);
taSend.setPrefColumnCount(60);
taSend.setPrefRowCount(8);
VBox vBox = new VBox();
vBox.getChildren().add(new ScrollPane(taChat));
vBox.getChildren().add(new ScrollPane(taSend));
vBox.getChildren().add(btSend);
SearchPane searchPane = new SearchPane();
BorderPane pane = new BorderPane();
pane.setCenter(vBox);
pane.setRight(searchPane);
Scene scene = new Scene(pane, 1000, 800);
primaryStage.setTitle("ChatWeEasy");
primaryStage.setScene(scene);
primaryStage.show();
// Connect to Server
try {
Socket socket = new Socket("localhost", 8000);
fromServer = new DataInputStream(
socket.getInputStream());
toServer = new DataOutputStream(
socket.getOutputStream());
}
catch(IOException ex){
ex.printStackTrace();
}
// Trigger: send local message to server
btSend.setOnAction(e -> {
if(!taSend.getText().isEmpty()) {
try {
toServer.writeUTF(taSend.getText());
toServer.flush();
taSend.clear();
}
catch(IOException ex) {
ex.printStackTrace();
}
}
});
// New thread to monitor message from server
new Thread(() -> {
try {
while(true) {
String message = fromServer.readUTF();
Platform.runLater(() -> {
taChat.appendText(message + '\n');
});
ManageRecords.addMessageRecord(message);
}
}
catch(Exception ex) {
ex.printStackTrace();
}
}).start();
}
public static void main(String[] args) {
Application.launch(args);
}
}
import java.util.ArrayList;
import javafx.geometry.HPos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextArea;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
public class SearchPane extends VBox {
//======= Design the SearchPane for Search Message Records =======//
private TextField tfString = new TextField();
private Button btSearch = new Button("Search");
private Button btClear = new Button("Clear");
private TextArea taSearchResult = new TextArea();
public SearchPane() {
// UI
GridPane pane = new GridPane();
pane.add(new Label("String"), 0, 0);
pane.add(tfString, 1, 0);
pane.add(btClear, 0, 1);
GridPane.setHalignment(btClear, HPos.LEFT);
pane.add(btSearch, 1, 1);
GridPane.setHalignment(btSearch, HPos.RIGHT);
taSearchResult.setPrefColumnCount(15);
taSearchResult.setPrefRowCount(30);
taSearchResult.setWrapText(true);
taSearchResult.setEditable(false);
ScrollPane scrollPane = new ScrollPane(taSearchResult);
getChildren().addAll(scrollPane, pane);
// Trigger of "Clear" button
btClear.setOnAction(e -> {
tfString.clear();
});
// Trigger of "Search" button
btSearch.setOnAction(e -> {
search(tfString);
});
}
// Search message records
private void search(TextField tfString) {
String s = "";
ArrayList records = new ArrayList<>();
if(!tfString.getText().isEmpty()) {
// Call function to search message records
records = ManageRecords.searchMessageRecords(tfString.getText());
}
for(int i = 0; i < records.size(); i++)
s += records.get(i)[0] + " : " + records.get(i)[1] + "\r\n";
// Print search result
taSearchResult.setText(s);
}
}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
public class ManageRecords {
//=========== search message records in file ========================//
public static ArrayList searchMessageRecords(String strings) {
ArrayList results = new ArrayList<>();
Integer row = new Integer(0);
File file = new File("message_records.dat");
String s = new String();
try {
try(
FileReader reader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(reader);
){
while((s = bufferedReader.readLine()) != null) {
row++;
String[] stemp = {"", ""};
if(s.toLowerCase().contains(
strings.toLowerCase().trim())) {
stemp[0] += row;
stemp[1] += s;
results.add(stemp);
}
}
}
}
catch(IOException ex) {
ex.printStackTrace();
}
return results;
}
//============= add message records to file =============================//
public static void addMessageRecord(String strings) {
File file = new File("message_records.dat");
if(!(file.exists()) || (!file.isFile())) {
try {
try(
BufferedOutputStream friendsOut =
new BufferedOutputStream(new FileOutputStream(
"message_records.dat"));
){
}
}
catch(IOException ex) {
ex.printStackTrace();
}
}
try {
try(
FileWriter writer = new FileWriter(file,true);
){
writer.write(strings + "\r\n");
writer.flush();
}
}
catch(IOException ex) {
ex.printStackTrace();
}
}
}
实现的聊天室比较原始,聊天记录文件的管理过于简单,搜索的实现也很低效。不过一步步动手实现出来后还是学习到很多,在这里和大家分享,希望大家指正。
我主要是学习了梁勇老师的《Java语言程序设计》(戴开宇老师译)后写的这个程序,其中许多代码也重用了书中的案例。另外,在如何实现服务器向所有用户广播消息的问题上,学习了这篇文章 点击打开链接 ,在此表示感谢。