Java Socket實(shí)現(xiàn)多人聊天系統(tǒng)
本文實(shí)例為大家分享了Java Socket實(shí)現(xiàn)多人聊天系統(tǒng)的具體代碼,供大家參考,具體內(nèi)容如下
前言
開發(fā)環(huán)境:Eclipse Java 2019-06
注意:本項(xiàng)目只在單主機(jī)運(yùn)行調(diào)試過,沒試過在局域網(wǎng)和不同主機(jī)之間接發(fā)消息和文件(估計(jì)不行),有需要的自行查閱資料。
一、多人聊天系統(tǒng)
1.1 客戶端
Login.java:登錄界面
// Login.java package exp5; import java.awt.*; import javax.swing.*; public class Login { JTextField textField = null; JPasswordField pwdField = null; ClientReadAndPrint.LoginListen listener=null; // 構(gòu)造函數(shù) public Login() { init(); } void init() { JFrame jf = new JFrame("登錄"); jf.setBounds(500, 250, 310, 210); jf.setResizable(false); // 設(shè)置是否縮放 JPanel jp1 = new JPanel(); JLabel headJLabel = new JLabel("登錄界面"); headJLabel.setFont(new Font(null, 0, 35)); // 設(shè)置文本的字體類型、樣式 和 大小 jp1.add(headJLabel); JPanel jp2 = new JPanel(); JLabel nameJLabel = new JLabel("用戶名:"); textField = new JTextField(20); JLabel pwdJLabel = new JLabel("密碼: "); pwdField = new JPasswordField(20); JButton loginButton = new JButton("登錄"); JButton registerButton = new JButton("注冊"); // 沒設(shè)置功能 jp2.add(nameJLabel); jp2.add(textField); jp2.add(pwdJLabel); jp2.add(pwdField); jp2.add(loginButton); jp2.add(registerButton); JPanel jp = new JPanel(new BorderLayout()); // BorderLayout布局 jp.add(jp1, BorderLayout.NORTH); jp.add(jp2, BorderLayout.CENTER); // 設(shè)置監(jiān)控 listener = new ClientReadAndPrint().new LoginListen(); // 新建監(jiān)聽類 listener.setJTextField(textField); // 調(diào)用PoliceListen類的方法 listener.setJPasswordField(pwdField); listener.setJFrame(jf); pwdField.addActionListener(listener); // 密碼框添加監(jiān)聽 loginButton.addActionListener(listener); // 按鈕添加監(jiān)聽 jf.add(jp); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 設(shè)置關(guān)閉圖標(biāo)作用 jf.setVisible(true); // 設(shè)置可見 } }
ChatView.java:登錄成功后的個(gè)人聊天界面
// ChatView.java package exp5; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; public class ChatView { String userName; //由客戶端登錄時(shí)設(shè)置 JTextField text; JTextArea textArea; ClientReadAndPrint.ChatViewListen listener; // 構(gòu)造函數(shù) public ChatView(String userName) { this.userName = userName ; init(); } // 初始化函數(shù) void init() { JFrame jf = new JFrame("客戶端"); jf.setBounds(500,200,400,330); //設(shè)置坐標(biāo)和大小 jf.setResizable(false); // 縮放為不能縮放 JPanel jp = new JPanel(); JLabel lable = new JLabel("用戶:" + userName); textArea = new JTextArea("***************登錄成功,歡迎來到多人聊天室!****************\n",12, 35); textArea.setEditable(false); // 設(shè)置為不可修改 JScrollPane scroll = new JScrollPane(textArea); // 設(shè)置滾動(dòng)面板(裝入textArea) scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); // 顯示垂直條 jp.add(lable); jp.add(scroll); text = new JTextField(20); JButton button = new JButton("發(fā)送"); JButton openFileBtn = new JButton("發(fā)送文件"); jp.add(text); jp.add(button); jp.add(openFileBtn); // 設(shè)置“打開文件”監(jiān)聽 openFileBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { showFileOpenDialog(jf); } }); // 設(shè)置“發(fā)送”監(jiān)聽 listener = new ClientReadAndPrint().new ChatViewListen(); listener.setJTextField(text); // 調(diào)用PoliceListen類的方法 listener.setJTextArea(textArea); listener.setChatViewJf(jf); text.addActionListener(listener); // 文本框添加監(jiān)聽 button.addActionListener(listener); // 按鈕添加監(jiān)聽 jf.add(jp); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 設(shè)置右上角關(guān)閉圖標(biāo)的作用 jf.setVisible(true); // 設(shè)置可見 } // “打開文件”調(diào)用函數(shù) void showFileOpenDialog(JFrame parent) { // 創(chuàng)建一個(gè)默認(rèn)的文件選擇器 JFileChooser fileChooser = new JFileChooser(); // 設(shè)置默認(rèn)顯示的文件夾 fileChooser.setCurrentDirectory(new File("C:/Users/Samven/Desktop")); // 添加可用的文件過濾器(FileNameExtensionFilter 的第一個(gè)參數(shù)是描述, 后面是需要過濾的文件擴(kuò)展名) // fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("(txt)", "txt")); // 設(shè)置默認(rèn)使用的文件過濾器(FileNameExtensionFilter 的第一個(gè)參數(shù)是描述, 后面是需要過濾的文件擴(kuò)展名 可變參數(shù)) fileChooser.setFileFilter(new FileNameExtensionFilter("(txt)", "txt")); // 打開文件選擇框(線程將被堵塞,知道選擇框被關(guān)閉) int result = fileChooser.showOpenDialog(parent); // 對話框?qū)?huì)盡量顯示在靠近 parent 的中心 // 點(diǎn)擊確定 if(result == JFileChooser.APPROVE_OPTION) { // 獲取路徑 File file = fileChooser.getSelectedFile(); String path = file.getAbsolutePath(); ClientFileThread.outFileToServer(path); } } }
Client.java:客戶端
// Client.java package exp5; import java.net.*; import javax.swing.*; import java.awt.event.*; import java.io.*; public class Client { // 主函數(shù),新建登錄窗口 public static void main(String[] args) { new Login(); } } /** * 負(fù)責(zé)客戶端的讀和寫,以及登錄和發(fā)送的監(jiān)聽 * 之所以把登錄和發(fā)送的監(jiān)聽放在這里,是因?yàn)橐蚕硪恍?shù)據(jù),比如mySocket,textArea */ class ClientReadAndPrint extends Thread{ static Socket mySocket = null; // 一定要加上static,否則新建線程時(shí)會(huì)清空 static JTextField textInput; static JTextArea textShow; static JFrame chatViewJFrame; static BufferedReader in = null; static PrintWriter out = null; static String userName; // 用于接收從服務(wù)端發(fā)送來的消息 public void run() { try { in = new BufferedReader(new InputStreamReader(mySocket.getInputStream())); // 輸入流 while (true) { String str = in.readLine(); // 獲取服務(wù)端發(fā)送的信息 textShow.append(str + '\n'); // 添加進(jìn)聊天客戶端的文本區(qū)域 textShow.setCaretPosition(textShow.getDocument().getLength()); // 設(shè)置滾動(dòng)條在最下面 } } catch (Exception e) {} } /**********************登錄監(jiān)聽(內(nèi)部類)**********************/ class LoginListen implements ActionListener{ JTextField textField; JPasswordField pwdField; JFrame loginJFrame; // 登錄窗口本身 ChatView chatView = null; public void setJTextField(JTextField textField) { this.textField = textField; } public void setJPasswordField(JPasswordField pwdField) { this.pwdField = pwdField; } public void setJFrame(JFrame jFrame) { this.loginJFrame = jFrame; } public void actionPerformed(ActionEvent event) { userName = textField.getText(); String userPwd = String.valueOf(pwdField.getPassword()); // getPassword方法獲得char數(shù)組 if(userName.length() >= 1 && userPwd.equals("123")) { // 密碼為123并且用戶名長度大于等于1 chatView = new ChatView(userName); // 新建聊天窗口,設(shè)置聊天窗口的用戶名(靜態(tài)) // 建立和服務(wù)器的聯(lián)系 try { InetAddress addr = InetAddress.getByName(null); // 獲取主機(jī)地址 mySocket = new Socket(addr,8081); // 客戶端套接字 loginJFrame.setVisible(false); // 隱藏登錄窗口 out = new PrintWriter(mySocket.getOutputStream()); // 輸出流 out.println("用戶【" + userName + "】進(jìn)入聊天室!"); // 發(fā)送用戶名給服務(wù)器 out.flush(); // 清空緩沖區(qū)out中的數(shù)據(jù) } catch (IOException e) { e.printStackTrace(); } // 新建普通讀寫線程并啟動(dòng) ClientReadAndPrint readAndPrint = new ClientReadAndPrint(); readAndPrint.start(); // 新建文件讀寫線程并啟動(dòng) ClientFileThread fileThread = new ClientFileThread(userName, chatViewJFrame, out); fileThread.start(); } else { JOptionPane.showMessageDialog(loginJFrame, "賬號(hào)或密碼錯(cuò)誤,請重新輸入!", "提示", JOptionPane.WARNING_MESSAGE); } } } /**********************聊天界面監(jiān)聽(內(nèi)部類)**********************/ class ChatViewListen implements ActionListener{ public void setJTextField(JTextField text) { textInput = text; // 放在外部類,因?yàn)槠渌胤揭惨玫? } public void setJTextArea(JTextArea textArea) { textShow = textArea; // 放在外部類,因?yàn)槠渌胤揭惨玫? } public void setChatViewJf(JFrame jFrame) { chatViewJFrame = jFrame; // 放在外部類,因?yàn)槠渌胤揭惨玫? // 設(shè)置關(guān)閉聊天界面的監(jiān)聽 chatViewJFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { out.println("用戶【" + userName + "】離開聊天室!"); out.flush(); System.exit(0); } }); } // 監(jiān)聽執(zhí)行函數(shù) public void actionPerformed(ActionEvent event) { try { String str = textInput.getText(); // 文本框內(nèi)容為空 if("".equals(str)) { textInput.grabFocus(); // 設(shè)置焦點(diǎn)(可行) // 彈出消息對話框(警告消息) JOptionPane.showMessageDialog(chatViewJFrame, "輸入為空,請重新輸入!", "提示", JOptionPane.WARNING_MESSAGE); return; } out.println(userName + "說:" + str); // 輸出給服務(wù)端 out.flush(); // 清空緩沖區(qū)out中的數(shù)據(jù) textInput.setText(""); // 清空文本框 textInput.grabFocus(); // 設(shè)置焦點(diǎn)(可行) // textInput.requestFocus(true); // 設(shè)置焦點(diǎn)(可行) } catch (Exception e) {} } } }
ClientFileThread.java:文件傳輸功能(客戶端)
// ClientFileThread.java package exp5; import java.io.*; import java.net.*; import javax.swing.*; public class ClientFileThread extends Thread{ private Socket socket = null; private JFrame chatViewJFrame = null; static String userName = null; static PrintWriter out = null; // 普通消息的發(fā)送(Server.java傳來的值) static DataInputStream fileIn = null; static DataOutputStream fileOut = null; static DataInputStream fileReader = null; static DataOutputStream fileWriter = null; public ClientFileThread(String userName, JFrame chatViewJFrame, PrintWriter out) { ClientFileThread.userName = userName; this.chatViewJFrame = chatViewJFrame; ClientFileThread.out = out; } // 客戶端接收文件 public void run() { try { InetAddress addr = InetAddress.getByName(null); // 獲取主機(jī)地址 socket = new Socket(addr, 8090); // 客戶端套接字 fileIn = new DataInputStream(socket.getInputStream()); // 輸入流 fileOut = new DataOutputStream(socket.getOutputStream()); // 輸出流 // 接收文件 while(true) { String textName = fileIn.readUTF(); long totleLength = fileIn.readLong(); int result = JOptionPane.showConfirmDialog(chatViewJFrame, "是否接受?", "提示", JOptionPane.YES_NO_OPTION); int length = -1; byte[] buff = new byte[1024]; long curLength = 0; // 提示框選擇結(jié)果,0為確定,1位取消 if(result == 0){ // out.println("【" + userName + "選擇了接收文件!】"); // out.flush(); File userFile = new File("C:\\Users\\Samven\\Desktop\\接受文件\\" + userName); if(!userFile.exists()) { // 新建當(dāng)前用戶的文件夾 userFile.mkdir(); } File file = new File("C:\\Users\\Samven\\Desktop\\接受文件\\" + userName + "\\"+ textName); fileWriter = new DataOutputStream(new FileOutputStream(file)); while((length = fileIn.read(buff)) > 0) { // 把文件寫進(jìn)本地 fileWriter.write(buff, 0, length); fileWriter.flush(); curLength += length; // out.println("【接收進(jìn)度:" + curLength/totleLength*100 + "%】"); // out.flush(); if(curLength == totleLength) { // 強(qiáng)制結(jié)束 break; } } out.println("【" + userName + "接收了文件!】"); out.flush(); // 提示文件存放地址 JOptionPane.showMessageDialog(chatViewJFrame, "文件存放地址:\n" + "C:\\Users\\Samven\\Desktop\\接受文件\\" + userName + "\\" + textName, "提示", JOptionPane.INFORMATION_MESSAGE); } else { // 不接受文件 while((length = fileIn.read(buff)) > 0) { curLength += length; if(curLength == totleLength) { // 強(qiáng)制結(jié)束 break; } } } fileWriter.close(); } } catch (Exception e) {} } // 客戶端發(fā)送文件 static void outFileToServer(String path) { try { File file = new File(path); fileReader = new DataInputStream(new FileInputStream(file)); fileOut.writeUTF(file.getName()); // 發(fā)送文件名字 fileOut.flush(); fileOut.writeLong(file.length()); // 發(fā)送文件長度 fileOut.flush(); int length = -1; byte[] buff = new byte[1024]; while ((length = fileReader.read(buff)) > 0) { // 發(fā)送內(nèi)容 fileOut.write(buff, 0, length); fileOut.flush(); } out.println("【" + userName + "已成功發(fā)送文件!】"); out.flush(); } catch (Exception e) {} } }
1.2 服務(wù)器端
MultiChat.java:多人聊天系統(tǒng)界面(服務(wù)器端)
// MultiChat.java package exp5; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.*; public class MultiChat { JTextArea textArea; // 用于向文本區(qū)域添加信息 void setTextArea(String str) { textArea.append(str+'\n'); textArea.setCaretPosition(textArea.getDocument().getLength()); // 設(shè)置滾動(dòng)條在最下面 } // 構(gòu)造函數(shù) public MultiChat() { init(); } void init() { JFrame jf = new JFrame("服務(wù)器端"); jf.setBounds(500,100,450,500); // 設(shè)置窗口坐標(biāo)和大小 jf.setResizable(false); // 設(shè)置為不可縮放 JPanel jp = new JPanel(); // 新建容器 JLabel lable = new JLabel("==歡迎來到多人聊天系統(tǒng)(服務(wù)器端)=="); textArea = new JTextArea(23, 38); // 新建文本區(qū)域并設(shè)置長寬 textArea.setEditable(false); // 設(shè)置為不可修改 JScrollPane scroll = new JScrollPane(textArea); // 設(shè)置滾動(dòng)面板(裝入textArea) scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); // 顯示垂直條 jp.add(lable); jp.add(scroll); jf.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); jf.add(jp); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 設(shè)置關(guān)閉圖標(biāo)作用 jf.setVisible(true); // 設(shè)置可見 } }
Server.java:服務(wù)器端
// Server.java package exp5; import java.io.*; import java.net.*; import java.util.*; public class Server{ static ServerSocket server = null; static Socket socket = null; static List<Socket> list = new ArrayList<Socket>(); // 存儲(chǔ)客戶端 public static void main(String[] args) { MultiChat multiChat = new MultiChat(); // 新建聊天系統(tǒng)界面 try { // 在服務(wù)器端對客戶端開啟文件傳輸?shù)木€程 ServerFileThread serverFileThread = new ServerFileThread(); serverFileThread.start(); server = new ServerSocket(8081); // 服務(wù)器端套接字(只能建立一次) // 等待連接并開啟相應(yīng)線程 while (true) { socket = server.accept(); // 等待連接 list.add(socket); // 添加當(dāng)前客戶端到列表 // 在服務(wù)器端對客戶端開啟相應(yīng)的線程 ServerReadAndPrint readAndPrint = new ServerReadAndPrint(socket, multiChat); readAndPrint.start(); } } catch (IOException e1) { e1.printStackTrace(); // 出現(xiàn)異常則打印出異常的位置 } } } /** * 服務(wù)器端讀寫類線程 * 用于服務(wù)器端讀取客戶端的信息,并把信息發(fā)送給所有客戶端 */ class ServerReadAndPrint extends Thread{ Socket nowSocket = null; MultiChat multiChat = null; BufferedReader in =null; PrintWriter out = null; // 構(gòu)造函數(shù) public ServerReadAndPrint(Socket s, MultiChat multiChat) { this.multiChat = multiChat; // 獲取多人聊天系統(tǒng)界面 this.nowSocket = s; // 獲取當(dāng)前客戶端 } public void run() { try { in = new BufferedReader(new InputStreamReader(nowSocket.getInputStream())); // 輸入流 // 獲取客戶端信息并把信息發(fā)送給所有客戶端 while (true) { String str = in.readLine(); // 發(fā)送給所有客戶端 for(Socket socket: Server.list) { out = new PrintWriter(socket.getOutputStream()); // 對每個(gè)客戶端新建相應(yīng)的socket套接字 if(socket == nowSocket) { // 發(fā)送給當(dāng)前客戶端 out.println("(你)" + str); } else { // 發(fā)送給其它客戶端 out.println(str); } out.flush(); // 清空out中的緩存 } // 調(diào)用自定義函數(shù)輸出到圖形界面 multiChat.setTextArea(str); } } catch (Exception e) { Server.list.remove(nowSocket); // 線程關(guān)閉,移除相應(yīng)套接字 } } }
ServerFileThread.java:文件傳輸功能(服務(wù)器端)
// ServerFileThread.java package exp5; import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.List; public class ServerFileThread extends Thread{ ServerSocket server = null; Socket socket = null; static List<Socket> list = new ArrayList<Socket>(); // 存儲(chǔ)客戶端 public void run() { try { server = new ServerSocket(8090); while(true) { socket = server.accept(); list.add(socket); // 開啟文件傳輸線程 FileReadAndWrite fileReadAndWrite = new FileReadAndWrite(socket); fileReadAndWrite.start(); } } catch (IOException e) { e.printStackTrace(); } } } class FileReadAndWrite extends Thread { private Socket nowSocket = null; private DataInputStream input = null; private DataOutputStream output = null; public FileReadAndWrite(Socket socket) { this.nowSocket = socket; } public void run() { try { input = new DataInputStream(nowSocket.getInputStream()); // 輸入流 while (true) { // 獲取文件名字和文件長度 String textName = input.readUTF(); long textLength = input.readLong(); // 發(fā)送文件名字和文件長度給所有客戶端 for(Socket socket: ServerFileThread.list) { output = new DataOutputStream(socket.getOutputStream()); // 輸出流 if(socket != nowSocket) { // 發(fā)送給其它客戶端 output.writeUTF(textName); output.flush(); output.writeLong(textLength); output.flush(); } } // 發(fā)送文件內(nèi)容 int length = -1; long curLength = 0; byte[] buff = new byte[1024]; while ((length = input.read(buff)) > 0) { curLength += length; for(Socket socket: ServerFileThread.list) { output = new DataOutputStream(socket.getOutputStream()); // 輸出流 if(socket != nowSocket) { // 發(fā)送給其它客戶端 output.write(buff, 0, length); output.flush(); } } if(curLength == textLength) { // 強(qiáng)制退出 break; } } } } catch (Exception e) { ServerFileThread.list.remove(nowSocket); // 線程關(guān)閉,移除相應(yīng)套接字 } } }
二、運(yùn)行效果
2.1 初始化
服務(wù)器端(先運(yùn)行Server.java)
登錄界面(接著運(yùn)行Client.java,運(yùn)行一次生成一個(gè)登錄界面)
這里我還沒有實(shí)現(xiàn)注冊功能,登錄的用戶名隨意(不為空即可),密碼是123。
2.2 登錄成功
2.3 發(fā)送信息
2.4 發(fā)送文件
打開文件我設(shè)置了默認(rèn)路徑是在桌面。接受文件需要先在桌面創(chuàng)建一個(gè)名為“接受文件”的文件夾,用于存放所有用戶接收的文件。
如果出現(xiàn)無法發(fā)送文件,應(yīng)該是ClientFileThread.java那里的路徑問題,路徑包括了電腦用戶的名字,比如我的是“Samven”,可以試試修改為自己的真實(shí)路徑。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- 基于Java實(shí)現(xiàn)互聯(lián)網(wǎng)實(shí)時(shí)聊天系統(tǒng)(附源碼)
- Java?NIO實(shí)現(xiàn)聊天系統(tǒng)
- 基于Java網(wǎng)絡(luò)編程和多線程的多對多聊天系統(tǒng)
- Java 網(wǎng)絡(luò)編程之 TCP 實(shí)現(xiàn)簡單的聊天系統(tǒng)
- Java網(wǎng)絡(luò)編程UDP實(shí)現(xiàn)多線程在線聊天
- Java網(wǎng)絡(luò)編程實(shí)例——簡單模擬在線聊天
- Java使用TCP實(shí)現(xiàn)在線聊天的示例代碼
- Java GUI編程實(shí)現(xiàn)在線聊天室
- Java中使用websocket實(shí)現(xiàn)在線聊天功能
- java實(shí)現(xiàn)在線聊天系統(tǒng)
相關(guān)文章
Java入門基礎(chǔ)之Java的基本語法與Java所支持的數(shù)據(jù)類型
這篇文章主要介紹了Java入門基礎(chǔ)之Java的基本語法與Java所支持的數(shù)據(jù)類型,熟悉語法和數(shù)據(jù)類型通常是了解一種編程語言的開始,需要的朋友可以參考下2016-02-02jcl與jul?log4j1?log4j2?logback日志系統(tǒng)機(jī)制及集成原理
這篇文章主要介紹了jcl與jul?log4j1?log4j2?logback的集成原理,Apache?Commons-logging?通用日志框架與日志系統(tǒng)的機(jī)制,有需要的朋友可以借鑒參考下2022-03-03SpringBoot 快速實(shí)現(xiàn) api 加密的方法
在項(xiàng)目中,為了保證數(shù)據(jù)的安全,我們常常會(huì)對傳遞的數(shù)據(jù)進(jìn)行加密,常用的加密算法包括對稱加密(AES)和非對稱加密(RSA),本文給大家介紹SpringBoot 快速實(shí)現(xiàn) api 加密,感興趣的朋友一起看看吧2023-10-10kotlin java 混合代碼 maven 打包實(shí)現(xiàn)
這篇文章主要介紹了kotlin java 混合代碼 maven 打包實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03