多人聊天室
功能设计
一、服务器端
- 启动服务器端,打开服务器端窗口
- 创建ServerSocket,循环监听客户端连接
- 存储连接到服务器端的多个Socket
- 接收客户端发送的信息(接收线程)
- 将接收到的消息转发给所有的客户端socket
- 在服务器窗口显示所有客户端发送的消息
- 关闭服务器
二、客户端
- 打开登录窗口,可以登录和注册
- 注册需要先判断账号密码是否规则,在验证账号是否已被注册,注册成功后返回登录窗口并在数据库用户表中插入用户信息
- 登录时与数据库连接比对账号密码
- 信息验证通过后,创建客户端Socket连接服务器
- 连接到服务器后,关闭登录窗口,打开聊天窗口
- 输入聊天信息(验证),发送聊天信息
- 客户端接收服务器发送过来的聊天(接收线程)
- 关闭客户端
流程图
![在这里插入图片描述](https://img-blog.csdnimg.cn/3dc80c28743a45cd81eb8df6c18ab30d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzUyMTYzMjMw,size_16,color_FFFFFF,t_70#pic_center)
具体设计
客户端
登录窗口
package frame;
import dao.LoginDao;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.Socket;
public class LoginFrame extends JFrame {
public void creatFrame() {
this.setSize(500, 300);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("欢迎来到聊天室");
this.setResizable(false);
this.setIconImage(new ImageIcon("").getImage());
JPanel jPanel = new JPanel(new GridLayout(4, 1));
JPanel welcomePanel = new JPanel();
JLabel welcomeLabel = new JLabel("欢迎登录");
welcomeLabel.setFont(new Font("楷体", Font.BOLD, 25));
welcomePanel.add(welcomeLabel);
JPanel accountPanel = new JPanel();
JLabel accountLabel = new JLabel("账号");
JTextField accountTextField = new JTextField(20);
accountPanel.add(accountLabel);
accountPanel.add(accountTextField);
JPanel passwordPanel = new JPanel();
JLabel pswLabel = new JLabel("密码");
JPasswordField pswField = new JPasswordField(20);
passwordPanel.add(pswLabel);
passwordPanel.add(pswField);
JPanel btnPanel = new JPanel();
JButton loginBtn = new JButton("登入");
JButton registerBtn = new JButton("注册");
btnPanel.add(loginBtn);
btnPanel.add(registerBtn);
jPanel.add(welcomePanel);
jPanel.add(accountPanel);
jPanel.add(passwordPanel);
jPanel.add(btnPanel);
loginBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
String account = accountTextField.getText();
String password = new String(pswField.getPassword());
if (account.length() == 0) {
JOptionPane.showMessageDialog(null, "请输入账号", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
if (password.length() == 0) {
JOptionPane.showMessageDialog(null, "请输入密码", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
LoginDao loginDao = new LoginDao();
int res = loginDao.login(account, password);
System.out.println(res);
if (res == 0) {
JOptionPane.showMessageDialog(null, "账号或密码错误", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
Socket socket = new Socket("192.168.1.14", 7777);
new ChatFrame(socket, account).creatFrame();
dispose();
} catch (Exception exception) {
exception.printStackTrace();
JOptionPane.showMessageDialog(null, "服务器连接失败", "操作提示", JOptionPane.WARNING_MESSAGE);
}
}
});
registerBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new RegFrame().creatFrame();
dispose();
}
});
this.add(jPanel);
this.setVisible(true);
}
}
注册窗口
package frame;
import dao.RegDao;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RegFrame extends JFrame {
public void creatFrame() {
this.setSize(600, 400);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("请注册你的信息");
this.setResizable(false);
this.setIconImage(new ImageIcon("").getImage());
JPanel jPanel = new JPanel(new GridLayout(4, 1));
JPanel welcomePanel = new JPanel();
JLabel welcomeLabel = new JLabel("账号注册");
welcomeLabel.setFont(new Font("楷体", Font.BOLD, 25));
welcomePanel.add(welcomeLabel);
JPanel accountPanel = new JPanel();
JLabel accountLabel = new JLabel("账号");
JTextField accountTextField = new JTextField(20);
accountPanel.add(accountLabel);
accountPanel.add(accountTextField);
JPanel passwordPanel = new JPanel();
JLabel pswLabel = new JLabel("密码");
JPasswordField pswField = new JPasswordField(20);
passwordPanel.add(pswLabel);
passwordPanel.add(pswField);
JPanel btnPanel = new JPanel();
JButton okBtn = new JButton("确定");
JButton backBtn = new JButton("返回");
btnPanel.add(okBtn);
btnPanel.add(backBtn);
jPanel.add(welcomePanel);
jPanel.add(accountPanel);
jPanel.add(passwordPanel);
jPanel.add(btnPanel);
okBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
String account = accountTextField.getText();
String password = new String(pswField.getPassword());
if (account.length() == 0) {
JOptionPane.showMessageDialog(null, "账号不能为空", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
if (password.length() == 0) {
JOptionPane.showMessageDialog(null, "密码不能为空", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
int result = new RegDao().repeatVerification(account);
if (result == 1) {
JOptionPane.showMessageDialog(null, "账号已被注册", "操作提示", JOptionPane.WARNING_MESSAGE);
}
if (result == 0) {
RegDao regDao = new RegDao();
regDao.saveInformation(account, password);
JOptionPane.showMessageDialog(null, "账号注册成功", "操作提示", JOptionPane.WARNING_MESSAGE);
new LoginFrame().creatFrame();
dispose();
}
} catch (Exception exception) {
exception.printStackTrace();
JOptionPane.showMessageDialog(null, "注册失败", "操作提示", JOptionPane.WARNING_MESSAGE);
}
}
});
backBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new LoginFrame().creatFrame();
dispose();
}
});
this.add(jPanel);
this.setVisible(true);
}
}
聊天窗口
package frame;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Date;
public class ChatFrame extends JFrame {
Socket socket;
String account;
DataOutputStream dataOutputStream;
DataInputStream dataInputStream;
JTextArea jTextArea;
public ChatFrame(Socket socket, String account) throws HeadlessException, IOException {
this.socket = socket;
this.account = account;
this.dataOutputStream = new DataOutputStream(socket.getOutputStream());
this.dataInputStream = new DataInputStream(socket.getInputStream());
}
public void creatFrame() {
this.setSize(600, 600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle(account);
this.setResizable(false);
this.setIconImage(new ImageIcon("").getImage());
JPanel jPanel = new JPanel(new BorderLayout());
jTextArea = new JTextArea();
jTextArea.setEditable(false);
JScrollPane jScrollPane = new JScrollPane(jTextArea);
jPanel.add(jScrollPane);
JPanel inputPanel = new JPanel();
JTextField inputTextField = new JTextField(40);
JButton sendBtn = new JButton("发送");
inputPanel.add(inputTextField);
inputPanel.add(sendBtn);
jPanel.add(inputPanel, BorderLayout.SOUTH);
this.add(jPanel);
this.setVisible(true);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
int res = JOptionPane.showConfirmDialog(null, "您确定要退出聊天室吗?", "操作提示", JOptionPane.OK_CANCEL_OPTION);
if (res == 0) {
try {
socket.close();
dispose();
new LoginFrame().creatFrame();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
});
new ClientThread().start();
sendBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String inputMsg = inputTextField.getText();
if (inputMsg.length() == 0) {
JOptionPane.showMessageDialog(null, "发送内容不能为空", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
if (inputMsg.length() > 200) {
JOptionPane.showMessageDialog(null, "发送内容不能超过200字", "操作提示", JOptionPane.WARNING_MESSAGE);
return;
}
try {
String sendMsg = account + "\t" + new Date().toLocaleString() + "\n" + inputMsg + "\n";
dataOutputStream.writeUTF(sendMsg);
inputTextField.setText("");
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
});
}
class ClientThread extends Thread {
@Override
public void run() {
boolean flag = true;
while (flag) {
try {
String acceptMsg = dataInputStream.readUTF();
jTextArea.append(acceptMsg);
} catch (IOException e) {
e.printStackTrace();
flag = false;
JOptionPane.showMessageDialog(null, "服务器连接失败", "操作提示", JOptionPane.WARNING_MESSAGE);
dispose();
new LoginFrame().creatFrame();
}
}
}
}
}
注册访问数据库交互类
package dao;
import java.sql.*;
public class RegDao {
public int repeatVerification(String account) throws ClassNotFoundException, SQLException {
Connection conn = null;
PreparedStatement ps = null;
int result = 0;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/chat_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
conn = DriverManager.getConnection(url, "root", "******");
ps = conn.prepareStatement("SELECT\n" +
" COUNT(account) COUNT\n" +
"FROM\n" +
" USER\n" +
"WHERE account = ?");
ps.setObject(1, account);
ResultSet res = ps.executeQuery();
if (res.next()) {
result = res.getInt("count");
}
} finally {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
return result;
}
public void saveInformation(String account, String password) throws SQLException, ClassNotFoundException {
Connection conn = null;
PreparedStatement ps = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/chat_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
conn = DriverManager.getConnection(url, "root", "152475");
ps = conn.prepareStatement("INSERT INTO USER (account, PASSWORD, reg_time)\n" +
"VALUES\n" +
" (?, ?, NOW ())");
ps.setString(1, account);
ps.setString(2, password);
ps.executeUpdate();
} finally {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
}
}
登录访问数据库交互类
package dao;
import java.sql.*;
public class LoginDao {
public int login(String account, String password) throws ClassNotFoundException, SQLException {
Connection conn = null;
PreparedStatement ps = null;
int result = 0;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/chat_db?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";
conn = DriverManager.getConnection(url, "root", "******");
ps = conn.prepareStatement(" SELECT\n" +
" COUNT(*) COUNT\n" +
"FROM\n" +
" USER\n" +
"WHERE account = ?\n" +
" AND PASSWORD = ?");
ps.setString(1, account);
ps.setString(2, password);
ResultSet res = ps.executeQuery();
if (res.next()) {
result = res.getInt("count");
}
} finally {
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
}
return result;
}
}
启动
package frame;
public class ClientRun {
public static void main(String[] args) {
new LoginFrame().creatFrame();
}
}
服务器
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
public class ServerFrame extends JFrame {
ArrayList<Socket> sockets = new ArrayList<>();
ServerSocket serverSocket;
JTextArea jTextArea;
public void creatFrame() {
this.setSize(400, 600);
this.setTitle("欢迎来到FJZ聊天室");
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setVisible(true);
jTextArea = new JTextArea();
jTextArea.setEditable(false);
JScrollPane jScrollPane = new JScrollPane(jTextArea);
this.add(jScrollPane);
this.setVisible(true);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
serverSocket.close();
System.exit(0);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
});
}
public void startServer() {
try {
serverSocket = new ServerSocket(7777);
int count = 0;
while (true) {
Socket socket = serverSocket.accept();
count++;
System.out.println("第" + count + "个客户端已连接");
sockets.add(socket);
new ServerThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
this.dispose();
JOptionPane.showMessageDialog(null, "服务器启动失败", "操作提示", JOptionPane.WARNING_MESSAGE);
}
}
class ServerThread extends Thread {
Socket clientSocket;
DataInputStream dataInputStream;
public ServerThread(Socket clientSocket) throws IOException {
this.clientSocket = clientSocket;
this.dataInputStream = new DataInputStream(clientSocket.getInputStream());
}
@Override
public void run() {
boolean flag = true;
while (flag) {
try {
String msg = dataInputStream.readUTF();
jTextArea.append(msg);
Iterator<Socket> socketIterator = sockets.iterator();
while (socketIterator.hasNext()) {
Socket s = socketIterator.next();
if (s.isClosed()) {
socketIterator.remove();
continue;
}
DataOutputStream dataOutputStream = new DataOutputStream(s.getOutputStream());
dataOutputStream.writeUTF(msg);
}
} catch (IOException e) {
e.printStackTrace();
flag = false;
sockets.remove(clientSocket);
System.out.println("客户端已下线");
}
}
}
}
}
public class ServerRun {
public static void main(String[] args) {
ServerFrame serverFrame = new ServerFrame();
serverFrame.creatFrame();
serverFrame.startServer();
}
}
数据库
CREATE DATABASE IF NOT EXISTS chat_db;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
account CHAR(12) NOT NULL ,
PASSWORD CHAR(16) NOT NULL,
reg_time DATETIME NOT NULL
);
SELECT
COUNT(account) COUNT
FROM
USER
WHERE account = ?
INSERT INTO USER (account, PASSWORD, reg_time)
VALUES
(?, ?, NOW ())
SELECT
COUNT(*) COUNT
FROM
USER
WHERE account = ?
AND PASSWORD = ?