前几天要做一个客户端和服务端可以进行多次通信的小实验,最初我是采用UDP实现一对一通信的功能,后来又说可以扩展为多人通信,所以又使用TCP方式实现了多人通信。只能说,无限扩展使人进步。
dao包:数据库操作包,简单实现了插入数据操作。
main包:启动包,在这里启动程序,必须先开启服务端。
thread包:线程包,主要是消息发送和处理两个线程。
view包:视图包,登录界面和主界面。
package com.hyt.dao;
import java.sql.*;
public class Dao {
protected static String DB_CLASSNAME = "com.mysql.cj.jdbc.Driver";
// 连接数据库的URL
protected static String DB_URL = "jdbc:mysql://localhost:3306/history?useSSL=false&serverTimezone=UTC";
// 用户名
protected static String DB_USER = "root";
// 密码
protected static String DB_PWD = "123456";
// 创建表的SQL语句
private static final String CREATE_SQL = "create table if not exists record(id int not null auto_increment primary key," +
"sender varchar(20) not null,message text null," +
"receiver varchar(100) not null)";
// 声明Connection对象
private static Connection conn = null;
public static void create() {
if (conn == null) {
try {
// 加载驱动程序
Class.forName(DB_CLASSNAME);
// 获取与数据库的连接
conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
// 创建Statement对象
Statement stmt = conn.createStatement();
// 执行创建表的SQL语句
stmt.execute(CREATE_SQL);
System.out.println("数据库已连接");
stmt.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 插入数据
* @param from : 发送方
* @param to : 消息内容
* @param message : 接收方
*/
public static void insert(String from,String to,String message) {
String sql = "insert into record(sender,message,receiver) values(?,?,?)";
try {
// 创建预处理对象
PreparedStatement pst = conn.prepareStatement(sql);
// 设置参数
pst.setString(1,from);
pst.setString(2,message);
pst.setString(3,to);
// 执行
pst.executeUpdate();
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
package com.hyt.thread;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.net.Socket;
public abstract class UniversalThread implements Runnable {
// 消息
protected String message = null;
// 创建数据包套接字
protected Socket socket;
protected BufferedReader reader = null;
protected PrintWriter writer = null;
UniversalThread(Socket socket) {
this.socket = socket;
}
/**
* 设置消息
* @param message:消息内容
*/
public void setMessage(String message) {
this.message = message;
}
}
package com.hyt.thread;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
public class SendThread extends UniversalThread implements Runnable {
public SendThread(Socket socket) {
super(socket);
try {
writer = new PrintWriter(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送消息的线程
*/
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) { // 当前线程已被中断,退出循环
break;
}
if (message != null && !"".equals(message)) { // 若消息不为空
// 发送消息
writer.println(message);
writer.flush();
}
// 消息设为空
message = null;
try {
// 线程休眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.hyt.thread;
import com.hyt.view.Client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
public class ReceiveThread extends UniversalThread implements Runnable {
// 父客户端
private Client client;
public ReceiveThread(Socket socket, Client client) {
super(socket);
this.client = client;
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) { // 若被中断,退出循环
break;
}
try {
// 读取一行内容
message = reader.readLine();
if (message == null || "".equals(message)) {
continue;
}
// 客户端显示消息
client.getOutput().append(message + "\n\n");
// 自动移动最新消息位置
client.getOutput().setSelectionStart(client.getOutput().getText().length());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.hyt.view;
import javax.swing.*;
import java.awt.*;
public class Login extends JFrame {
// 用户昵称
public static String name;
// 提示标签
private JLabel label;
// 昵称输入框
private JTextField field;
// 点击按钮
private JButton button;
public Login() {
setTitle("聊天室");
setBounds(500, 400, 400, 200);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLayout(null);
label = new JLabel();
label.setFont(new Font("微软雅黑", Font.PLAIN, 18));
label.setText("请输入你的昵称:");
label.setBounds(30, 0, 150, 50);
add(label);
field = new JTextField();
field.setBounds(200, 10, 150, 30);
add(field);
button = new JButton("加入");
button.addActionListener(e -> {
// 对话窗体出现
new Client(field.getText());
// 本窗体消失
setVisible(false);
});
button.setBounds(100, 100, 150, 30);
add(button);
setVisible(true);
}
}
package com.hyt.view;
import com.hyt.main.Server;
import com.hyt.thread.ReceiveThread;
import com.hyt.thread.SendThread;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
public class Client extends JFrame {
// 创建套接字对象
private Socket socket;
// 用户昵称
private String name;
// 提示标签
private JLabel label;
// 消息输入框
private JTextField input;
// 消息输出框
private JTextArea output;
// 发送按钮
private JButton button;
// 消息发送线程
private SendThread sendThread = null;
// 消息接收线程
private ReceiveThread receiveThread = null;
private Thread send, receive;
public Client(String name) {
this.name = name;
try {
connect();
} catch (IOException e) {
e.printStackTrace();
}
initView();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public JTextArea getOutput() {
return output;
}
private void initView() {
setTitle("聊天室");
setBounds(700, 200, 400, 500);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(null);
// 添加提示标签
label = new JLabel("输入消息:");
label.setFont(new Font("华文中宋 常规", Font.PLAIN, 12));
label.setBounds(5, 430, 70, 30);
getContentPane().add(label);
// 添加输入文本框
input = new JTextField();
input.setFont(new Font("华文中宋 常规", Font.PLAIN, 14));
input.setBounds(70, 430, 230, 30);
input.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// 按下回车键,发送消息
if (e.getKeyCode() == 10) {
button.doClick();
}
}
});
getContentPane().add(input);
//添加输出的文本区域
output = new JTextArea();
output.setFont(new Font("华文中宋 常规", Font.PLAIN, 15));
output.setPreferredSize(new Dimension(380, 5000));
// 自动换行
output.setLineWrap(true);
final JScrollPane scrollPane = new JScrollPane();
// 文本域添加滚动面板中
scrollPane.setViewportView(output);
scrollPane.setBounds(0, 0, 380, 430);
getContentPane().add(scrollPane);
//添加发送按钮
button = new JButton("发 送");
button.setFont(new Font("华文中宋 常规", Font.PLAIN, 14));
button.setBounds(310, 430, 70, 30);
button.addActionListener(actionEvent -> {
// 将输入框内容设为线程发送消息
sendThread.setMessage(input.getText());
// 输入框清空
input.setText("");
});
getContentPane().add(button);
sendThread = new SendThread(socket);
receiveThread = new ReceiveThread(socket, this);
send = new Thread(sendThread);
receive = new Thread(receiveThread);
// 启动线程
send.start();
receive.start();
sendThread.setMessage(name);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// 关闭与服务器的连接
closeConnection();
System.exit(0);
}
});
setVisible(true);
}
/**
* 连接服务器,参与聊天
*
* @throws IOException
*/
private void connect() throws IOException {
socket = new Socket(InetAddress.getLocalHost(), Server.PORT);
}
/**
* 断开连接,退出聊天室
*/
public synchronized void closeConnection() {
sendThread.setMessage("退出聊天室");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断两个线程
send.interrupt();
receive.interrupt();
if (socket != null) {
try {
// 关闭套接字
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.hyt.main;
import com.hyt.dao.Dao;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class Server {
// 默认绑定端口
public static final int PORT = 9999;
// 从属线程链表
private static ArrayList<MessageThread> clients = new ArrayList<>();
// 创建服务器套接字
private ServerSocket serverSocket;
// 服务器主线程
private ServerThread serverThread;
public Server() {
try {
serverSocket = new ServerSocket(PORT);
} catch (IOException e) {
e.printStackTrace();
}
// 实例化服务器套接字
serverThread = new ServerThread(serverSocket);
// 主线程开启
new Thread(serverThread).start();
// 获取链接并创建表
Dao.create();
System.out.println("服务器已开启");
}
public static void main(String[] args) {
new Server();
}
/**
* 主线程,用来等待客户的请求
*/
class ServerThread implements Runnable {
private ServerSocket serverSocket;
ServerThread(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
@Override
public void run() {
while (true) {
Socket socket = null;
try {
// 等待客户机连接
socket = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
}
// 创建该客户的消息处理线程
MessageThread messageThread = new MessageThread(socket);
// 开启该线程
new Thread(messageThread).start();
// 将此线程加入链表
clients.add(messageThread);
}
}
}
/**
* 从属线程,处理消息
*/
class MessageThread implements Runnable {
private Socket socket;
private BufferedReader reader = null;
private PrintWriter writer = null;
private String name;
MessageThread(Socket socket) {
this.socket = socket;
try {
// 实例化BufferedReader对象
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 实例化PrintWriter对象
writer = new PrintWriter(socket.getOutputStream());
// 获取用户信息
name = reader.readLine();
writer.println(name + "已成功加入");
writer.flush();
for (int i = clients.size() - 1; i >= 0; i--) {
// 告诉所有人有新人加入
clients.get(i).getWriter().println(name + "已加入聊天室!");
clients.get(i).getWriter().flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public PrintWriter getWriter() {
return writer;
}
public BufferedReader getReader() {
return reader;
}
@Override
public void run() {
String message;
while (true) {
try {
// 获取消息内容
message = reader.readLine();
if (message == null || "".equals(message)) {
continue;
}
for (int i = clients.size() - 1; i >= 0; i--) {
// 向各个用户发送消息
clients.get(i).getWriter().println(name + ":" + message);
clients.get(i).getWriter().flush();
// 保存到数据库
Dao.insert(name,clients.get(i).getName(),message);
}
if ("退出聊天室".equals(message)) { // 用户退出,清空连接
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
clients.remove(this);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.hyt.main;
import com.hyt.view.Login;
public class Chatter1 {
public static void main(String[] args) {
new Login();
}
}
package com.hyt.main;
import com.hyt.view.Login;
public class Chatter2 {
public static void main(String[] args) {
new Login();
}
}
package com.hyt.main;
import com.hyt.view.Login;
public class Chatter3 {
public static void main(String[] args) {
new Login();
}
}