《综合项目实战-局域网内的沟通软件》
《综合项目实战-局域网内的沟通软件》
以下内容全为黑马程序员的资源:
需求:
- 展示一个用户的登录界面,这个界面只要求用户输入自己聊天的昵称就可以了。
登录进入后,展示一个群聊的窗口,这个窗口,展示在线人数,展示消息展示框,消息输入框,发送按钮。可以实现群聊。实现实时展示在线人数。完全做到即时通讯功能。
技术选型
1、GUI编程技术:Swing
2、网络编程
3、面向对象设计
4、常用API
思路分析
1、创建一个模块,代表我们的项目
- 模块名取名为:itheima-chat-system.
2、拿到系统需要的界面:Swing的代码
– 登录界面:这个界面只要求用户输入自己聊天的昵称就可以了。
package com.itheima.ui;
import javax.swing.*;
import java.awt.*;
public class ChatEntryFrame extends JFrame {
private JTextField nicknameField;
private JButton enterButton;
private JButton cancelButton;
public ChatEntryFrame() {
setTitle("局域网聊天室");
setSize(350, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false); // 禁止调整大小
// 设置背景颜色
getContentPane().setBackground(Color.decode("#F0F0F0"));
// 创建主面板并设置布局
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBackground(Color.decode("#F0F0F0"));
add(mainPanel);
// 创建顶部面板
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
topPanel.setBackground(Color.decode("#F0F0F0"));
// 标签和文本框
JLabel nicknameLabel = new JLabel("昵称:");
nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
nicknameField = new JTextField(10);
nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
nicknameField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),
BorderFactory.createEmptyBorder(5, 5, 5, 5)
));
topPanel.add(nicknameLabel);
topPanel.add(nicknameField);
mainPanel.add(topPanel, BorderLayout.NORTH);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
buttonPanel.setBackground(Color.decode("#F0F0F0"));
enterButton = new JButton("进入");
enterButton.setFont(new Font("楷体", Font.BOLD, 16));
enterButton.setBackground(Color.decode("#007BFF"));
enterButton.setForeground(Color.WHITE);
enterButton.setBorderPainted(false);
enterButton.setFocusPainted(false);
cancelButton = new JButton("取消");
cancelButton.setFont(new Font("楷体", Font.BOLD, 16));
cancelButton.setBackground(Color.decode("#DC3545"));
cancelButton.setForeground(Color.WHITE);
cancelButton.setBorderPainted(false);
cancelButton.setFocusPainted(false);
buttonPanel.add(enterButton);
buttonPanel.add(cancelButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// 添加监听器
enterButton.addActionListener(e -> {
String nickname = nicknameField.getText();
if (!nickname.isEmpty()) {
// 进入聊天室逻辑
dispose(); // 关闭窗口
} else {
JOptionPane.showMessageDialog(this, "请输入昵称!");
}
});
cancelButton.addActionListener(e -> System.exit(0));
this.setVisible(true);
}
public static void main(String[] args) {
new ChatEntryFrame();
}
}
-
获取系统需要的聊天界面。
-
package com.itheima.ui; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class ClientChatFrame extends JFrame { public JTextArea smsContent = new JTextArea(23, 50); private JTextArea smsSend = new JTextArea(4, 40); public JList<String> onLineUsers = new JList<>(); private JButton sendBn = new JButton("发送"); public ClientChatFrame() { initView(); this.setVisible(true); } private void initView() { this.setSize(700, 600); this.setLayout(new BorderLayout()); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口,退出程序 this.setLocationRelativeTo(null); // 窗口居中 // 设置窗口背景色 this.getContentPane().setBackground(new Color(0xf0, 0xf0, 0xf0)); // 设置字体 Font font = new Font("SimKai", Font.PLAIN, 14); // 消息内容框 smsContent.setFont(font); smsContent.setBackground(new Color(0xdd, 0xdd, 0xdd)); smsContent.setEditable(false); // 发送消息框 smsSend.setFont(font); smsSend.setWrapStyleWord(true); smsSend.setLineWrap(true); // 在线用户列表 onLineUsers.setFont(font); onLineUsers.setFixedCellWidth(120); onLineUsers.setVisibleRowCount(13); // 创建底部面板 JPanel bottomPanel = new JPanel(new BorderLayout()); bottomPanel.setBackground(new Color(0xf0, 0xf0, 0xf0)); // 消息输入框 JScrollPane smsSendScrollPane = new JScrollPane(smsSend); smsSendScrollPane.setBorder(BorderFactory.createEmptyBorder()); smsSendScrollPane.setPreferredSize(new Dimension(500, 50)); // 发送按钮 sendBn.setFont(font); sendBn.setBackground(Color.decode("#009688")); sendBn.setForeground(Color.WHITE); // 按钮面板 JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5)); btns.setBackground(new Color(0xf0, 0xf0, 0xf0)); btns.add(sendBn); // 添加组件 bottomPanel.add(smsSendScrollPane, BorderLayout.CENTER); bottomPanel.add(btns, BorderLayout.EAST); // 用户列表面板 JScrollPane userListScrollPane = new JScrollPane(onLineUsers); userListScrollPane.setBorder(BorderFactory.createEmptyBorder()); userListScrollPane.setPreferredSize(new Dimension(120, 500)); // 中心消息面板 JScrollPane smsContentScrollPane = new JScrollPane(smsContent); smsContentScrollPane.setBorder(BorderFactory.createEmptyBorder()); // 添加所有组件 this.add(smsContentScrollPane, BorderLayout.CENTER); this.add(bottomPanel, BorderLayout.SOUTH); this.add(userListScrollPane, BorderLayout.EAST); } public static void main(String[] args) { new ClientChatFrame(); } }
3、定义一个App启动类:创建进入界面对象并展示。
public class App {
public static void main(String[] args) {
new ChatEntryFrame();
}
}
4、分析系统的整体架构。
1、开发服务端要做的事情大概有这些。
– 接收客户端的管道链接。
– 接收登录消息,接收昵称信息。
– 服务端也可能是接收客户端发送过来的群聊消息。
– 服务端存储全部在线的socket管道,以便到时候知道哪些客户端在线 ,以便为这些客户端转发消息。
– 如果服务端收到了登录消息,接收昵称,然后更新所有客户端的在线人数列表。
– 如果服务端收到了群聊消息,需要接收这个人的消息,再转发给所有客户端展示这个消息。
2、客户端界面已经准备好了。
5、先开发完整的服务端
- 第一步:创建一个服务端的项目:itheima-chat-server
- 第二步:创建一个服务端启动类,启动服务器等待客户端的链接。
public class Server {
public static void main(String[] args) {
System.out.println("启动服务端系统.....");
try {
// 1、注册端口。
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
// 2、主线程负责接受客户端的连接请求
while (true) {
// 3、调用accept方法,获取到客户端的Socket对象
System.out.println("等待客户端的连接.....");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接成功.....");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 第三步:把这个管道交给一个独立的线程来处理:以便支持很多客户端可以同时进来通信。
public class Server {
public static void main(String[] args) {
System.out.println("启动服务端系统.....");
try {
// 1、注册端口。
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
// 2、主线程负责接受客户端的连接请求
while (true) {
// 3、调用accept方法,获取到客户端的Socket对象
System.out.println("等待客户端的连接.....");
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
System.out.println("一个客户端连接成功.....");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
-
第四步:定义一个集合容器存储所有登录进来的客户端管道,以便将来群发消息给他们.
– 这个集合只需要一个记住所有的在线的客户端socket
// 定义一个Map集合,键是存储客户端的管道,值是这个管道的用户名称。
public static final Map<Socket, String> onLineSockets = new HashMap<>();
第五步、服务端线程开始接收登录消息/群聊消息
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
// 所以客户端必须声明协议发送消息
// 比如客户端先发1,代表接下来是登录消息。
// 比如客户端先发2,代表接下来是群聊消息。
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
// 所以客户端必须声明协议发送消息
// 比如客户端先发1,代表接下来是登录消息。
// 比如客户端先发2,代表接下来是群聊消息。
// 先从socket管道中接收客户端发送来的消息类型编号
DataInputStream dis = new DataInputStream(socket.getInputStream());
int type = dis.readInt();
switch (type){
case 1:
// 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
break;
case 2:
// 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
break;
case 3:
// 客户端发来了私聊消息,接下来要接收私聊消息内容,再把私聊消息转发给指定客户端。
break;
}
} catch (Exception e) {
System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
}
}
}
第六步、实现服务端的登录消息接收
package com.itheima;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Collection;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
// 所以客户端必须声明协议发送消息
// 比如客户端先发1,代表接下来是登录消息。
// 比如客户端先发2,代表接下来是群聊消息。
// 先从socket管道中接收客户端发送来的消息类型编号
DataInputStream dis = new DataInputStream(socket.getInputStream());
int type = dis.readInt(); // 1、2、3
switch (type){
case 1:
// 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
String nickname = dis.readUTF();
// 把这个登录成功的客户端socket存入到在线集合。
Server.onLineSockets.put(socket, nickname);
// 更新全部客户端的在线人数列表
updateClientOnLineUserList();
break;
case 2:
// 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
break;
case 3:
// 客户端发来了私聊消息,接下来要接收私聊消息内容,再把私聊消息转发给指定客户端。
break;
}
} catch (Exception e) {
System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除
}
}
private void updateClientOnLineUserList() {
// 更新全部客户端的在线人数列表
// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道。
// 1、拿到当前全部在线用户昵称
Collection<String> onLineUsers = Server.onLineSockets.values();
// 2、把这个集合中的所有用户都推送给全部客户端socket管道。
for (Socket socket : Server.onLineSockets.keySet()) {
try {
// 3、把集合中的所有用户名称,通过socket管道发送给客户端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2 代表发的是群聊消息
dos.writeInt(onLineUsers.size()); // 告诉客户端,接下来要发多少个用户名称
for (String onLineUser : onLineUsers) {
dos.writeUTF(onLineUser);
}
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
第七步: 接收客户端的群聊消息
线程没收一个客户端的群聊消息,就应该把这个消息转发给全部在线的客户端对应的socket管道
package com.itheima;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
// 所以客户端必须声明协议发送消息
// 比如客户端先发1,代表接下来是登录消息。
// 比如客户端先发2,代表接下来是群聊消息。
// 先从socket管道中接收客户端发送来的消息类型编号
DataInputStream dis = new DataInputStream(socket.getInputStream());
while (true) {
int type = dis.readInt(); // 1、2、3
switch (type){
case 1:
// 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
String nickname = dis.readUTF();
// 把这个登录成功的客户端socket存入到在线集合。
Server.onLineSockets.put(socket, nickname);
// 更新全部客户端的在线人数列表
updateClientOnLineUserList();
break;
case 2:
// 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
String msg = dis.readUTF();
sendMsgToAll(msg);
break;
case 3:
// 客户端发来了私聊消息,接下来要接收私聊消息内容,再把私聊消息转发给指定客户端。
break;
}
}
} catch (Exception e) {
System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除
updateClientOnLineUserList(); // 下线了用户也需要更新全部客户端的在线人数列表。
}
}
// 给全部在线socket推送当前客户端发来的消息
private void sendMsgToAll(String msg) {
// 一定要拼装好这个消息,再发给全部在线socket.
StringBuilder sb = new StringBuilder();
String name = Server.onLineSockets.get(socket);
// 获取当前时间
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");
String nowStr = dtf.format(now);
String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n")
.append(msg).append("\r\n").toString();
// 推送给全部客户端socket
for (Socket socket : Server.onLineSockets.keySet()) {
try {
// 3、把集合中的所有用户名称,通过socket管道发送给客户端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息 2 代表发的是群聊消息
dos.writeUTF(msgResult);
dos.flush(); // 刷新数据!
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void updateClientOnLineUserList() {
// 更新全部客户端的在线人数列表
// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道。
// 1、拿到当前全部在线用户昵称
Collection<String> onLineUsers = Server.onLineSockets.values();
// 2、把这个集合中的所有用户都推送给全部客户端socket管道。
for (Socket socket : Server.onLineSockets.keySet()) {
try {
// 3、把集合中的所有用户名称,通过socket管道发送给客户端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2 代表发的是群聊消息
dos.writeInt(onLineUsers.size()); // 告诉客户端,接下来要发多少个用户名称
for (String onLineUser : onLineUsers) {
dos.writeUTF(onLineUser);
}
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
6、完善整个客户端程序的代码
第一步:从登录界面开始: 完成了登录,完成了socket管道传给消息聊天界面。
给这个进入按钮绑定一个点击事件监听器,让他可以点击,一旦点击了,获取到昵称,然后立即请求与服务器端的socket管道链接。
并立即发送登录信息:发送1,发送昵称。
再展示客户端的聊天界面: 接收到了昵称,接收到了属于自己客户端的socket通信管道。
第二步:立即在消息聊天界面,立即读取客户端socket管道从服务端发来的在线人数更新消息/群聊消息
- 交给一个独立的线程专门负责读取客户端socket从服务端收到的在线人数更新数据和群聊数据。
- 收到消息,先判断消息的类型,判断是在线人数更新消息还是群聊消息,分开处理。
第三步:接收群聊消息。
- 接收消息类型 2 ,接收群聊数据,展示到界面的面板上去即可。
第四步:发送群聊消息。
- 给发送按钮绑定一个点击事件,获取输入框的消息内容,再先发送2,再把群聊内容发送给服务端就完事了。
完整代码如下:
- 服务端
package ChatServer;
public class Constant {
public static final int PORT = 8888;
}
package ChatServer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server {
// 定义一个Map集合,键是存储客户端的管道,值是这个管道的用户名称。
public static final Map<Socket, String> onLineSockets = new HashMap<>();
public static void main(String[] args) {
System.out.println("启动服务端系统.....");
try {
// 1、注册端口。
ServerSocket serverSocket = new ServerSocket(Constant.PORT);
// 2、主线程负责接受客户端的连接请求
while (true) {
// 3、调用accept方法,获取到客户端的Socket对象
System.out.println("等待客户端的连接.....");
Socket socket = serverSocket.accept();
new ServerReaderThread(socket).start();
System.out.println("一个客户端连接成功.....");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package ChatServer;
//线程
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
public void run() {
try {
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
// 所以客户端必须声明协议发送消息
// 比如客户端先发1,代表接下来是登录消息。
// 比如客户端先发2,代表接下来是群聊消息。
// 先从socket管道中接收客户端发送来的消息类型编号
DataInputStream dis = new DataInputStream(socket.getInputStream());
while (true) {
int type = dis.readInt();//先读类型编号
switch (type) {
case 1:
// 客户端发来了登录消息,接下来要接收昵称数据,再更新全部在线客户端的在线人数列表。
String nickname = dis.readUTF();//接收名称
// 把这个登录成功的客户端socket存入到 在线集合。
Server.onLineSockets.put(socket, nickname);
// 更新全部客户端的在线人数列表
updateClientOnLineUserList();
break;
case 2:
// 客户端发来了群聊消息,接下来要接收群聊消息内容,再把群聊消息转发给全部在线客户端。
String msg = dis.readUTF();//接收消息内容
sentMsgToAll(msg);
break;
case 3:
// 客户端发来了私聊消息,接下来要接收私聊消息内容,再把私聊消息转发给指定客户端。
break;
}
}
} catch (Exception e) {
System.out.println("客户端下线了:" + socket.getInetAddress().getHostAddress());
Server.onLineSockets.remove(socket);//把下线的客户端socket从在线集合中移除
updateClientOnLineUserList();//下线也更新所有人的列表
}
}
private void sentMsgToAll(String mag) {
// 一定要拼装好这个消息,再发给全部在线socket.
StringBuilder sb=new StringBuilder();//使用StringBuilder类来拼接内容
String name=Server.onLineSockets.get(socket);//得到当前socket名称
LocalDateTime now=LocalDateTime.now();//获取当前时间
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");//指定格式
String date=now.format(dtf);
String msgResult=sb.append(name).append(" ").append(date).append("\r\n").append(mag).append("\r\n").toString();//打印消息
//把这个集合中的所有用户都推送给全部客户端的Socket管道
for (Socket socket : Server.onLineSockets.keySet()) {
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(2);// 2代表告诉客户端接下来是群聊消息
dos.writeUTF(msgResult);//群聊消息内容
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void updateClientOnLineUserList() {
//更新全部客户端的在线人数列表
//拿到全部在线客户端的用户名称,把这些名称转发给全部在线的Socket管道
Collection<String> onLineUsers = Server.onLineSockets.values();
//把这个集合中的所有用户都推送给全部客户端的Socket管道
for (Socket socket : Server.onLineSockets.keySet()) {
try {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);// 1代表告诉客户端接下来是在线人数列表信息
dos.writeInt(onLineUsers.size());
for (String user : onLineUsers) {
dos.writeUTF(user);
}
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 客户端
package UI;
public class Constant {
public static final String SERVER_IP = "127.0.0.1";
public static final int SERVER_PORT = 8888;
}
package UI;
import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.net.Socket;
public class ChatEntryFrame extends JFrame {
private JTextField nicknameField;
private JButton enterButton;
private JButton cancelButton;
private Socket socket;
public ChatEntryFrame() {
setTitle("局域网聊天室");
setSize(350, 150);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false); // 禁止调整大小
// 设置背景颜色
getContentPane().setBackground(Color.decode("#F0F0F0"));
// 创建主面板并设置布局
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBackground(Color.decode("#F0F0F0"));
add(mainPanel);
// 创建顶部面板
JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
topPanel.setBackground(Color.decode("#F0F0F0"));
// 标签和文本框
JLabel nicknameLabel = new JLabel("昵称:");
nicknameLabel.setFont(new Font("楷体", Font.BOLD, 16));
nicknameField = new JTextField(10);
nicknameField.setFont(new Font("楷体", Font.PLAIN, 16));
nicknameField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY),
BorderFactory.createEmptyBorder(5, 5, 5, 5)
));
topPanel.add(nicknameLabel);
topPanel.add(nicknameField);
mainPanel.add(topPanel, BorderLayout.NORTH);
// 按钮面板
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 10));
buttonPanel.setBackground(Color.decode("#F0F0F0"));
enterButton = new JButton("进入");
enterButton.setFont(new Font("楷体", Font.BOLD, 16));
enterButton.setBackground(Color.decode("#007BFF"));
enterButton.setForeground(Color.WHITE);
enterButton.setBorderPainted(false);
enterButton.setFocusPainted(false);
cancelButton = new JButton("取消");
cancelButton.setFont(new Font("楷体", Font.BOLD, 16));
cancelButton.setBackground(Color.decode("#DC3545"));
cancelButton.setForeground(Color.WHITE);
cancelButton.setBorderPainted(false);
cancelButton.setFocusPainted(false);
buttonPanel.add(enterButton);
buttonPanel.add(cancelButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
// 添加监听器
enterButton.addActionListener(e -> {
String nickname = nicknameField.getText();
nicknameField.setText("");
if (!nickname.isEmpty()) {
try {
login(nickname);
// 进入聊天室逻辑,把名称传给聊天界面,通信管道也要传给聊天界面
new ClientChatFrame(nickname,socket);
this.dispose(); // 关闭窗口
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
JOptionPane.showMessageDialog(this, "请输入昵称!");
}
});
cancelButton.addActionListener(e -> System.exit(0));
this.setVisible(true);
}
private void login(String name) throws Exception {
//立即发送登录消息给服务端程序
//请求一个socket管道建立与服务端的连接
socket = new Socket(Constant.SERVER_IP,Constant.SERVER_PORT);
//立即发送消息类型和名称
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
dos.writeInt(1);
dos.writeUTF(name);
dos.flush();
}
}
package UI;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
public class ClientChatFrame extends JFrame {
public JTextArea smsContent = new JTextArea(23, 50);
private JTextArea smsSend = new JTextArea(4, 40);
public JList<String> onLineUsers = new JList<>();
private JButton sendBn = new JButton("发送");
private Socket socket;
private String name;
public ClientChatFrame() {
initView();
this.setVisible(true);
}
public ClientChatFrame(String name, Socket socket) {
this();//调用上面的无参构造器,初始化界面信息
//初始化数据
//立马展示名称到窗口
this.setTitle(name+"的聊天窗口");
this.socket = socket;
//立即把客户端的这个Socket管道交给一个独立的线程专门负责读取客户端socket从服务端收到的在线人数更新数据和群聊数据。
new ClientReaderThread(socket,this).start();
}
private void initView() {
this.setSize(750, 600);
this.setLayout(new BorderLayout());
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口,退出程序
this.setLocationRelativeTo(null); // 窗口居中
// 设置窗口背景色
this.getContentPane().setBackground(new Color(0xf0, 0xf0, 0xf0));
// 设置字体
Font font = new Font("SimKai", Font.PLAIN, 14);
// 消息内容框
smsContent.setFont(font);
smsContent.setBackground(new Color(0xdd, 0xdd, 0xdd));
smsContent.setEditable(false);
// 发送消息框
smsSend.setFont(font);
smsSend.setWrapStyleWord(true);
smsSend.setLineWrap(true);
// 在线用户列表
onLineUsers.setFont(font);
onLineUsers.setFixedCellWidth(120);
onLineUsers.setVisibleRowCount(13);
// 创建底部面板
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.setBackground(new Color(0xf0, 0xf0, 0xf0));
// 消息输入框
JScrollPane smsSendScrollPane = new JScrollPane(smsSend);
smsSendScrollPane.setBorder(BorderFactory.createEmptyBorder());
smsSendScrollPane.setPreferredSize(new Dimension(500, 50));
// 发送按钮
sendBn.setFont(font);
sendBn.setBackground(Color.decode("#009688"));
sendBn.setForeground(Color.WHITE);
// 按钮面板
JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 5));
btns.setBackground(new Color(0xf0, 0xf0, 0xf0));
btns.add(sendBn);
//给发送按钮绑定点击事件
sendBn.addActionListener(e->{
//获取输入框中的内容
String msg=smsSend.getText();
//清空输入框
smsSend.setText("");
//发送消息
sentMsgToServer(msg);
});
// 添加组件
bottomPanel.add(smsSendScrollPane, BorderLayout.CENTER);
bottomPanel.add(btns, BorderLayout.EAST);
// 用户列表面板
JScrollPane userListScrollPane = new JScrollPane(onLineUsers);
userListScrollPane.setBorder(BorderFactory.createEmptyBorder());
userListScrollPane.setPreferredSize(new Dimension(120, 500));
// 中心消息面板
JScrollPane smsContentScrollPane = new JScrollPane(smsContent);
smsContentScrollPane.setBorder(BorderFactory.createEmptyBorder());
// 添加所有组件
this.add(smsContentScrollPane, BorderLayout.CENTER);
this.add(bottomPanel, BorderLayout.SOUTH);
this.add(userListScrollPane, BorderLayout.EAST);
}
//发送消息给服务器
private void sentMsgToServer(String msg) {
try {
DataOutputStream dos=new DataOutputStream(socket.getOutputStream());
//把消息发给服务器
dos.writeInt(2);
dos.writeUTF(msg);
dos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ClientChatFrame();
}
public void updateOnlineUsers(String[] onLineNames) {
//把线程读取到的在线用户名称展示到界面上
onLineUsers.setListData(onLineNames);
}
public void setMsgToWin(String msg) {
//更新群聊消息到界面展示
smsContent.append(msg);
}
}
package UI;
//线程
import java.io.DataInputStream;
import java.net.Socket;
public class ClientReaderThread extends Thread {
private Socket socket;
private DataInputStream dis;
private ClientChatFrame win;
public ClientReaderThread(Socket socket, ClientChatFrame win) {
this.socket = socket;
this.win = win;
}
public void run() {
try {
// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息
dis = new DataInputStream(socket.getInputStream());
while (true) {
int type = dis.readInt();//先读类型编号
switch (type) {
case 1:
// 服务端发来了在线人数更新信息登录消息
updateClientOnLineUserList();
break;
case 2:
// 服务端发来了群聊消息
getMsgToWin();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void getMsgToWin() throws Exception {
//获取群聊消息
String msg=dis.readUTF();
win.setMsgToWin(msg);
}
//更新全部客户端的在线人数列表
private void updateClientOnLineUserList() throws Exception {
//读取有多少个在线用户
int count = dis.readInt();
String[] onLineNames= new String[count];
//循环控制读取多少个用户信息
for (int i = 0; i < count; i++) {
String nickname = dis.readUTF();
//将每个用户添加到集合中
onLineNames[i]=nickname;
}
//将集合中都数据展示到窗口上
win.updateOnlineUsers(onLineNames);
}
}
- 启动客户端
import UI.ChatEntryFrame;
public class APP {
public static void main(String[] args) {
new ChatEntryFrame();
}
}
浙公网安备 33010602011771号