一、聊天室的功能
服务器端
1.启动服务器,在服务器循环监听客户端
2.把循环接收的多个客户端Socket对象存储起来(Collection)
3.在服务器每个socket都要监听各自客户端发送的消息
4.在某个客户端发送了消息,服务器将此消息转发给其他客户端
客户端
1.用户登录(需要账号不为空即可),创建Socket----->未实现与数据库的连接(JDBC)
2.若Socket创建成功,打开聊天窗口
3.输入内容,点击发送按钮发送消息
4.客户端监听服务器端发送回来的消息并把消息显示出来
二、示例代码
创建一个新项目chatRoom:其中包含Client和Server两个Module
在这两个包中的src中分别创建新的包实现不同的功能:
Client
ChatFrame
package com.ffyc.chatroomclient;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ChatFrame extends JFrame {
JTextArea chatArea;
public ChatFrame(String account, Socket socket) throws IOException {
//创建向客户端发送数据的数据输出流
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
// 创建主窗口
JFrame frame = new JFrame("欢迎"+account+"加入聊天");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
// 创建公告面板
JPanel announcementPanel = new JPanel();
announcementPanel.setLayout(new BoxLayout(announcementPanel, BoxLayout.Y_AXIS));
announcementPanel.setPreferredSize(new Dimension(300, 0)); // 设置公告面板的宽度
announcementPanel.setBorder(BorderFactory.createTitledBorder("公告"));
// 创建聊天窗口
chatArea = new JTextArea();
chatArea.setEditable(false);
JScrollPane chatScrollPane = new JScrollPane(chatArea);
chatScrollPane.setBorder(BorderFactory.createTitledBorder("聊天"));
chatArea.setEditable(false);
// 创建输入区域
JPanel inputPanel = new JPanel();
inputPanel.setLayout(new BorderLayout());
JTextArea inputArea = new JTextArea(5, 20);
inputArea.setLineWrap(true);
JScrollPane jScrollPane = new JScrollPane(inputArea);
JButton sendButton = new JButton("发送");
inputPanel.add(jScrollPane, BorderLayout.CENTER);
inputPanel.add(sendButton, BorderLayout.EAST);
// 将组件添加到主窗口
frame.add(announcementPanel, BorderLayout.WEST);
frame.add(chatScrollPane, BorderLayout.CENTER);
frame.add(inputPanel, BorderLayout.SOUTH);
// 设置窗口大小并显示
frame.setSize(800, 600);
frame.setVisible(true);
//来到聊天窗口后,可以开启一个线程监听服务器发送的消息
new ClientThread(socket).start();
//为发送按钮添加事件监听
sendButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String message = inputArea.getText();
if (message.length() == 0) {
JOptionPane.showMessageDialog(null, "输入内容不能为空!");
return;
} //不为空,向服务器端发送消息
String msg = account+" "+new SimpleDateFormat("HH:mm:ss").format(new Date())+"\n";
msg+=message;
try {
dataOutputStream.writeUTF(msg);
inputArea.setText("");//清空发送内容
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null,"内容发送失败,请检查网络!");
}
}
});
}
//线程监听服务器发送的消息
class ClientThread extends Thread{
boolean f = true;
DataInputStream dataInputStream;
public ClientThread(Socket socket) throws IOException {
dataInputStream = new DataInputStream(socket.getInputStream());
}
@Override
public void run() {
while (f){
try {
String msg = dataInputStream.readUTF();
chatArea.append(msg+"\n"); //显示聊天内容
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器关闭");
f = false;
}
}
}
}
}
LoginFrame
package com.ffyc.chatroomclient;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.Socket;
import java.sql.Driver;
import java.sql.DriverManager;
public class LoginFrame extends JFrame {
public LoginFrame() {
this.setSize(350, 400);//设置窗口大小
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭窗口时退出程序
this.setLocationRelativeTo(null);//设置窗口位置水平垂直居中
this.setTitle("欢迎登陆");//设置窗口标题
this.setResizable(true);//设置窗口为不可调整大小
//创建登录面板
JPanel allPanel = new JPanel(new GridLayout(4, 1, 5, 10));
//欢迎面板
JPanel welcomePanel = new JPanel();
JLabel welcomeLabel = new JLabel("网络聊天室登录:");
welcomePanel.add(welcomeLabel);
//账户面板
JPanel accountPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 40, 20));
JLabel accountLabel = new JLabel("账户");
JTextField accountTextField = new JTextField(15);
accountPanel.add(accountLabel);
accountPanel.add(accountTextField);
//密码面板
JPanel passwordPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 40, 20));
JLabel passwordLabel = new JLabel("密码");
JTextField passwordTextField = new JTextField(15);
passwordPanel.add(passwordLabel);
passwordPanel.add(passwordTextField);
//注册面板
JPanel registerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 40, 20));
JButton loginButton = new JButton("登录");
JButton loginButton1 = new JButton("注册");
registerPanel.add(loginButton);
registerPanel.add(loginButton1);
//把面板添加到总面板中
allPanel.add(welcomePanel);
allPanel.add(accountPanel);
allPanel.add(passwordPanel);
allPanel.add(registerPanel);
//将面板添加到窗口中
this.add(allPanel);
this.setVisible(true);//设置窗口可视化,放在最后一行
//为登录按钮添加事件监听
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//创建账号和密码
String account = accountTextField.getText();
if (account.length() == 0) {
JOptionPane.showMessageDialog(null, "账号不能为空!");
return;
}
String password = passwordTextField.getText();
if (password.length() == 0) {
JOptionPane.showMessageDialog(null, "密码不能为空!");
return;
}
//后期预留与数据库交互
//创建Socket
try {
Socket socket = new Socket("127.0.0.1", 9999);
new ChatFrame(account,socket);//创建聊天窗口
dispose();//释放聊天窗口
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(null, "服务器连接失败,请稍后再试");
}
}
});
}
}
Run
package com.ffyc.chatroomclient;
public class Run {
public static void main(String[] args) {
LoginFrame lf = new LoginFrame();
}
}
Server
ServerDemo
package com.ffyc.chatroomserver;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class ServerDemo {
//创建集合接收客户端口
ArrayList<Socket> sockets = new ArrayList<>();
public void startServer() {
//创建服务器端
try {
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器启动成功");
//监听客户端
while (true) {
Socket socket = serverSocket.accept();
sockets.add(socket);
System.out.println("有客户端连接,当前连接人数:" + sockets.size());
//为每一个连接到服务器的客户端开启一个线程
new SocketThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器启动失败!");
}
}
//创建一个内部类,继承Thread类,用于监听自己客户端有没有发送消息
class SocketThread extends Thread {
boolean f = true;
Socket socket;
DataInputStream dataInputStream;
public SocketThread(Socket socket) throws IOException {
this.socket = socket;
dataInputStream = new DataInputStream(socket.getInputStream());
}
@Override
public void run() {
while (f) {//监听客户端发送的消息
try {
String msg = dataInputStream.readUTF();
//向不同的客户端发送消息
for (Socket s:sockets){//遍历Socket集合
DataOutputStream dataOutputStream = new DataOutputStream(s.getOutputStream());
dataOutputStream.writeUTF(msg);
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户端下线了");
sockets.remove(socket);
f = false;
}
}
}
}
}
ServerRun
package com.ffyc.chatroomserver;
public class ServerRun {
public static void main(String[] args) {
new ServerDemo().startServer();
}
}
三、在做聊天室时遇到的问题
Client中的Run()不能同时多次运行
解决办法:找到idea2023中的Allow parallel run(允许同时运行)
1.点击Edit Configurations...
2.点击Modify options
3.勾选Allow mutiple instances
最后点击OK,问题就解决了