目录
案例要求:
实现思路:
itheima-chat-server包
src
com.itheima
Constant类:
Server类:
ServerReaderThread类:
itheima-chat-system包
src
com.itheima.ui
ChatEntryFrame类:
ClientChatFrame类:
ClientReaderThread类:
Constant类:
APP启动类:
总结:
案例要求:
实现思路:
itheima-chat-server包
src
com.itheima
Constant类:
package com.itheima;public class Constant {public static final int PORT = 6666;
}
Server类:
package com.itheima;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();}}
}
ServerReaderThread类:
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;}@Overridepublic void run() {try {// 接收的消息可能有很多种类型:1、登录消息(包含昵称) 2、群聊消息 3、私聊消息// 所以客户端必须声明协议发送消息// 比如客户端先发1,代表接下来是登录消息。// 比如客户端先发2,代表接下来是群聊消息。// 先从socket管道中接收客户端发送来的消息类型编号DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1、2、3switch (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();// 推送给全部客户端socketfor (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();}}}
}
itheima-chat-system包
src
com.itheima.ui
ChatEntryFrame类:
package com.itheima.ui;import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
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); // 显示窗口}public void login(String nickname) throws Exception {// 立即发送登录消息给服务端程序。// 1、创建Socket管道请求与服务端的socket链接socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );// 2、立即发送消息类型1 和自己的昵称给服务端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 消息类型 登录dos.writeUTF(nickname);dos.flush();}public static void main(String[] args) {new ChatEntryFrame();}
}
ClientChatFrame类:
package com.itheima.ui;import javax.swing.*;
import java.awt.*;
import java.io.DataOutputStream;
import java.io.IOException;
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); // 显示窗口}public void login(String nickname) throws Exception {// 立即发送登录消息给服务端程序。// 1、创建Socket管道请求与服务端的socket链接socket = new Socket(Constant.SERVER_IP, Constant.SERVER_PORT );// 2、立即发送消息类型1 和自己的昵称给服务端DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dos.writeInt(1); // 消息类型 登录dos.writeUTF(nickname);dos.flush();}public static void main(String[] args) {new ChatEntryFrame();}
}
ClientReaderThread类:
package com.itheima.ui;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class ClientReaderThread extends Thread{private Socket socket;private DataInputStream dis;private ClientChatFrame win;public ClientReaderThread( Socket socket, ClientChatFrame win) {this.win = win;this.socket = socket;}@Overridepublic void run() {try {// 接收的消息可能有很多种类型:1、在线人数更新的数据 2、群聊消息dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1、2、3switch (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 {// 1、读取有多少个在线用户int count = dis.readInt();// 2、循环控制读取多少个用户信息。String[] names = new String[count];for (int i = 0; i < count; i++) {// 3、读取每个用户的信息String nickname = dis.readUTF();// 4、将每个用户的信息添加到数组names[i] = nickname;}// 5、将集合中的数据展示到窗口上win.updateOnlineUsers(names);}}
Constant类:
package com.itheima.ui;public class Constant {public static final String SERVER_IP = "127.0.0.1";public static final int SERVER_PORT = 6666;
}
APP启动类:
import com.itheima.ui.ChatEntryFrame;public class App {public static void main(String[] args) {new ChatEntryFrame(); // 启动登录界面}
}