当前位置: 首页 > news >正文

通信网络编程3.0——JAVA

 主要添加了私聊功能

1服务器类定义与成员变量

public class ChatServer {int port = 6666;// 定义服务器端口号为 6666ServerSocket ss;// 定义一个 ServerSocket 对象用于监听客户端连接//List<Socket> clientSockets = new ArrayList<>();// 定义一个列表用于存储已连接的客户端 Socket 对象List<Socket> clientSockets = new CopyOnWriteArrayList<>();List<String> clientNames = new ArrayList<>(10);//迭代时会复制整个底层数组,因此在遍历过程中其他线程对集合的修改不会影响当前遍历,// 有效避免了 ConcurrentModificationException 异常。
}

  • 端口号:服务器的端口号被定义为 6666,客户端需要知道这个端口号才能与服务器建立连接。
  • ServerSocket 对象ServerSocket 是 Java 提供的一种用于服务器端编程的类,它负责监听特定端口上的客户端连接请求。一旦有客户端连接,就会创建一个新的 Socket 对象来处理与该客户端之间的通信。
  • 客户端套接字列表clientSockets 使用了 CopyOnWriteArrayList 来存储与客户端建立连接的 Socket 对象。这种数据结构在遍历时会复制整个底层数组,因此在遍历过程中其他线程对集合的修改不会影响当前遍历,有效避免了 ConcurrentModificationException 异常。
  • 客户端名称列表clientNames 用于存储每个连接客户端的名称,方便在消息中区分不同的客户端。

2初始化服务器

public void initServer() {// 初始化服务器的方法try {ss = new ServerSocket(port);// 创建 ServerSocket 对象并绑定到指定端口System.out.println("服务器启动,等待客户端连接...");} catch (IOException e) {throw new RuntimeException(e);}}
  •  在服务器初始化方法 initServer 中,通过调用 ServerSocket 的构造函数并传入端口号,创建了一个 ServerSocket 对象,绑定了服务器到指定端口,使服务器能够监听该端口上的客户端连接请求。一旦初始化成功,就会打印出 "服务器启动,等待客户端连接..." 的提示信息,告知服务器已正常启动并处于监听状态。

3监听客户端连接

public void listenerConnection() {// 监听客户端连接的方法,返回连接的 Socket 对象new Thread(()->{while(true){try {Socket socket = ss.accept();// 调用 accept() 方法等待客户端连接clientNames.add("Hello");//clientSockets.add(socket);synchronized (clientSockets) {// 同步操作确保线程安全clientSockets.add(socket);// 将连接的客户端 Socket 对象添加到列表中}System.out.println("客户端已连接:" + socket.getInetAddress().getHostAddress());// 输出客户端连接成功提示信息及客户端 IP 地址} catch (IOException e) {throw new RuntimeException(e);}}}).start();}
  • listenerConnection 方法创建了一个新线程,用于不断地监听客户端的连接请求。在循环中,调用 ss.accept() 方法阻塞等待客户端的连接。一旦有客户端连接,就会创建一个新的 Socket 对象,并将其添加到 clientSockets 列表中。同时,向 clientNames 列表中添加一个默认客户端名称 "Hello",后续可以根据实际情况更新该名称。每当有新的客户端连接成功,就会打印出客户端的 IP 地址,表明该客户端已成功连接到服务器。

4读取客户端消息

public void readMsg(List<Socket> clientSockets, JTextArea msgShow) {// 读取客户端消息的方法//System.out.println("clientSockets size: " + clientSockets.size()); // 检查列表大小synchronized (clientSockets) {// 对客户端列表进行同步操作Thread tt = new Thread(() -> {// 创建一个线程用于读取并处理客户端消息//System.out.println("开始读取客户端发送的消息");while (true) {// 无限循环持续读取消息InputStream is;// 定义输入流对象用于读取客户端消息Socket socket = null;try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}for (Socket cSocket : clientSockets) {// 遍历每个客户端 Socket//System.out.println("循环每个socket");socket = cSocket;if(socket == null){continue;}try {is = socket.getInputStream();// 获取客户端 Socket 对象的输入流} catch (IOException e) {throw new RuntimeException(e);}try {int idLen = is.read();// 读取消息中发送方名称长度的字节if(idLen == 0){continue;}byte[] id = new byte[idLen];// 根据读取的长度创建字节数组存储发送方名称is.read(id);// 读取发送方名称字节数组int si = clientSockets.indexOf(socket);clientNames.set(si,new String(id));int msgLen = is.read();// 读取消息内容长度的字节if(msgLen == 0){continue;}byte[] msg = new byte[msgLen];// 根据读取的长度创建字节数组存储消息内容is.read(msg);// 读取消息内容字节数组String clientmsg = new String(msg);//先判断@有没有int start = clientmsg.indexOf('@');if(start != -1){int end = clientmsg.indexOf(' ');String friendID = clientmsg.substring(start+1,end);String message = clientmsg.substring(end);System.out.println(new String(id) + "发送给" + friendID + " 的一条私聊消息:" + message);// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(id) + "发送给" + friendID + " 的一条私聊消息:" + message + "\n");for (Socket clientSocket : clientSockets) {// 遍历所有已连接的客户端 Socket 对象if (clientSocket == socket) {// 如果是当前发送消息的客户端continue;}int s = clientSockets.indexOf(clientSocket);String name = clientNames.get(s);if(name.equals(friendID)){OutputStream os = null;// 定义输出流对象用于向其他客户端发送消息os = clientSocket.getOutputStream();// 获取客户端 Socket 对象的输出流os.write(id.length);// 发送发送方名称长度os.write(id);// 发送发送方名称字节数组message += "(这是一条私聊消息)";os.write(message.getBytes().length);// 发送消息内容长度os.write(message.getBytes());// 发送消息内容字节数组os.flush();// 刷新输出流确保数据发送完成break;}}}else {System.out.println(new String(id) + "发送的消息:" + new String(msg));// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(id) + "说:" + new String(msg) + "\n");// 转发信息给所有其他客户端for (Socket clientSocket : clientSockets) {// 遍历所有已连接的客户端 Socket 对象if (clientSocket == socket) {// 如果是当前发送消息的客户端continue;}OutputStream os = null;// 定义输出流对象用于向其他客户端发送消息os = clientSocket.getOutputStream();// 获取客户端 Socket 对象的输出流os.write(id.length);// 发送发送方名称长度os.write(id);// 发送发送方名称字节数组os.write(msg.length);// 发送消息内容长度os.write(msg);// 发送消息内容字节数组os.flush();// 刷新输出流确保数据发送完成}}} catch (IOException e) {throw new RuntimeException(e);}}}});tt.start();}}
  • readMsg 方法的核心功能是读取客户端发送的消息,并根据消息类型(普通消息或私聊消息)进行相应的处理和转发。
  • 首先,创建一个新线程 tt,在无限循环中依次检查 clientSockets 列表中的每个 Socket 对象。对于每个客户端 Socket,通过 socket.getInputStream() 获取输入流,从而读取客户端发送的数据。
  • 客户端发送的数据格式预先约定为:首先是一个字节表示发送方名称的长度,然后是发送方名称对应的字节数组;接下来是一个字节表示消息内容长度,最后是消息内容对应的字节数组。按照这种格式,先读取发送方名称长度 idLen,根据该长度创建字节数组 id 读取发送方名称,再读取消息内容长度 msgLen,创建字节数组 msg 读取消息内容。
  • 随后,将读取到的发送方名称更新到对应的 clientNames 列表位置。接下来对消息内容进行判断,如果消息内容中包含 "@" 符号,则认为这是一条私聊消息。通过解析消息内容,获取私聊目标的名称 friendID 和实际消息内容 message,然后在 clientSockets 中查找对应的私聊目标客户端 Socket,并将私聊消息发送给该目标客户端。
  • 如果消息内容中不包含 "@" 符号,则认为这是一条普通消息,直接将消息转发给除发送方外的所有其他客户端。
  • 无论是普通消息还是私聊消息,都通过输出流 OutputStream 将消息发送给目标客户端。发送时,同样按照约定的数据格式,先发送发送方名称的长度和名称字节数组,再发送消息内容的长度和内容字节数组,并调用 os.flush() 刷新输出流确保数据发送完成。

5服务器启动

public void start() {// 启动服务器的方法initServer();// 调用初始化服务器的方法//new Thread(()->{//startSend();// 启动服务端从控制台向所有客户端发送消息的线程//}).start();ChatUI ui = new ChatUI("服务端", clientSockets);ui.setVisible(true); // 确保 UI 可见listenerConnection();// 调用监听客户端连接的方法readMsg(clientSockets,ui.msgShow);// 调用读取消息的方法}
  • start 方法中,首先调用 initServer 方法初始化服务器,然后创建一个 ChatUI 对象作为服务器端的用户界面(假设存在 ChatUI 类,用于展示聊天内容等信息),使用户界面可见。接着调用 listenerConnection 方法启动监听客户端连接的线程,最后调用 readMsg 方法启动读取消息的线程,并将读取到的消息显示在用户界面中。

6主方法

public static void main(String[] args) {ChatServer server = new ChatServer();// 创建 ChatServer 对象server.start();// 调用启动服务器的方法}
  • main 方法作为程序的入口,创建了一个 ChatServer 对象,并调用其 start 方法启动服务器。 

7其他模块代码不变

public class Client {Socket socket;// 定义 Socket 对象用于与服务器建立连接String ip;// 定义服务器 IP 地址int port;// 定义服务器端口号InputStream in;// 定义输入流对象用于读取服务器发送的消息OutputStream out;// 定义输出流对象用于向服务器发送消息public Client(String ip, int port) {// 构造方法,初始化客户端 IP 地址和端口号this.ip = ip;this.port = port;}public void connectServer(String userName) {// 连接服务器的方法try {socket = new Socket(ip, port);// 创建 Socket 对象连接到指定 IPin = socket.getInputStream();// 获取 Socket 对象的输入流用于读取消息out = socket.getOutputStream();// 获取 Socket 对象的输出流用于发送消息try {out.write(userName.length());out.write(userName.getBytes());out.flush();} catch (IOException e) {throw new RuntimeException(e);}System.out.println("连接服务器成功");} catch (IOException e) {throw new RuntimeException(e);}}public void readMsg(JTextArea msgShow) {// 读取服务器发送的消息的方法new Thread(() -> {// 创建一个线程用于读取并处理服务器消息try {System.out.println("开始读取消息");while (true) { // 无限循环持续读取消息int senderNameLength = in.read();// 读取发送方名称长度的字节byte[] senderNameBytes = new byte[senderNameLength];// 根据读取的长度创建字节数组存储发送方名称in.read(senderNameBytes);// 读取发送方名称字节数组int msgLength = in.read();// 读取消息内容长度的字节byte[] msgBytes = new byte[msgLength];// 根据读取的长度创建字节数组存储消息内容in.read(msgBytes);// 读取消息内容字节数组System.out.println(new String(senderNameBytes) + "发送的消息:" + new String(msgBytes));// 将字节数组转换为字符串并输出消息内容msgShow.append(new String(senderNameBytes) +"说:" + new String(msgBytes) + "\n");}} catch (IOException e) {throw new RuntimeException(e);}}).start();}/*public void startSend() {// 从控制台向服务器发送消息的方法new Thread(() -> {// 创建一个线程用于读取控制台输入并发送消息try {BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));// 创建缓冲读取器读取控制台输入System.out.println("请输入用户名:");String userName = reader.readLine();// 读取一行控制台输入作为用户名System.out.println("请输入消息(按回车发送):");while (true) {// 无限循环持续读取并发送消息String msg = reader.readLine();// 读取一行控制台输入作为消息内容if (msg != null && !msg.isEmpty()) {// 如果输入的消息不为空out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名字节数组out.write(msg.getBytes().length);// 发送消息内容长度out.write(msg.getBytes());// 发送消息内容字节数组out.flush();// 刷新输出流确保数据发送完成}}} catch (IOException e) {throw new RuntimeException(e);}}).start();}*/public void startClient() {// 启动客户端的方法String userName = JOptionPane.showInputDialog("请输入用户名:");connectServer(userName);// 调用连接服务器的方法ChatUI ui = new ChatUI(userName, out);readMsg(ui.msgShow);// 调用读取消息的方法//startSend();// 调用发送消息的方法try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread() {public void run() {while (true) {try {out.write(0);out.flush();Thread.sleep(500);} catch (IOException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start();}public static void main(String[] args) {Client client = new Client("127.0.0.1", 6666);// 创建 Client 对象,连接到本地主机的 6666 端口client.startClient();// 调用启动客户端的方法}
}
public class ChatUI extends JFrame {public JTextArea msgShow = new JTextArea();// 显示消息的文本区域public ChatUI(String title, List<Socket> clientSockets) {// 服务器端构造方法super(title);// 设置窗口标题setSize(500, 500);// 设置窗口大小setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 设置关闭操作JScrollPane scrollPane = new JScrollPane(msgShow);// 创建滚动面板包括消息显示区域scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);// 添加到窗口北部// 创建消息输入面板及组件JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("发送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);// 添加到窗口南部setVisible(true);ChatListener cl = new ChatListener();// 创建事件监听器send.addActionListener(cl);// 为发送按钮添加监听器cl.showMsg = msgShow;// 传递消息显示组件cl.msgInput = msg;cl.userName = title;cl.clientSockets = clientSockets;}public ChatUI(String title, OutputStream out) {// 客户端构造方法super(title);setSize(500, 500);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);JScrollPane scrollPane = new JScrollPane(msgShow);scrollPane.setPreferredSize(new Dimension(0, 350));add(scrollPane, BorderLayout.NORTH);JPanel msgInput = new JPanel();JTextArea msg = new JTextArea();JScrollPane scrollPane1 = new JScrollPane(msg);scrollPane1.setPreferredSize(new Dimension(480, 80));msgInput.add(scrollPane1);JButton send = new JButton("发送");msgInput.add(send);msgInput.setPreferredSize(new Dimension(0, 120));add(msgInput, BorderLayout.SOUTH);setVisible(true);clientListener cl = new clientListener();send.addActionListener(cl);cl.showMsg = msgShow;cl.msgInput = msg;cl.userName = title;cl.out = out;}
}
public class ChatListener implements ActionListener {public List<Socket> clientSockets;// 客户端 Socket 列表JTextArea showMsg;// 消息显示区域JTextArea msgInput;// 消息输入区域String userName;// 用户名OutputStream out;// 输出流public void actionPerformed(ActionEvent e) {// 处理发送按钮点击事件String text = msgInput.getText();// 获取输入的消息文本showMsg.append(userName + ": " + text + "\n");// 在显示区域追加消息for (Socket cSocket : clientSockets) {// 遍历所有客户端Socket socket = cSocket;try {out = socket.getOutputStream();// 获取客户端输出流out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名out.write(text.getBytes().length);// 发送消息内容长度out.write(text.getBytes());// 发送消息内容out.flush();// 刷新输出流} catch (IOException ex) {throw new RuntimeException(ex);}}}}
public class clientListener implements ActionListener {JTextArea showMsg;// 消息显示区域JTextArea msgInput;// 消息输入区域String userName;// 用户名OutputStream out;// 输出流public void actionPerformed(ActionEvent e) {// 处理发送按钮点击String text = msgInput.getText();// 获取输入消息showMsg.append(userName + ": " + text + "\n");// 显示消息try {out.write(userName.getBytes().length);// 发送用户名长度out.write(userName.getBytes());// 发送用户名out.write(text.getBytes().length);// 发送消息长度out.write(text.getBytes());// 发送消息内容out.flush();// 刷新输出流//msgInput.setText(""); // 清空输入框} catch (IOException ex) {throw new RuntimeException(ex);}}
}

8运行效果

http://www.xdnf.cn/news/1066411.html

相关文章:

  • Spring Cloud微服务
  • Java面试题027:一文深入了解数据库Redis(3)
  • 【软考高级系统架构论文】论数据分片技术及其应用
  • Redis中的bigkey的介绍及影响
  • 安全再升级! 正也科技通过信息安全等级保护三级备案
  • 七八章习题测试
  • 高级版 Web Worker 封装(含 WorkerPool 调度池 + 超时控制)
  • 本地文件深度交互新玩法:Obsidian Copilot的深度开发
  • 能耗管理新革命:物联网实现能源高效利用
  • 小学期前端三件套学习(更新中)
  • 开启游戏新时代:神经网络渲染技术实现重大跨越
  • 【Torch】nn.GRU算法详解
  • 前端跨域解决方案(7):Node中间件
  • 容器技术入门与Docker环境部署指南
  • asp.net core Razor动态语言编程代替asp.net .aspx更高级吗?
  • 如何在 Vue 应用中嵌入 ONLYOFFICE 编辑器
  • LED-Merging: 无需训练的模型合并框架,兼顾LLM安全和性能!!
  • WebSocket长连接在小程序中的实践:消息推送与断线重连机制设计
  • 运维打铁: Windows 服务器基础运维要点解析
  • 详解HarmonyOS NEXT仓颉开发语言中的全局弹窗
  • AI编程再突破,文心快码发布行业首个多模态、多智能体协同AI IDE
  • vue3整合element-plus
  • WebSocket快速入门
  • 卓易通是什么
  • 深度学习:PyTorch卷积神经网络(CNN)之图像入门
  • 【软考高级系统架构论文】论企业集成平台的理解与应用
  • Spring Boot 使用 ElasticSearch
  • 大数据时代UI前端的变革:从静态展示到动态交互
  • ISCSI存储
  • FreeRTOS 介绍、使用方法及应用场景