前言
最近剛好是期末,碰上Java實訓,藉此將之前寫的在線聊天室搬出來,加上GUI(Swing),當成實訓作品,記錄一下這次實訓的結果。
本篇文章主要敘述的是
① 在線聊天室的代碼結構;
② 將java文件打包成jar,再打包成exe文件;
③ 利用內網穿透技術實現與他人在線聊天。附:在線聊天室實用socket通信,利用的網絡協議是TCP,架構爲C/S模式(Client-Server=>客戶機-服務器)
功能設計
- 總體設計
- 詳細設計
(1)聊天室服務器端
1)設置聊天室服務器的端口號,管理員暱稱,啓動服務器或者關閉服務器。
2)系統消息日誌記錄,管理員可發佈系統消息給各在線用戶。
3)管理員在線與聊天室在線用戶進行羣聊。
4)管理員可對在線用戶列表中指定用戶進行私聊請求,對方同意即可開始私聊。
5)管理員可對在線用戶列表中指定用戶進行踢出聊天室操作,並通知其他人。
(2)聊天室客戶端
1)用戶設置聊天室IP,端口號,用戶暱稱,連接服務器進入聊天室或退出聊天室。
2)系統消息通知,接受服務器端發佈的消息,以及用戶一些操作。
3)用戶可與其他在線用戶進行羣聊。
4)用戶可與指定用戶列表中其他在線用戶進行私聊請求,同意即可開始私聊。
5)用戶可以屏蔽指定用戶列表中的用戶的羣聊發言,屏蔽後即接受不到對方發言, 同時也可以選擇取消屏蔽。
GUI畫面展示
服務器端
- 啓動界面
- 聊天界面
客戶端
- 聊天界面
私聊窗口
- 被私聊者
- 聊天窗口
主要代碼
客戶端與服務器交互通過特定的指令與信息,客戶端發送特定格式的指令和信息,服務器端接受到指令和信息,根據指令處理不同的業務請求,再將結果信息和響應指令發送到客戶端,客戶端根據不同指令將信息呈現到用戶端GUI,或者改變客戶端。
服務器端和用戶端的主類都用到了內部類,因爲畢竟容易獲取主類的變量值,具體的類和方法介紹我就不仔細講了,代碼裏面都有註釋了,不懂看看註釋,肯定不是因爲我懶。
服務器端
- 服務器端主線程用來運行管理員操作的GUI界面
- 子線程運行ServerSocket服務
(1)創建ServerSocket對象,綁定監聽端口。
(2)通過accept()方法監聽客戶端請求
(3)連接建立後,通過輸入流讀取客戶端的數據
(4)通過輸出流,向客戶端迴應信息
- 每有一個新的用戶連接生成,會創建對應的子線程來處理對應用戶端的需求,用戶斷開連接時,該線程也隨之停止。
package top.hcode.chatRoom;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static top.hcode.chatRoom.CloseUtils.close;
/**
* @Author: Himit_ZH
* @Date: 2020/6/4 17:17
* @Description: 聊天室服務器,管理員GUI界面
*/
public class chatServer {
private CopyOnWriteArrayList<Channel> allUserChannel;
/*以下爲窗口參數*/
private JFrame frame;
//頭部參數
private JTextField port_textfield;
private JTextField name_textfield;
private JButton head_connect;
private JButton head_exit;
private int port;
//底部參數
private JTextField text_field;
private JTextField sysText_field;
private JButton foot_send;
private JButton foot_sysSend;
private JButton foot_userClear;
//右邊參數
private JLabel users_label;
private JButton privateChat_button;
private JButton kick_button;
private JList<String> userlist;
private DefaultListModel<String> users_model;
//左邊參數
private JScrollPane sysTextScrollPane;
private JTextPane sysMsgArea;
private JScrollBar sysVertical;
//中間參數
private JScrollPane userTextScrollPane;
private JTextPane userMsgArea;
private JScrollBar userVertical;
//時間格式化工具類
static private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//設置時間
//用戶自增ID
private int userId = 1000;
//服務器管理員名字
private String adminName;
//服務器線程
private ServerSocket serverSocket;
//服務器線程
private Server server;
//管理員的私聊窗口隊列和線程隊列
private HashMap<String, privateChatFrame> adminPrivateQueue;
private HashMap<String, Channel> adminPrivateThread;
public static void main(String[] args) {
new chatServer().init();
}
/**
* @MethodName init GUI初始化,初始化各種監聽事件
* @Params * @param null
* @Return null
* @Since 2020/6/6
*/
public void init() {
allUserChannel = new CopyOnWriteArrayList<>();
adminPrivateQueue = new HashMap<>();
adminPrivateThread = new HashMap<>();
setUIStyle();
frame = new JFrame("Hcode聊天室服務器");
JPanel panel = new JPanel(); /*主要的panel,上層放置連接區,下層放置消息區,
中間是消息面板,左邊是room列表,右邊是當前room的用戶列表*/
JPanel headpanel = new JPanel(); /*上層panel,用於放置連接區域相關的組件*/
JPanel footpanel = new JPanel(); /*下層panel,用於放置發送信息區域的組件*/
JPanel centerpanel = new JPanel(); /*中間panel,用於放置聊天信息*/
JPanel leftpanel = new JPanel(); /*左邊panel,用於放置房間列表和加入按鈕*/
JPanel rightpanel = new JPanel(); /*右邊panel,用於放置房間內人的列表*/
/*最上層的佈局,分中間,東南西北五個部分*/
BorderLayout layout = new BorderLayout();
/*格子布局,主要用來設置西、東、南三個部分的佈局*/
GridBagLayout gridBagLayout = new GridBagLayout();
/*主要設置北部的佈局*/
FlowLayout flowLayout = new FlowLayout();
/*設置初始窗口的一些性質*/
frame.setSize(900, 600);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setContentPane(panel);
frame.setLayout(layout);
/*設置各個部分的panel的佈局和大小*/
headpanel.setLayout(flowLayout);
footpanel.setLayout(gridBagLayout);
leftpanel.setLayout(gridBagLayout);
centerpanel.setLayout(gridBagLayout);
rightpanel.setLayout(gridBagLayout);
//設置面板大小
leftpanel.setPreferredSize(new Dimension(350, 0));
rightpanel.setPreferredSize(new Dimension(155, 0));
footpanel.setPreferredSize(new Dimension(0, 40));
//頭部佈局
port_textfield = new JTextField("8888");
name_textfield = new JTextField("匿名");
port_textfield.setPreferredSize(new Dimension(70, 25));
name_textfield.setPreferredSize(new Dimension(150, 25));
JLabel port_label = new JLabel("端口號:");
JLabel name_label = new JLabel("管理員:");
head_connect = new JButton("啓動");
head_exit = new JButton("關閉");
headpanel.add(port_label);
headpanel.add(port_textfield);
headpanel.add(name_label);
headpanel.add(name_textfield);
headpanel.add(head_connect);
headpanel.add(head_exit);
//底部佈局
foot_send = new JButton("發送聊天信息");
foot_sysSend = new JButton("發送系統消息");
foot_sysSend.setPreferredSize(new Dimension(110, 0));
foot_userClear = new JButton("清空聊天消息");
foot_userClear.setPreferredSize(new Dimension(148, 0));
sysText_field = new JTextField();
sysText_field.setPreferredSize(new Dimension(230, 0));
text_field = new JTextField();
footpanel.add(sysText_field, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(foot_sysSend, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 3), 0, 0));
footpanel.add(text_field, new GridBagConstraints(2, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(foot_send, new GridBagConstraints(3, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(foot_userClear, new GridBagConstraints(4, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
//左邊佈局
JLabel sysMsg_label = new JLabel("系統日誌:");
sysMsgArea = new JTextPane();
sysMsgArea.setEditable(false);
sysTextScrollPane = new JScrollPane();
sysTextScrollPane.setViewportView(sysMsgArea);
sysVertical = new JScrollBar(JScrollBar.VERTICAL);
sysVertical.setAutoscrolls(true);
sysTextScrollPane.setVerticalScrollBar(sysVertical);
leftpanel.add(sysMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
leftpanel.add(sysTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//右邊佈局
users_label = new JLabel("當前連接用戶:0");
privateChat_button = new JButton("私聊");
kick_button = new JButton("踢出");
users_model = new DefaultListModel<>();
userlist = new JList<String>(users_model);
JScrollPane userListPane = new JScrollPane(userlist);
rightpanel.add(users_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(privateChat_button, new GridBagConstraints(0, 1, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(kick_button, new GridBagConstraints(0, 2, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(userListPane, new GridBagConstraints(0, 3, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//中間佈局
JLabel userMsg_label = new JLabel("世界聊天:");
userMsgArea = new JTextPane();
userMsgArea.setEditable(false);
userTextScrollPane = new JScrollPane();
userTextScrollPane.setViewportView(userMsgArea);
userVertical = new JScrollBar(JScrollBar.VERTICAL);
userVertical.setAutoscrolls(true);
userTextScrollPane.setVerticalScrollBar(userVertical);
centerpanel.add(userMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
centerpanel.add(userTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//設置頂層佈局
panel.add(headpanel, "North");
panel.add(footpanel, "South");
panel.add(leftpanel, "West");
panel.add(rightpanel, "East");
panel.add(centerpanel, "Center");
//將按鈕事件全部註冊到監聽器
allActionListener allActionListener = new allActionListener();
//開啓服務
head_connect.addActionListener(allActionListener);
// 管理員發佈消息
foot_send.addActionListener(allActionListener);
//關閉服務器
head_exit.addActionListener(allActionListener);
//清空消息日誌
foot_sysSend.addActionListener(allActionListener);
//清空世界聊天消息
foot_userClear.addActionListener(allActionListener);
//私聊
privateChat_button.addActionListener(allActionListener);
//踢人
kick_button.addActionListener(allActionListener);
//服務器窗口關閉事件
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int option = JOptionPane.showConfirmDialog(frame, "確定關閉服務器界面?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
if (e.getWindow() == frame) {
if (server != null) { //如果已開啓服務,就告訴各個已連接客戶端:服務器已關閉
sendSysMsg("由於服務器關閉,已斷開連接", 3);
}
frame.dispose();
System.exit(0);
} else {
return;
}
} else {
return;
}
}
});
//聊天信息輸入框的監聽回車按鈕事件
text_field.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = text_field.getText();
if (text != null && !text.equals("")) {
sendAdminMsg(text);
text_field.setText("");
insertMessage(userTextScrollPane, userMsgArea, null, "[管理員]" + adminName +" "+df.format(new Date()) , " "+text, userVertical, false);
}
}
}
});
// 系統信息輸入框的回車監控事件
sysText_field.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String sysText = sysText_field.getText(); //獲取輸入框中的內容
if (sysText != null && !sysText.equals("")) {
sendSysMsg(sysText, 2);
sysText_field.setText("");
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "[管理員]" + adminName + ":" + sysText, sysVertical, true);
}
}
}
});
//窗口顯示
frame.setVisible(true);
String name = JOptionPane.showInputDialog("請輸入本聊天室管理員暱稱:");
if (name != null &&!name.equals("")) {
name_textfield.setText(name);
}
}
// //線程鎖,防止多線程爭奪同個id
// public synchronized int getUserId() {
// userId++;
// return userId;
// }
/**
* @MethodName ipCheckPort
* @Params * @param null
* @Description 驗證端口格式是否準確
* @Return
* @Since 2020/6/8
*/
public static boolean ipCheckPort(String text){
return text.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])");
}
/**
* 按鈕監聽內部類
* Function: 全局監聽事件,監聽所有按鈕
*/
private class allActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
switch (cmd) {
case "啓動":
String strport = port_textfield.getText();
if (!ipCheckPort(strport)){
JOptionPane.showMessageDialog(frame, "請使用0~65535的整數作爲端口號!", "失敗", JOptionPane.ERROR_MESSAGE);
break;
}
port = Integer.parseInt(strport);
try {
server = new Server(new ServerSocket(port));
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "服務器開啓成功!", sysVertical, true);
head_connect.setText("已啓動");
head_exit.setText("關閉");
(new Thread(server)).start();
adminName = name_textfield.getText();
name_textfield.setEditable(false);
port_textfield.setEditable(false);
} catch (IOException ee) {
//端口被佔用
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "錯誤:端口號被佔用!", sysVertical, true);
JOptionPane.showMessageDialog(frame, "開啓服務器失敗!端口被佔用,請更換端口號!", "失敗", JOptionPane.ERROR_MESSAGE);
}
break;
case "關閉":
if (server == null) {
JOptionPane.showMessageDialog(frame, "不能關閉,未開啓過服務器!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
sendSysMsg("由於服務器關閉,已斷開連接", 3);
try {
serverSocket.close();
} catch (Exception e1) {
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "錯誤:服務器關閉失敗!", sysVertical, true);
}
head_connect.setText("啓動");
head_exit.setText("已關閉");
port_textfield.setEditable(true);
name_textfield.setEditable(true);
for (Channel channel : allUserChannel) {
channel.release();
}
server = null;
users_model.removeAllElements();
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "服務器已關閉!", sysVertical, true);
JOptionPane.showMessageDialog(frame, "服務器已關閉!");
break;
case "發送系統消息":
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室服務器!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String sysText = sysText_field.getText(); //獲取輸入框中的內容
sendSysMsg(sysText, 2);
sysText_field.setText("");
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()), "[管理員]" + adminName + ":" + sysText, sysVertical, true);
break;
case "發送聊天信息":
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室服務器!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String text = text_field.getText(); //獲取輸入框中的內容
sendAdminMsg(text);
text_field.setText("");
insertMessage(userTextScrollPane, userMsgArea, null, "[管理員]" + adminName +" "+df.format(new Date()) , " "+text, userVertical, false);
break;
case "踢出":
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室服務器!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String selected = null;
try {
selected = userlist.getSelectedValue();
kickUser(selected);
} catch (NullPointerException e1) {
JOptionPane.showMessageDialog(frame, "請點擊選擇需要踢出的用戶", "錯誤", JOptionPane.ERROR_MESSAGE);
}
break;
case "清空系統日誌":
sysMsgArea.setText("");
break;
case "清空聊天消息":
userMsgArea.setText("");
break;
case "私聊":
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室服務器!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String privateSelected = userlist.getSelectedValue();
privateChat(privateSelected);
break;
default:
break;
}
}
}
/**
* @MethodName insertMessage
* @Params * @param null
* @Description 往系統消息文本域或者聊天事件文本域插入固定格式的內容
* @Return
* @Since 2020/6/6
*/
private void insertMessage(JScrollPane scrollPane, JTextPane textPane, String icon_code,
String title, String content, JScrollBar vertical, boolean isSys) {
StyledDocument document = textPane.getStyledDocument(); /*獲取textpane中的文本*/
/*設置標題的屬性*/
Color content_color = null;
if (isSys) {
content_color = Color.RED;
} else {
content_color = Color.GRAY;
}
SimpleAttributeSet title_attr = new SimpleAttributeSet();
StyleConstants.setBold(title_attr, true);
StyleConstants.setForeground(title_attr, Color.BLUE);
/*設置正文的屬性*/
SimpleAttributeSet content_attr = new SimpleAttributeSet();
StyleConstants.setBold(content_attr, false);
StyleConstants.setForeground(content_attr, content_color);
Style style = null;
if (icon_code != null) {
Icon icon = new ImageIcon("icon/" + icon_code + ".png");
style = document.addStyle("icon", null);
StyleConstants.setIcon(style, icon);
}
try {
document.insertString(document.getLength(), title + "\n", title_attr);
if (style != null)
document.insertString(document.getLength(), "\n", style);
else
document.insertString(document.getLength(), content + "\n", content_attr);
} catch (BadLocationException ex) {
System.out.println("Bad location exception");
}
/*設置滑動條到最後*/
textPane.setCaretPosition(textPane.getDocument().getLength());
}
/**
* @MethodName sendSysMsg
* @Params * @param null
* @Description 發佈系統消息
* @Return
* @Since 2020/6/6
*/
private void sendSysMsg(String content, int code) {
if (code == 2) {
String msg = "<time>" + df.format(new Date()) + "</time><sysMsg>" + content + "</sysMsg>";
for (Channel channel : allUserChannel) {
channel.send(formatCodeWithMsg(msg, code));
}
} else if (code == 3) {
for (Channel channel : allUserChannel) {
channel.send(formatCodeWithMsg(content, code));
}
}
}
/**
* @MethodName sendAdminMsg
* @Params * @param null
* @Description 發送管理員聊天
* @Return
* @Since 2020/6/6
*/
private void sendAdminMsg(String content) {
String msg = "<userId>0</userId><userMsg>" + content + "</userMsg><time>" + df.format(new Date()) + "</time>";
for (Channel channel : allUserChannel) {
channel.send(formatCodeWithMsg(msg, 1));
}
}
/**
* @MethodName kickUser
* @Params * @param null
* @Description 踢出操作,對選中用戶進行踢出操作,順便向其它用戶說明
* @Return
* @Since 2020/6/6
*/
private void kickUser(String selected) {
String kickedUserName = null;
String kickedUserId = null;
//管理員還在與之私聊不可踢!避免衝突!
for (Channel channel1 : allUserChannel) {
String tmp = "[用戶" + channel1.user.getId() + "]" + channel1.user.getUsername();
if (adminPrivateThread.containsKey(channel1.user.getId().toString())) {
JOptionPane.showMessageDialog(frame, "管理員與該用戶的私聊未結束,無法踢出!", "錯誤", JOptionPane.ERROR_MESSAGE);
return;
}
if(tmp.equals(selected)){
kickedUserName = tmp;
kickedUserId = channel1.user.getId().toString();
}
}
String kickedUserMsg = "對不起,您已被本聊天室管理員踢出!";
String otherUserMsg = "<time>" + df.format(new Date()) + "</time><sysMsg>" +"通知:"+kickedUserName+" 被踢出了聊天室!" + "</sysMsg>";
for (Channel channel2 : allUserChannel) {
String tmp = "[用戶" + channel2.user.getId() + "]" + channel2.user.getUsername();
if (tmp.equals(selected)) {
//告訴對方你被踢了
channel2.send(formatCodeWithMsg(kickedUserMsg, 3));
//服務器端系統記錄
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] " + df.format(new Date()) , tmp+" 被踢出了聊天室", sysVertical, true);
//服務器端界面用戶列表移除對應用戶
users_model.removeElement(selected);
channel2.release();
users_label.setText("當前連接用戶:" + allUserChannel.size());
break;
}else{
//通知每個用戶 此人被踢出聊天室
channel2.send(formatCodeWithMsg(otherUserMsg,2));
channel2.send(formatCodeWithMsg(kickedUserId,5));
}
}
}
/**
* @MethodName privateChat
* @Params * @param null
* @Description 管理員私聊操作
* @Return
* @Since 2020/6/6
*/
private void privateChat(String selected) {
for (Channel channel : allUserChannel) {
String tmp = "[用戶" + channel.user.getId() + "]" + channel.user.getUsername();
if (tmp.equals(selected)) {
if(adminPrivateQueue.containsKey(channel.user.getId().toString())){ //不能重複私聊
JOptionPane.showMessageDialog(frame, "與該用戶私聊窗口已存在,請不要重複私聊!", "錯誤", JOptionPane.ERROR_MESSAGE);
return;
}
String Msg = "<from>[管理員]" + adminName+ "</from><id>0</id>";
channel.send(formatCodeWithMsg(Msg, 9)); //將自己的個人信息發給想要私聊的用戶線程
break;
}
}
}
/**
* @MethodName formatCodeWithMsg
* @Params * @param null
* @Description 消息與命令的格式化
* @Return
* @Since 2020/6/6
*/
private String formatCodeWithMsg(String msg, int code) {
return "<cmd>" + code + "</cmd><msg>" + msg + "</msg>\n";
}
/**
* @ClassName Server
* @Params * @param null
* @Description 服務器開啓TCP接受客戶端信息的線程,內部類實現
* @Return
* @Since 2020/6/6
*/
private class Server implements Runnable {
private Server(ServerSocket socket) {
serverSocket = socket;
}
@Override
public void run() {
while (true) {
Socket client = null;
try {
client = serverSocket.accept();
userId++;
User user = new User("user" + userId, userId, client);
Channel channel = new Channel(user);
allUserChannel.add(channel);
users_label.setText("當前連接用戶:" + allUserChannel.size());
(new Thread(channel)).start();
} catch (IOException e) {
//關閉不處理
break;
}
}
}
}
/**
* @ClassName Channel
* @Params * @param null
* @Description 內部類 啓動一個線程應對一個客戶端的服務
* @Return
* @Since 2020/6/6
*/
protected class Channel implements Runnable {
private DataInputStream dis;
private DataOutputStream dos;
private boolean isRunning;
private User user;
private CopyOnWriteArrayList<Channel> shieldList;
private HashMap<String, Channel> privateQueue;
public Channel(User user) {
try {
this.dis = new DataInputStream(user.getSocket().getInputStream());
this.dos = new DataOutputStream(user.getSocket().getOutputStream());
this.shieldList = new CopyOnWriteArrayList<>();
this.privateQueue = new HashMap<>();
this.isRunning = true;
this.user = user;
} catch (IOException var3) {
System.out.println("聊天室服務器初始化失敗");
this.release();
}
}
public User getUser() {
return user;
}
public CopyOnWriteArrayList<Channel> getShieldList() {
return shieldList;
}
private void parseMsg(String msg) {
String code = null;
String message = null;
if (msg.length() > 0) {
//獲取指令碼
Pattern pattern = Pattern.compile("<cmd>(.*)</cmd>");
Matcher matcher = pattern.matcher(msg);
if (matcher.find()) {
code = matcher.group(1);
}
//獲取消息
pattern = Pattern.compile("<msg>(.*)</msg>");
matcher = pattern.matcher(msg);
if (matcher.find()) {
message = matcher.group(1);
}
switch (code) {
// 該服務器線程對應的客戶端線程新用戶剛加入
case "new":
user.setUsername(message);
if (!users_model.contains("[用戶" + user.getId() + "]" + user.getUsername())) {
users_model.addElement("[用戶" + user.getId() + "]" + user.getUsername());
}
String title = "[系統日誌] " + df.format(new Date());
String content = "[用戶" + user.getId() + "]" + user.getUsername() + " 加入了聊天室";
insertMessage(sysTextScrollPane, sysMsgArea, null, title, content, sysVertical, true);
sendAnyone(formatCodeWithMsg("<username>" + user.getUsername() + "</username><id>" + user.getId() + "</id>", 4), false);
//給當前線程服務的客戶端id
send(formatCodeWithMsg(String.valueOf(user.getId()), 8));
break;
case "exit":
if (users_model.contains("[用戶" + user.getId() + "]" + user.getUsername())) {
users_model.removeElement("[用戶" + user.getId() + "]" + user.getUsername());
}
String logTitle = "[系統日誌] " + df.format(new Date());
String logContent = "[用戶" + user.getId() + "]" + user.getUsername() + " 退出了聊天室";
insertMessage(sysTextScrollPane, sysMsgArea, null, logTitle, logContent, sysVertical, true);
allUserChannel.remove(this);
sendAnyone(formatCodeWithMsg("" + user.getId(), 5), false);
this.release(); //移除用戶,關閉線程。
break;
case "getList":
//給客戶端傳送
send(formatCodeWithMsg(getUsersList(), 7));
break;
case "msg":
String now = df.format(new Date());
//寫入服務端的聊天世界中
insertMessage(userTextScrollPane, userMsgArea, null, "[用戶" + user.getId() + "]" + user.getUsername() + " "+now, " "+message, userVertical, false);
// 將自己說的話發給每個人
sendAnyone(formatCodeWithMsg("<userId>" + user.getId() + "</userId><userMsg>" + message + "</userMsg><time>" + now + "</time>", 1), false);
break;
case "buildPrivateChat":
//建立私聊機制
//如果私聊對象是管理員
if (message.equals("0")){
int option = JOptionPane.showConfirmDialog(frame, "[" + user.getUsername() + "]想與你私聊,是否同意?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) { //同意私聊
String agreeMsg = "<result>1</result><from>" + adminName+ "</from><id>0</id>";
send(formatCodeWithMsg(agreeMsg, 10));
privateChatFrame privateChatFrame = new privateChatFrame("與[" + user.getUsername() + "]的私聊窗口", user.getUsername(), user.getId().toString());
adminPrivateQueue.put(user.getId().toString(),privateChatFrame);
adminPrivateThread.put(user.getId().toString(), this);
}else{ //拒絕私聊
String refuseMsg = "<result>0</result><from>" + adminName + "</from><id>0</id>";
send(formatCodeWithMsg(refuseMsg, 10));
}
}else { //普通用戶私聊對象
for (Channel channel : allUserChannel) {
if (channel.getUser().getId() == Integer.parseInt(message)) {
String Msg = "<from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";
this.privateQueue.put(message, channel); //先將對方放入私聊隊列
channel.privateQueue.put(user.getId().toString(), this); //對方也將你放入私聊隊列
channel.send(formatCodeWithMsg(Msg, 9)); //將自己的個人信息發給想要私聊的用戶線程
break;
}
}
}
break;
case "agreePrivateChar": //同意與此ID的人進行私聊
if (message.equals("0")){ //如果對方是管理員
privateChatFrame privateChatFrame = new privateChatFrame("與[" + user.getUsername() + "]的私聊窗口", user.getUsername(), user.getId().toString());
adminPrivateQueue.put(user.getId().toString(),privateChatFrame);
adminPrivateThread.put(user.getId().toString(), this);
}else { //普通用戶
String agreeMsg = "<result>1</result><from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";
privateQueue.get(message).send(formatCodeWithMsg(agreeMsg, 10));
}
break;
case "refusePrivateChar"://拒絕與此ID的人進行私聊,從私聊隊列移除
if(message.equals("0")){ //如果是管理員
JOptionPane.showMessageDialog(frame, "[" + user.getUsername() + "]拒絕了你的私聊請求", "失敗", JOptionPane.ERROR_MESSAGE);
}else {
String refuseMsg = "<result>0</result><from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";
privateQueue.get(message).send(formatCodeWithMsg(refuseMsg, 10));
privateQueue.get(message).privateQueue.remove(user.getId()); //對方也移除
privateQueue.remove(message); //移除對方
}
break;
case "privateMsg": //轉發私聊消息
Pattern privatePattern = Pattern.compile("<msg>(.*)</msg><id>(.*)</id>");
Matcher privateMatcher = privatePattern.matcher(message);
if (privateMatcher.find()) {
String toPrivateMsg = privateMatcher.group(1);
String toPrivateId = privateMatcher.group(2);
if (toPrivateId.equals("0")){ //想要發給管理員
//管理員主線程獲取當前服務線程對應的私聊窗口
privateChatFrame nowPrivateChat = adminPrivateQueue.get(user.getId().toString());
insertMessage(nowPrivateChat.textScrollPane,nowPrivateChat.msgArea,null, df.format(new Date()) + " 對方說:", " "+toPrivateMsg, nowPrivateChat.vertical, false);
}else {
String resultMsg = "<msg>" + toPrivateMsg + "</msg><id>" + user.getId() + "</id>";
//根據信息來源ID(想要轉發的用戶ID)找到對應線程將自己的id和信息發給他
privateQueue.get(toPrivateId).send(formatCodeWithMsg(resultMsg, 11));
}
}
break;
case "privateExit":
if (message.equals("0")){ //如果是管理員
JOptionPane.showMessageDialog(frame, "由於對方結束了私聊,該私聊窗口即將關閉!", "提示", JOptionPane.WARNING_MESSAGE);
adminPrivateQueue.get(user.getId().toString()).dispose();
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), "由於[" + user.getUsername() + "]關閉了私聊窗口,私聊結束!", sysVertical, true);
adminPrivateQueue.remove(user.getId().toString()); //移除此私聊對話窗口
adminPrivateThread.remove(user.getId().toString()); //移除此私聊對話窗口線程
}else {//普通用戶私聊
String endMsg = "<id>" + user.getId() + "</id>";
privateQueue.get(message).send(formatCodeWithMsg(endMsg, 12));
privateQueue.get(message).privateQueue.remove(this.user.getId().toString()); //對方也移除
privateQueue.remove(message); //移除對方
}
break;
case "shield": //將傳來的id對應的服務線程加入到屏蔽列表裏面
for (Channel channel : allUserChannel) {
if (channel.getUser().getId() == Integer.parseInt(message)) {
if (!shieldList.contains(channel)) {
shieldList.add(channel);
}
break;
}
}
System.out.println("屏蔽時:"+shieldList);
break;
case "unshield": //將傳來的id對應的服務線程從屏蔽列表裏面刪除
for (Channel channel : allUserChannel) {
if (channel.getUser().getId() == Integer.parseInt(message)) {
if (shieldList.contains(channel)) {
shieldList.remove(channel);
}
break;
}
}
System.out.println("取消屏蔽時:"+shieldList);
break;
case "setName":
// 改了暱稱,跟其它客戶端的用戶列表進行更正
users_model.removeElement(user.getId() + "#@" + user.getUsername());
user.setUsername(message);
users_model.addElement(user.getId() + "#@" + user.getUsername());
sendAnyone(formatCodeWithMsg("<id>" + user.getId() + "</id><username>" + message + "</username>", 6), false);
break;
default:
System.out.println("not valid message from user" + user.getId());
break;
}
}
}
private String getUsersList() {
StringBuffer stringBuffer = new StringBuffer();
/*獲得房間中所有的用戶的列表,然後構造成一定的格式發送回去*/
stringBuffer.append("<user><id>0</id><username>" + adminName + "</username></user>");
for (Channel each : allUserChannel) {
stringBuffer.append("<user><id>" + each.getUser().getId() +
"</id><username>" + each.getUser().getUsername() + "</username></user>");
}
return stringBuffer.toString();
}
private String receive() {
String msg = "";
try {
msg = this.dis.readUTF();
} catch (IOException var3) {
this.release();
}
return msg;
}
public void send(String msg) {
try {
this.dos.writeUTF(msg);
this.dos.flush();
} catch (IOException var3) {
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統日誌] "+df.format(new Date()), "用戶[" + user.getUsername() + "]的服務器端服務線程出錯,請重啓服務器!", sysVertical, true);
this.release();
}
}
private void sendAnyone(String msg, boolean isSys) {
for (Channel userChannel : allUserChannel) {
//獲取每個用戶的線程類
if (!userChannel.getShieldList().contains(this)) {//當前服務線程不在對方線程屏蔽組內的才發送信息
userChannel.send(msg);
}
}
}
//釋放資源
public void release() {
this.isRunning = false;
close(dis, dos, this.user.getSocket());
// 列表中移除用戶
users_model.removeElement(user.getId() + "#@" + user.getUsername());
if (allUserChannel.contains(this)) {
allUserChannel.remove(this);
}
users_label.setText("當前連接用戶:" + allUserChannel.size());
}
@Override
public void run() {
while (isRunning) {
String msg = receive();
if (!msg.equals("")) {
parseMsg(msg);
}
}
}
}
/**
* @ClassName privateChatFrame
* @Params * @param null
* @Description 管理員所屬的私聊窗口內部類
* @Return
* @Since 2020/6/6
*/
private class privateChatFrame extends JFrame {
private String otherName;
private String otherId;
private JButton sendButton;
private JTextField msgTestField;
private JTextPane msgArea;
private JScrollPane textScrollPane;
private JScrollBar vertical;
public privateChatFrame(String title, String otherName, String otherId) throws HeadlessException {
super(title);
this.otherName = otherName;
this.otherId = otherId;
//全局面板容器
JPanel panel = new JPanel();
//全局佈局
BorderLayout layout = new BorderLayout();
JPanel headpanel = new JPanel(); //上層panel,
JPanel footpanel = new JPanel(); //下層panel
JPanel centerpanel = new JPanel(); //中間panel
//頭部佈局
FlowLayout flowLayout = new FlowLayout();
//底部佈局
GridBagLayout gridBagLayout = new GridBagLayout();
setSize(600, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
//窗口關閉事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int option = JOptionPane.showConfirmDialog(e.getOppositeWindow(), "確定結束私聊?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
if (server != null) {
//關閉當前私聊連接
String endMsg = "<id>0</id>";
adminPrivateThread.get(otherId).send(formatCodeWithMsg(endMsg, 12)); //關閉當前私聊連接
adminPrivateQueue.remove(otherId); //移除此私聊對話窗口
adminPrivateThread.remove(otherId);
}
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), "您與[" + otherName + "]的私聊結束", sysVertical, true);
dispose();
} else {
return;
}
}
});
setContentPane(panel);
setLayout(layout);
headpanel.setLayout(flowLayout);
footpanel.setLayout(gridBagLayout);
footpanel.setPreferredSize(new Dimension(0, 40));
centerpanel.setLayout(gridBagLayout);
//添加頭部部件
JLabel Name = new JLabel(otherName);
headpanel.add(Name);
//設置底部佈局
sendButton = new JButton("發送");
sendButton.setPreferredSize(new Dimension(40, 0));
msgTestField = new JTextField();
footpanel.add(msgTestField, new GridBagConstraints(0, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(sendButton, new GridBagConstraints(1, 0, 1, 1, 10, 10,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
//中間佈局
msgArea = new JTextPane();
msgArea.setEditable(false);
textScrollPane = new JScrollPane();
textScrollPane.setViewportView(msgArea);
vertical = new JScrollBar(JScrollBar.VERTICAL);
vertical.setAutoscrolls(true);
textScrollPane.setVerticalScrollBar(vertical);
centerpanel.add(textScrollPane, new GridBagConstraints(0, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//設置頂層佈局
panel.add(headpanel, "North");
panel.add(footpanel, "South");
panel.add(centerpanel, "Center");
//聊天信息輸入框的監聽回車按鈕事件
msgTestField.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = msgTestField.getText();
if (text != null && !text.equals("")) {
String resultMsg = "<msg>"+text+"</msg><id>0</id>";
adminPrivateThread.get(otherId).send(formatCodeWithMsg(resultMsg,11));
msgTestField.setText("");
insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你說:", " "+text, vertical, false);
}
}
}
});
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("發送")) {
if (server == null) {
JOptionPane.showMessageDialog(frame, "請先開啓聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = msgTestField.getText();
if (text != null && !text.equals("")) {
String resultMsg = "<msg>"+text+"</msg><id>0</id>";
adminPrivateThread.get(otherId).send(formatCodeWithMsg(resultMsg,11));
msgTestField.setText("");
insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你說:", " "+text, vertical, false);
}
}
}
});
//窗口顯示
setVisible(true);
}
}
/**
* @MethodName setUIStyle
* @Params * @param null
* @Description 根據操作系統自動變化GUI界面風格
* @Return
* @Since 2020/6/6
*/
public static void setUIStyle() {
// String lookAndFeel = UIManager.getSystemLookAndFeelClassName(); //設置當前系統風格
String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); //可跨系統
try {
UIManager.setLookAndFeel(lookAndFeel);
UIManager.put("Menu.font", new Font("宋體", Font.PLAIN, 12));
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客戶端
- 主線程運行用戶端的GUI界面,發送用戶的需求指令和信息給服務器端
- 創建一個子線程receive來接受服務器端發來指令和信息。
package top.hcode.chatRoom;
import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static top.hcode.chatRoom.CloseUtils.close;
/**
1. @Author: Himit_ZH
2. @Date: 2020/6/4 14:55
3. @Description: 聊天室客戶端GUI
*/
public class Client {
private JFrame frame;
//頭部參數
private JTextField host_textfield;
private JTextField port_textfield;
private JTextField name_textfield;
private JButton head_connect;
private JButton head_exit;
//底部參數
private JTextField text_field;
private JButton foot_send;
private JButton foot_sysClear;
private JButton foot_userClear;
//右邊參數
private JLabel users_label;
private JButton privateChat_button;
private JButton shield_button;
private JButton unshield_button;
private JList<String> userlist;
private DefaultListModel<String> users_model;
private HashMap<String, Integer> users_map;
//左邊參數
private JScrollPane sysTextScrollPane;
private JTextPane sysMsgArea;
private JScrollBar sysVertical;
//中間參數
private JScrollPane userTextScrollPane;
private JTextPane userMsgArea;
private JScrollBar userVertical;
//發送和接受參數
private DataOutputStream dos;
private Receive receive;
private Socket charClient;
//時間格式化工具類
static private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//設置時間
//當前用戶的id
private int id;
//私聊窗口Map
private HashMap<String, privateChatFrame> privateChatFrameMap;
public static void main(String[] args) {
new Client().init();
}
/**
* @MethodName init
* @Params * @param null
* @Description 客戶端GUI界面初始化,各種監聽事件綁定
* @Return
* @Since 2020/6/6
*/
public void init() {
users_map = new HashMap<>();
privateChatFrameMap = new HashMap<>();
/*設置窗口的UI風格和字體*/
setUIStyle();
frame = new JFrame("Hcode聊天室用戶端");
JPanel panel = new JPanel(); /*主要的panel,上層放置連接區,下層放置消息區,中間是消息面板,左邊是系統消息,右邊是當前room的用戶列表*/
JPanel headpanel = new JPanel(); /*上層panel,用於放置連接區域相關的組件*/
JPanel footpanel = new JPanel(); /*下層panel,用於放置發送信息區域的組件*/
JPanel centerpanel = new JPanel(); /*中間panel,用於放置聊天信息*/
JPanel leftpanel = new JPanel(); /*左邊panel,用於放置房間列表和加入按鈕*/
JPanel rightpanel = new JPanel(); /*右邊panel,用於放置房間內人的列表*/
/*頂層的佈局,分中間,東南西北五個部分*/
BorderLayout layout = new BorderLayout();
/*格子布局,主要用來設置西、東、南三個部分的佈局*/
GridBagLayout gridBagLayout = new GridBagLayout();
/*主要設置北部的佈局*/
FlowLayout flowLayout = new FlowLayout();
/*設置初始窗口的一些性質*/
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setContentPane(panel);
frame.setLayout(layout);
/*設置各個部分的panel的佈局和大小*/
headpanel.setLayout(flowLayout);
footpanel.setLayout(gridBagLayout);
leftpanel.setLayout(gridBagLayout);
centerpanel.setLayout(gridBagLayout);
rightpanel.setLayout(gridBagLayout);
//設置面板大小
leftpanel.setPreferredSize(new Dimension(200, 0));
rightpanel.setPreferredSize(new Dimension(155, 0));
footpanel.setPreferredSize(new Dimension(0, 40));
//頭部佈局
host_textfield = new JTextField("127.0.0.1");
port_textfield = new JTextField("8888");
name_textfield = new JTextField("匿名");
host_textfield.setPreferredSize(new Dimension(100, 25));
port_textfield.setPreferredSize(new Dimension(70, 25));
name_textfield.setPreferredSize(new Dimension(150, 25));
JLabel host_label = new JLabel("服務器IP:");
JLabel port_label = new JLabel("端口:");
JLabel name_label = new JLabel("暱稱:");
head_connect = new JButton("連接");
head_exit = new JButton("退出");
headpanel.add(host_label);
headpanel.add(host_textfield);
headpanel.add(port_label);
headpanel.add(port_textfield);
headpanel.add(name_label);
headpanel.add(name_textfield);
headpanel.add(head_connect);
headpanel.add(head_exit);
//底部佈局
foot_send = new JButton("發送");
foot_sysClear = new JButton("清空系統消息");
foot_sysClear.setPreferredSize(new Dimension(193, 0));
foot_userClear = new JButton("清空聊天消息");
foot_userClear.setPreferredSize(new Dimension(148, 0));
text_field = new JTextField();
footpanel.add(foot_sysClear, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(text_field, new GridBagConstraints(1, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(foot_send, new GridBagConstraints(2, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(foot_userClear, new GridBagConstraints(3, 0, 1, 1, 1.0, 1.0,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
//左邊佈局
JLabel sysMsg_label = new JLabel("系統消息:");
sysMsgArea = new JTextPane();
sysMsgArea.setEditable(false);
sysTextScrollPane = new JScrollPane();
sysTextScrollPane.setViewportView(sysMsgArea);
sysVertical = new JScrollBar(JScrollBar.VERTICAL);
sysVertical.setAutoscrolls(true);
sysTextScrollPane.setVerticalScrollBar(sysVertical);
leftpanel.add(sysMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
leftpanel.add(sysTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//右邊佈局
users_model = new DefaultListModel<>();
userlist = new JList<String>(users_model);
JScrollPane userListPane = new JScrollPane(userlist);
users_label = new JLabel("聊天室內人數:0");
privateChat_button = new JButton("私聊");
shield_button = new JButton("屏蔽對方");
unshield_button = new JButton("取消屏蔽");
rightpanel.add(users_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(privateChat_button, new GridBagConstraints(0, 1, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(shield_button, new GridBagConstraints(0, 2, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(unshield_button, new GridBagConstraints(0, 3, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
rightpanel.add(userListPane, new GridBagConstraints(0, 4, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//中間佈局
JLabel userMsg_label = new JLabel("世界聊天:");
userMsgArea = new JTextPane();
userMsgArea.setEditable(false);
userTextScrollPane = new JScrollPane();
userTextScrollPane.setViewportView(userMsgArea);
userVertical = new JScrollBar(JScrollBar.VERTICAL);
userVertical.setAutoscrolls(true);
userTextScrollPane.setVerticalScrollBar(userVertical);
centerpanel.add(userMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
centerpanel.add(userTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
/*設置頂層佈局*/
panel.add(headpanel, "North");
panel.add(footpanel, "South");
panel.add(leftpanel, "West");
panel.add(rightpanel, "East");
panel.add(centerpanel, "Center");
//將按鈕事件全部註冊到監聽器
allActionListener allActionListener = new allActionListener();
//連接服務器
head_connect.addActionListener(allActionListener);
//往服務器發送消息
foot_send.addActionListener(allActionListener);
//退出聊天室
head_exit.addActionListener(allActionListener);
//清空系統消息
foot_sysClear.addActionListener(allActionListener);
//清空世界聊天消息
foot_userClear.addActionListener(allActionListener);
//私聊
privateChat_button.addActionListener(allActionListener);
//屏蔽
shield_button.addActionListener(allActionListener);
//取消屏蔽
unshield_button.addActionListener(allActionListener);
//窗口關閉事件
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int option = JOptionPane.showConfirmDialog(frame, "確定關閉聊天室界面?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
if (e.getWindow() == frame) {
if (receive != null) {
sendMsg("exit", ""); //如果已連接就告訴服務器本客戶端已斷開連接,退出聊天室
}
frame.dispose();
System.exit(0);
} else {
return;
}
} else {
return;
}
}
});
//聊天信息輸入框的監聽回車按鈕事件
text_field.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接服務器進入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = text_field.getText();
if (text != null && !text.equals("")) {
sendMsg("msg", text);
text_field.setText("");
}
}
}
});
//窗口顯示
frame.setVisible(true);
String name = JOptionPane.showInputDialog("請輸入聊天所用暱稱:");
if (name != null &&!name.equals("")) {
name_textfield.setText(name);
}
}
/**
* @MethodName ipCheck
* @Params * @param null
* @Description 驗證ip格式是否正確
* @Return
* @Since 2020/6/8
*/
public static boolean ipCheckHost(String text) {
if (text != null && !text.isEmpty()) {
// 定義正則表達式
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."+
"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."+
"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."+
"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
// 判斷ip地址是否與正則表達式匹配
if (text.matches(regex)) {
// 返回判斷信息
return true;
} else {
// 返回判斷信息
return false;
}
}
return false;
}
/**
* @MethodName ipCheckPort
* @Params * @param null
* @Description 驗證端口格式是否準確
* @Return
* @Since 2020/6/8
*/
public static boolean ipCheckPort(String text){
return text.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])");
}
/**
* @ClassName allActionListener
* @Params * @param null
* @Description 全局監聽事件,監聽所有按鈕,輸入框,內部類
* @Return
* @Since 2020/6/6
*/
private class allActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
switch (cmd) {
case "連接":
//獲取文本框裏面的ip和port
String strhost = host_textfield.getText();
String strport = port_textfield.getText();
if (!ipCheckHost(strhost)){
JOptionPane.showMessageDialog(frame, "請檢查ip格式是否準確!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
if(!ipCheckPort(strport)){
JOptionPane.showMessageDialog(frame, "請檢查端口號是否爲0~65535之間的整數!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
//連接服務器,開啓線程
connectServer(strhost, Integer.parseInt(strport));
// String name = JOptionPane.showInputDialog("請輸入你的暱稱:"); /*提示輸入暱稱*/
// name_textfield.setText(name);
/*發送設置姓名的消息和列出用戶列表的消息*/
// send.setMsg("setName", name);
break;
case "退出":
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "對不起,您現在不在聊天室,無法退出!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
sendMsg("exit", "");
head_connect.setText("連接");
head_exit.setText("已退出");
port_textfield.setEditable(true);
name_textfield.setEditable(true);
host_textfield.setEditable(true);
try {
charClient.close();
} catch (IOException ioException) {
JOptionPane.showMessageDialog(frame, "關閉連接服務器失敗,請重啓客戶端!", "錯誤", JOptionPane.ERROR_MESSAGE);
}
showEscDialog("您已成功退出聊天室!");
charClient = null;
receive = null;
break;
case "發送":
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接服務器進入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String text = text_field.getText();
if (text != null && !text.equals("")) {
sendMsg("msg", text);
text_field.setText("");
}
break;
case "私聊":
//服務器未連接
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接服務器進入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String selected = userlist.getSelectedValue();
//私聊自己
if (selected.equals(getUserName(String.valueOf(id)))) {
JOptionPane.showMessageDialog(frame, "你不能私聊自己!", "警告", JOptionPane.WARNING_MESSAGE);
break;
}
//已有私聊窗口
if (privateChatFrameMap.containsKey(users_map.get(selected).toString())) {
JOptionPane.showMessageDialog(frame, "與該用戶私聊窗口已存在,請不要重複私聊!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
if (users_map.containsKey(selected)) {
sendMsg("buildPrivateChat", String.valueOf(users_map.get(selected)));
//建立溝通彈窗
}
break;
case "屏蔽對方":
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接服務器進入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String selectedShield = userlist.getSelectedValue();
//如果是自己
if (selectedShield.equals(getUserName(String.valueOf(id)))) {
JOptionPane.showMessageDialog(frame, "你不能屏蔽自己!", "警告", JOptionPane.WARNING_MESSAGE);
break;
}
//不準屏蔽管理員!
if (selectedShield.equals(getUserName(String.valueOf(0)))) {
JOptionPane.showMessageDialog(frame, "對不起,不支持屏蔽管理員!", "警告", JOptionPane.WARNING_MESSAGE);
break;
}
//不能重複屏蔽!
if (selectedShield.indexOf("(已屏蔽)") != -1) {
JOptionPane.showMessageDialog(frame, "對方已被屏蔽了!請不要重複操作!", "錯誤", JOptionPane.ERROR_MESSAGE);
break;
}
if (users_map.containsKey(selectedShield)) { //發送需要屏蔽用戶的id
sendMsg("shield", String.valueOf(users_map.get(selectedShield)));
users_map.put(selectedShield + "(已屏蔽)", users_map.get(selectedShield));
users_map.remove(selectedShield);
}
int index1 = users_model.indexOf(selectedShield);
users_model.set(index1, selectedShield + "(已屏蔽)");
break;
case "取消屏蔽":
if (charClient == null || receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接服務器進入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);
break;
}
String unShield = userlist.getSelectedValue();
if (unShield.indexOf("(已屏蔽)") == -1) {
JOptionPane.showMessageDialog(frame, "對方並未被屏蔽!", "警告", JOptionPane.WARNING_MESSAGE);
break;
}
String name = unShield.substring(0, unShield.indexOf("(已屏蔽)"));
sendMsg("unshield", String.valueOf(users_map.get(unShield)));
int index2 = users_model.indexOf(unShield);
users_model.set(index2, name);
users_map.put(name, users_map.get(unShield));
users_map.remove(unShield);
break;
case "清空系統消息":
sysMsgArea.setText("");
break;
case "清空聊天消息":
userMsgArea.setText("");
break;
default:
break;
}
}
}
/**
* @MethodName connectServer
* @Params * @param null
* @Description 開啓與服務器的連接,開啓接受服務器的指令與信息的Receive線程類
* @Return
* @Since 2020/6/6
*/
private boolean connectServer(String host, int port) {
try {
charClient = new Socket(host, port);
dos = new DataOutputStream(charClient.getOutputStream());
receive = new Receive(charClient, this);
(new Thread(receive)).start();//接受服務器的消息的線程(系統消息和其他網友的信息)
head_connect.setText("已連接");
head_exit.setText("退出");
port_textfield.setEditable(false);
name_textfield.setEditable(false);
host_textfield.setEditable(false);
sendMsg("new", name_textfield.getText()); //後續寫在登錄窗口
sendMsg("getList", "");
return true;
} catch (IOException e) {
JOptionPane.showMessageDialog(frame, "連接服務器失敗!", "錯誤", JOptionPane.ERROR_MESSAGE);
return false;
}
}
/**
* @MethodName sendMsg
* @Params * @param null
* @Description 發送指令與信息給服務器端對應的線程服務
* @Return
* @Since 2020/6/6
*/
private void sendMsg(String cmd, String msg) {
try {
dos.writeUTF("<cmd>" + cmd + "</cmd><msg>" + msg + "</msg>");
dos.flush();
} catch (IOException e) {
close(dos, charClient);
}
}
public void setId(int id) {
this.id = id;
}
/**
* @MethodName updateTextArea
* @Params * @param null
* @Description 更新系統文本域或聊天事件文本域
* @Return
* @Since 2020/6/6
*/
public void updateTextArea(String content, String where) {
if (content.length() > 0) {
Matcher matcher = null;
if (where.equals("user")) {
Pattern pattern = Pattern.compile("<userId>(.*)</userId><userMsg>(.*)</userMsg><time>(.*)</time>");
matcher = pattern.matcher(content);
if (matcher.find()) {
String userId = matcher.group(1);
String userMsg = matcher.group(2);
String time = matcher.group(3);
// if(userMsg.startsWith("<emoji>")){
// String emojiCode = userMsg.substring(7, smsg.length()-8);
// insertMessage(userTextScrollPane, userMsgArea, emojiCode, fromName+"說:", null,userVertical);
// return ;
// }
if (userId.equals("0")) {
insertMessage(userTextScrollPane, userMsgArea, null, getUserName(userId) + " "+time , " "+userMsg, userVertical, false);
} else {
String fromName = getUserName(userId);
if (fromName.equals("[用戶" + id + "]" + name_textfield.getText())) //如果是自己說的話
fromName = "你";
insertMessage(userTextScrollPane, userMsgArea, null, fromName + " "+time, " "+userMsg, userVertical, false);
}
}
} else {
Pattern pattern = Pattern.compile("<time>(.*)</time><sysMsg>(.*)</sysMsg>");
matcher = pattern.matcher(content);
if (matcher.find()) {
String sysTime = matcher.group(1);
String sysMsg = matcher.group(2);
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + sysTime, sysMsg, sysVertical, true);
}
}
}
}
/**
* @MethodName insertMessage
* @Params * @param null
* @Description 更新文本域信息格式化工具
* @Return
* @Since 2020/6/6
*/
private void insertMessage(JScrollPane scrollPane, JTextPane textPane, String icon_code,
String title, String content, JScrollBar vertical, boolean isSys) {
StyledDocument document = textPane.getStyledDocument(); /*獲取textpane中的文本*/
/*設置標題的屬性*/
Color content_color = null;
if (isSys) {
content_color = Color.RED;
} else {
content_color = Color.GRAY;
}
SimpleAttributeSet title_attr = new SimpleAttributeSet();
StyleConstants.setBold(title_attr, true);
StyleConstants.setForeground(title_attr, Color.BLUE);
/*設置正文的屬性*/
SimpleAttributeSet content_attr = new SimpleAttributeSet();
StyleConstants.setBold(content_attr, false);
StyleConstants.setForeground(content_attr, content_color);
Style style = null;
if (icon_code != null) {
Icon icon = new ImageIcon("icon/" + icon_code + ".png");
style = document.addStyle("icon", null);
StyleConstants.setIcon(style, icon);
}
try {
document.insertString(document.getLength(), title + "\n", title_attr);
if (style != null)
document.insertString(document.getLength(), "\n", style);
else
document.insertString(document.getLength(), content + "\n", content_attr);
} catch (BadLocationException ex) {
System.out.println("Bad location exception");
}
/*設置滑動條到最後*/
textPane.setCaretPosition(textPane.getDocument().getLength());
// vertical.setValue(vertical.getMaximum());
}
/**
* @MethodName getUserName
* @Params * @param null
* @Description 在users_map中根據value值用戶ID獲取key值的用戶名字
* @Return
* @Since 2020/6/6
*/
private String getUserName(String strId) {
int uid = Integer.parseInt(strId);
Set<String> set = users_map.keySet();
Iterator<String> iterator = set.iterator();
String cur = null;
while (iterator.hasNext()) {
cur = iterator.next();
if (users_map.get(cur) == uid) {
return cur;
}
}
return "";
}
/**
* @MethodName showEscDialog
* @Params * @param null
* @Description 處理當前客戶端用戶斷開與服務器連接的一切事務
* @Return
* @Since 2020/6/6
*/
public void showEscDialog(String content) {
//清除所有私聊
if (privateChatFrameMap.size() != 0) {
Set<Map.Entry<String, privateChatFrame>> entrySet = privateChatFrameMap.entrySet();
Iterator<Map.Entry<String, privateChatFrame>> iter = entrySet.iterator();
while (iter.hasNext())
{
Map.Entry<String, privateChatFrame> entry = iter.next();
entry.getValue().dispose(); //關閉對應窗口
sendMsg("privateExit", entry.getKey()); //想對方說明私聊結束
}
}
//關閉輸出流
close(dos, charClient);
receive.setRunning(false);
//輸入框可編輯
port_textfield.setEditable(true);
name_textfield.setEditable(true);
host_textfield.setEditable(true);
head_connect.setText("連接");
head_exit.setText("已退出");
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), content, sysVertical, true);
JOptionPane.showMessageDialog(frame, content, "提示", JOptionPane.WARNING_MESSAGE);
/*清除消息區內容,清除用戶數據模型內容和用戶map內容,更新房間內人數*/
userMsgArea.setText("");
// sysMsgArea.setText("");
users_map.clear();
users_model.removeAllElements();
users_label.setText("聊天室內人數:0");
}
/**
* @MethodName addUser
* @Params * @param null
* @Description 當有新的用戶加入聊天室,系統文本域的更新和用戶列表的更新
* @Return
* @Since 2020/6/6
*/
public void addUser(String content) {
if (content.length() > 0) {
Pattern pattern = Pattern.compile("<username>(.*)</username><id>(.*)</id>");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String name = matcher.group(1);
String id = matcher.group(2);
if (!users_map.containsKey(name)) {
users_map.put("[用戶" + id + "]" + name, Integer.parseInt(id));
users_model.addElement("[用戶" + id + "]" + name);
} else {
users_map.remove("[用戶" + id + "]" + name);
users_model.removeElement(name);
users_map.put("[用戶" + id + "]" + name, Integer.parseInt(id));
users_model.addElement("[用戶" + id + "]" + name);
}
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), "[用戶" + id + "]" + name + " 加入了聊天室", sysVertical, true);
}
}
users_label.setText("聊天室內人數:" + users_map.size()); //更新房間內的人數
}
/**
* @MethodName delUser
* @Params content(爲退出用戶的ID)
* @Description 當有用戶退出時,系統文本域的通知和用戶列表的更新
* @Return
* @Since 2020/6/6
*/
public void delUser(String content) {
if (content.length() > 0) {
Set<String> set = users_map.keySet();
String delName = getUserName(content);
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), delName + " 退出了聊天室", sysVertical, true);
users_map.remove(delName);
users_model.removeElement(delName);
}
users_label.setText("聊天室內人數:" + users_map.size());//更新房間內的人數
}
/**
* @MethodName updateUsername
* @Params content(爲指定用戶的ID)
* @Description 修改指定ID用戶的暱稱(暫時用不到)
* @Return
* @Since 2020/6/6
*/
public void updateUsername(String content) {
if (content.length() > 0) {
Pattern pattern = Pattern.compile("<id>(.*)</id><username>(.*)</username>");
Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
String id = matcher.group(1);
String name = matcher.group(2);
if (id.equals("0")) {
users_map.remove("[管理員]" + name);
users_model.removeElementAt(0);
users_map.put("[管理員]" + name, Integer.parseInt(id));
users_model.addElement("[管理員]" + name);
} else if (users_map.get("[用戶" + id + "]" + name) != Integer.parseInt(id)) {
users_map.put("[用戶" + id + "]" + name, Integer.parseInt(id));
users_model.addElement("[用戶" + id + "]" + name);
} else {
users_map.remove("[用戶" + id + "]" + name);
users_model.removeElement("[用戶" + id + "]" + name);
users_map.put("[用戶" + id + "]" + name, Integer.parseInt(id));
users_model.addElement("[用戶" + id + "]" + name);
}
}
}
}
/**
* @MethodName getUserList
* @Params * @param null
* @Description 從服務器獲取全部用戶信息的列表,解析信息格式,列出所有用戶
* @Return
* @Since 2020/6/6
*/
public void getUserList(String content) {
String name = null;
String id = null;
Pattern numPattern = null;
Matcher numMatcher = null;
Pattern userListPattern = null;
if (content.length() > 0) {
numPattern = Pattern.compile("<user>(.*?)</user>");
numMatcher = numPattern.matcher(content);
//遍歷字符串,進行正則匹配,獲取所有用戶信息
while (numMatcher.find()) {
String detail = numMatcher.group(1);
userListPattern = Pattern.compile("<id>(.*)</id><username>(.*)</username>");
Matcher userListmatcher = userListPattern.matcher(detail);
if (userListmatcher.find()) {
name = userListmatcher.group(2);
id = userListmatcher.group(1);
if (id.equals("0")) {
name = "[管理員]" + name;
users_map.put(name, Integer.parseInt(id));
} else {
name = "[用戶" + id + "]" + name;
users_map.put(name, Integer.parseInt(id));
}
users_model.addElement(name);
}
}
users_model.removeElementAt(0);
}
users_label.setText("聊天室內人數:" + users_map.size());
}
/**
* @MethodName askBuildPrivateChat
* @Params * @param null
* @Description 處理某用戶對當前客戶端的用戶的私聊請求
* @Return
* @Since 2020/6/6
*/
public void askBuildPrivateChat(String msg) {
Pattern pattern = Pattern.compile("<from>(.*)</from><id>(.*)</id>");
Matcher matcher = pattern.matcher(msg);
if (matcher.find()) {
String toPrivateChatName = matcher.group(1);
String toPrivateChatId = matcher.group(2);
int option = JOptionPane.showConfirmDialog(frame, "[" + toPrivateChatName + "]想與你私聊,是否同意?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
sendMsg("agreePrivateChar", toPrivateChatId);
privateChatFrame chatFrame = new privateChatFrame("與[" + toPrivateChatName + "]的私聊窗口", toPrivateChatName, toPrivateChatId);
privateChatFrameMap.put(toPrivateChatId, chatFrame);
} else {
sendMsg("refusePrivateChar", toPrivateChatId);
}
}
}
/**
* @MethodName startOrStopHisPrivateChat
* @Params * @param null
* @Description 獲取請求指定用戶的私聊請求的結果,同意就開啓私聊窗口,拒絕就提示。
* @Return
* @Since 2020/6/6
*/
public void startOrStopHisPrivateChat(String msg) {
Pattern pattern = Pattern.compile("<result>(.*)</result><from>(.*)</from><id>(.*)</id>");
Matcher matcher = pattern.matcher(msg);
if (matcher.find()) {
String result = matcher.group(1);
String toPrivateChatName = matcher.group(2);
String toPrivateChatId = matcher.group(3);
if (result.equals("1")) { //對方同意的話
if (toPrivateChatId.equals("0")) {
toPrivateChatName = "[管理員]" + toPrivateChatName;
}
privateChatFrame chatFrame = new privateChatFrame("與[" + toPrivateChatName + "]的私聊窗口", toPrivateChatName, toPrivateChatId);
privateChatFrameMap.put(toPrivateChatId, chatFrame);
} else if (result.equals("0")) {
JOptionPane.showMessageDialog(frame, "[" + toPrivateChatName + "]拒絕了你的私聊請求", "失敗", JOptionPane.ERROR_MESSAGE);
}
}
}
/**
* @MethodName giveMsgToPrivateChat
* @Params * @param null
* @Description 根據服務器端發來的用戶ID和內容,搜尋當前客戶端的用戶中對應傳來的用戶ID的私聊窗口,將內容寫進私聊窗口的文本域
* @Return
* @Since 2020/6/6
*/
public void giveMsgToPrivateChat(String msg) {
Pattern privatePattern = Pattern.compile("<msg>(.*)</msg><id>(.*)</id>");
Matcher privateMatcher = privatePattern.matcher(msg);
if (privateMatcher.find()) {
String toPrivateMsg = privateMatcher.group(1);
String toPrivateId = privateMatcher.group(2);
privateChatFrame chatFrame = privateChatFrameMap.get(toPrivateId);
insertMessage(chatFrame.textScrollPane, chatFrame.msgArea, null, df.format(new Date()) + " 對方說:", " "+toPrivateMsg, chatFrame.vertical, false);
}
}
/**
* @MethodName endPrivateChat
* @Params * @param null
* @Description 結束指定id用戶的私聊窗口
* @Return
* @Since 2020/6/6
*/
public void endPrivateChat(String msg) {
Pattern privatePattern = Pattern.compile("<id>(.*)</id>");
Matcher privateMatcher = privatePattern.matcher(msg);
if (privateMatcher.find()) {
String endPrivateId = privateMatcher.group(1);
privateChatFrame chatFrame = privateChatFrameMap.get(endPrivateId);
JOptionPane.showMessageDialog(frame, "由於對方結束了私聊,該私聊窗口即將關閉!", "提示", JOptionPane.WARNING_MESSAGE);
chatFrame.dispose();
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), "由於[" + chatFrame.otherName + "]關閉了私聊窗口,私聊結束!", sysVertical, true);
privateChatFrameMap.remove(endPrivateId);
}
}
/**
* @ClassName privateChatFrame
* @Params * @param null
* @Description 私聊窗口GUI的內部類
* @Return
* @Since 2020/6/6
*/
private class privateChatFrame extends JFrame {
private String otherName;
private String otherId;
private JButton sendButton;
private JTextField msgTestField;
private JTextPane msgArea;
private JScrollPane textScrollPane;
private JScrollBar vertical;
public privateChatFrame(String title, String otherName, String otherId) throws HeadlessException {
super(title);
this.otherName = otherName;
this.otherId = otherId;
//全局面板容器
JPanel panel = new JPanel();
//全局佈局
BorderLayout layout = new BorderLayout();
JPanel headpanel = new JPanel(); //上層panel,
JPanel footpanel = new JPanel(); //下層panel
JPanel centerpanel = new JPanel(); //中間panel
//頭部佈局
FlowLayout flowLayout = new FlowLayout();
//底部佈局
GridBagLayout gridBagLayout = new GridBagLayout();
setSize(600, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
setContentPane(panel);
setLayout(layout);
headpanel.setLayout(flowLayout);
footpanel.setLayout(gridBagLayout);
footpanel.setPreferredSize(new Dimension(0, 40));
centerpanel.setLayout(gridBagLayout);
//添加頭部部件
JLabel Name = new JLabel(otherName);
headpanel.add(Name);
//設置底部佈局
sendButton = new JButton("發送");
sendButton.setPreferredSize(new Dimension(40, 0));
msgTestField = new JTextField();
footpanel.add(msgTestField, new GridBagConstraints(0, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
footpanel.add(sendButton, new GridBagConstraints(1, 0, 1, 1, 10, 10,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));
//中間佈局
msgArea = new JTextPane();
msgArea.setEditable(false);
textScrollPane = new JScrollPane();
textScrollPane.setViewportView(msgArea);
vertical = new JScrollBar(JScrollBar.VERTICAL);
vertical.setAutoscrolls(true);
textScrollPane.setVerticalScrollBar(vertical);
centerpanel.add(textScrollPane, new GridBagConstraints(0, 0, 1, 1, 100, 100,
GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
//設置頂層佈局
panel.add(headpanel, "North");
panel.add(footpanel, "South");
panel.add(centerpanel, "Center");
//窗口關閉事件
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int option = JOptionPane.showConfirmDialog(e.getOppositeWindow(), "確定結束私聊?", "提示",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
if (receive != null) {
sendMsg("privateExit", otherId); //關閉當前私聊連接
}
insertMessage(sysTextScrollPane, sysMsgArea, null, "[系統消息] " + df.format(new Date()), "您與[" + otherName + "]的私聊結束", sysVertical, true);
dispose();
privateChatFrameMap.remove(otherId);
} else {
return;
}
}
});
//聊天信息輸入框的監聽回車按鈕事件
msgTestField.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (e.getKeyChar() == KeyEvent.VK_ENTER) {
if (receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = msgTestField.getText();
if (text != null && !text.equals("")) {
sendMsg("privateMsg", "<msg>" + text + "</msg><id>" + otherId + "</id>");
msgTestField.setText("");
insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你說:", " "+text, vertical, false);
}
}
}
});
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (cmd.equals("發送")) {
if (receive == null) {
JOptionPane.showMessageDialog(frame, "請先連接聊天室的服務器!", "提示", JOptionPane.WARNING_MESSAGE);
return;
}
String text = msgTestField.getText();
if (text != null && !text.equals("")) {
sendMsg("privateMsg", "<msg>" + text + "</msg><id>" + otherId + "</id>");
msgTestField.setText("");
insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你說:", " "+text, vertical, false);
}
}
}
});
//窗口顯示
setVisible(true);
}
}
/**
* @MethodName setUIStyle
* @Params * @param null
* @Description 根據操作系統自動變化GUI界面風格
* @Return
* @Since 2020/6/6
*/
public static void setUIStyle() {
// String lookAndFeel = UIManager.getSystemLookAndFeelClassName(); //設置當前系統風格
String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); //可跨系統
try {
UIManager.setLookAndFeel(lookAndFeel);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
其它代碼
1. 服務器端用到的用戶信息類
package top.hcode.chatRoom;
import java.net.Socket;
/**
* @Author: Himit_ZH
* @Date: 2020/6/4 23:25
* @Description: 聊天室用戶信息類
*/
public class User {
private String username;
private Integer id;
private Socket socket;
public User(String username, Integer id, Socket socket) {
this.username = username;
this.id = id;
this.socket = socket;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
2. 客戶端接受獲取服務器端指令和信息的線程類
package top.hcode.chatRoom;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 使用多線程封裝:接收端
* 1、接收消息
* 2、釋放資源
* 3、重寫run
*
* @author Himit_ZH
*/
public class Receive implements Runnable {
private DataInputStream dis;
private Socket connect;
private boolean isRunning;
private Client client;
public Receive(Socket connect, Client client) {
this.connect = connect;
this.isRunning = true;
this.client = client;
try {
dis = new DataInputStream(connect.getInputStream());
} catch (IOException e) {
System.out.println("數據輸入流初始化錯誤,請重啓");
release();
}
}
//接收消息
private String receive() {
String msg = "";
try {
msg = dis.readUTF();
} catch (IOException e) {
release();
}
return msg;
}
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
while (isRunning) {
String msg = receive();
if(!msg.equals("")&&msg!=null) {
parseMessage(msg); //收到指令與消息的字符串,進行指令分配
}
}
}
/**
* @MethodName parseMessage
* @Params * @param null
* @Description 處理服務器發來的指令,將對應信息給予客戶端線程相應的方法處理
* @Return
* @Since 2020/6/6
*/
public void parseMessage(String message) {
String code = null;
String msg = null;
/*
* 先用正則表達式匹配響應code碼和msg內容
*/
if (message.length() > 0) {
Pattern pattern = Pattern.compile("<cmd>(.*)</cmd>");
Matcher matcher = pattern.matcher(message);
if (matcher.find()) {
code = matcher.group(1);
}
pattern = Pattern.compile("<msg>(.*)</msg>");
matcher = pattern.matcher(message);
if (matcher.find()) {
msg = matcher.group(1);
}
switch (code) {
case "1": //更新世界聊天
client.updateTextArea(msg, "user");
break;
case "2": //更新系統消息
client.updateTextArea(msg, "sys");
break;
case "3": //與服務器斷開消息 :可能爲被踢,或者服務器關閉
client.showEscDialog(msg);
break;
case "4": //新增用戶
client.addUser(msg);
break;
case "5": //刪除用戶 有其它用戶退出 或者自己退出
client.delUser(msg);
break;
case "6": //更新某個用戶更新的名字
client.updateUsername(msg);
break;
case "7": /*列出用戶列表*/
client.getUserList(msg);
break;
case "8": //設置用戶id
client.setId(Integer.parseInt(msg));
break;
case "9": //他人私聊於你,請求是否同意
client.askBuildPrivateChat(msg);
break;
case "10": //你請求想與他人私聊的結果
client.startOrStopHisPrivateChat(msg);
break;
case "11": //對方給你的私聊消息
client.giveMsgToPrivateChat(msg);
break;
case "12": //對方結束了私聊
client.endPrivateChat(msg);
}
}
}
//釋放資源
private void release() {
this.isRunning = false;
CloseUtils.close(dis, connect);
}
}
3. 客戶端和服務器端都要用到的關閉連接或IO流的工具類
package top.hcode.chatRoom;
import java.io.Closeable;
/**
* 名字:工具類
* 作用:關閉各種資源
* @author Himit_ZH
*
*/
public class CloseUtils {
/**
* 釋放資源
*/
public static void close(Closeable... targets ) {
for(Closeable target:targets) {
try {
if(null!=target) {
target.close();
}
}catch(Exception e) {
e.printStackTrace();
}
}
}
}
打包成jar
- 項目結構
以idea爲演示
-
然後在打開的窗口裏面這樣操作
-
點擊選擇jar包運行的主類
-
選擇要執行的是服務器端還是客戶端
-
最後點擊OK
-
然後構建環境生成jar包
-
在彈窗中選擇Bulid
-
最後jar包就生成了,點開就能運行
服務器端和客戶端生成jar包的操作差不多,就是選擇主類的時候記得區別!
打包成exe文件
- 下載exe4j,自行百度下載咯
- 打開後,最好去網上找個註冊碼,不然之後每次打開exe文件都會有exe4j的廣告彈窗!
提供一個註冊碼:L-g782dn2d-1f1yqxx1rv1sqd
-
開始,點擊next
-
選擇“JAR in EXE mode”,點擊“Next”
-
給生成的exe應用取個名稱,之後選擇exe生成路徑,點擊“Next”
-
可選生成自定義圖標,選擇ico文件,沒有就不選"Icon File",最好不勾選“Allow only a single…”,因爲這樣才能在電腦運行多個客戶端,選擇了的話,生成的exe文件只能打開一個,當然服務器端可以點。
-
然後點擊這個
-
在新窗口,繼續點”Next"
-
選擇勾上,然後繼續“Next"
-
VM Parameters要填的是: -Dappdir=${EXE4J_EXEDIR}
然後點擊下一步
-
選擇jdk版本
-
選擇JRE
-
之後,一路點”Next"
-
最後生成exe,可以點擊查看,或者去選擇的生成目錄查看exe文件
如何讓其它電腦訪問聊天室?
在線聊天室運用的是Socket通信,網絡協議是TCP/IP,所以要如何讓別的主機電腦訪問聊天室呢
- 把聊天室服務器端放在有公網IP的雲服務器或者主機上,開放特定的TCP端口號即可。
- 內網穿透技術,可以利用NAT穿透技術讓外網的電腦能夠訪問處於內網的聊天室服務器,當然這裏提供白嫖的內網穿透,畢竟只是同學之間玩玩這個聊天室而已。
點擊去官網註冊Sunny-Ngrok
註冊完後登錄後,點擊開通隧道。
白嫖黨,選擇免費服務器。可能會有點卡,但是拿來使用聊天室夠了
點擊確定後,按圖中說明填。
查看隧道,下載該軟件,複製對應的TCP隧道ID
下載好軟件後,點開目錄,啓動內網穿透
輸入隧道id,回車啓動即可
接着,打開聊天室的服務器,記住剛剛填寫的本地的端口號,就是127.0.0.1:8888,這裏需要一致,我們填寫8888,啓動服務。
在這個官網申請到的白嫖服務器的公網IP是64.69.43.237,端口號就是自己申請的,或者你可以查看運行的內網穿透窗口。
接着,啓動客戶端,來測試一下是否成功連接上服務器。
最後,連接成功,趕快把客戶端發給同學,大家一起來聊天室聊天吧,哈哈哈
最後
提供在線聊天室服務器端和用戶端生成的jar包和exe文件的集合壓縮包
CSDN下載地址=>jar包和和exe文件整合的壓縮包下載