Java编写一个原始的聊天室(线程、服务器-客户端)

小白一只,刚看完梁勇老师的《Java语言程序设计后》,写了一个简单的聊天室,采用单服务器-多客户端的方式,能使多个用户同时对话,聊天记录被保存在本地文件中,可从客户端搜索聊天记录。界面用的是 javafx 工具。

我为了方便,将客户端地址设为“localhost”,让服务器和客户端程序都在自己电脑上运行了。

显示结果

服务器面板

初始运行服务器的结果,显示了服务器开始运行的时间

运行3个客户端后,服务器显示了三个用户进入的时间

Java编写一个原始的聊天室(线程、服务器-客户端)_第1张图片

 

客户端面板

客户端左侧为聊天面板 + 消息输入框,右侧为 聊天记录搜索面板 + 聊天记录搜索框

Java编写一个原始的聊天室(线程、服务器-客户端)_第2张图片

 

 

Java编写一个原始的聊天室(线程、服务器-客户端)_第3张图片

测试几条输入后, 搜索“hello”的聊天记录(前面的编号是聊天记录的序号)

Java编写一个原始的聊天室(线程、服务器-客户端)_第4张图片

搜索“world”的聊天记录

Java编写一个原始的聊天室(线程、服务器-客户端)_第5张图片

(由于三个客户端都在自己电脑上运行,所以每条聊天记录被写入文档3遍)

 

过程简介

      每个客户将消息传给服务器。服务器每收到一条消息,就将该消息发送给当前所有用户。

服务器设计思路

    一个面板显示服务器开始运行的时间,及各用户进入的时间。 

     一个主线程创建服务器套接字,并不断地监听端口。当用户加入时,为每个用户单独使用一个线程管理(使用线程池),在服务器面板显示用户加入的时间, 并将该用户加入【消息传送列表】。

    管理单个用户的线程:不断地监听该用户,如果收到该用户传来的消息,则将该消息发送给【消息传送列表】中的所有用户。如果该用户退出,则将该用户从【消息传送列表】中移除。

客户端设计思路

    “发送”按钮触发器: 点击发送按钮后,如果输入消息非空, 则将该消息发送给服务器,并将消息输入框的清空。

      一个线程不停地监听,如果收到从服务器传来的消息,则将该消息打印到聊天面板,并写入本地聊天记录文件。

搜索面板设计思路

    单独设计聊天记录搜索界面:如果聊天记录搜索框的输入内容非空, 则点击“搜索”按钮后,触发搜索函数,将本地文件中搜索到的聊天记录打印在聊天记录搜索面板。   将这个搜索界面加入到主界面。

 

Java代码

服务器

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语言程序设计》(戴开宇老师译)后写的这个程序,其中许多代码也重用了书中的案例。另外,在如何实现服务器向所有用户广播消息的问题上,学习了这篇文章 点击打开链接 ,在此表示感谢。

    

你可能感兴趣的:(Java)