🔍 登录 注册

基于BIO模式下的即时通信服务端的实现

大 大苹果 2023-12-14 linux 👁 105 ⏱ 8min

功能说明

  • 客户端登录功能

    • 可以启动客户端进行登录。
    • 客户登录需要输入ip和用户名么。
  • 在线人数实时更新

    • 客户端用户登录以后,同步更新所有客户端联系人信息栏。
  • 离线人数更新

    • 检测到有客服端下线后,需要同步更新所有客户端联系人信息栏。
  • 群聊

    • 任意客户端发送的消息,可以推送给当前所有客户端接收。
  • 私聊

    • 可以选择某个员工,点击私聊按钮,然后发出的消息可以被该客户端单独接收。
  • @消息

    • 可以选择某个员工,然后发出的消息可以@该用户,但是其他人也可以看到
  • 消息用户和消息时间点

    • 服务端可以实时记录该用户的消息时间点,然后进行消息的多路转发。

服务端实现

  • 服务端代码

    package com.learnclub.chat;
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashMap;
    import java.util.Map;
    import com.learnclub.utils.Constants;
    public class ServerChat {
    	// 用于存储所有的在线客户端socket及用户名
    	public static Map<Socket, String> onLineSockets = new HashMap<Socket, String>();
    	public static void main(String[] args) {
    		try {
    			// 监听指定端口
    			ServerSocket ss = new ServerSocket(Constants.PORT);
    			while(true) {
    				// 与客户端建立连接
    				Socket socket = ss.accept();
    				为每个客户端socket创建一个线程处理
    				new ServerReaderThread(socket).start();
    			}
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    
    
  • 线程处理客户端消息的转发与用户在线的实时维护实现代码

    package com.learnclub.chat;
    
    import java.io.DataInputStream;
    import java.io.DataOutputStream;
    import java.io.IOException;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.Collection;
    import java.util.Set;
    
    import com.learnclub.utils.Constants;
    // 线程实现
    public class ServerReaderThread extends Thread {
    	private Socket socket;
    
    	public ServerReaderThread(Socket socket) {
    		this.socket = socket;
    	}
    
    	public void run() {
    		DataInputStream dis = null;
    		try {
    			// 从socket中获取当前客户端输入流
    			dis = new DataInputStream(socket.getInputStream());
    			while(true) {
    				// 获取标识
    				int flag = dis.readInt();
    				// 标识为1标识发送的是用户名
    				if (flag == 1) {
    					// 获取用户名
    					String name = dis.readUTF();
    					// 输出用户名及其ip地址
    					System.out.println(name + "------------>" + socket.getRemoteSocketAddress());
    					// 讲客户端及其socket与用户名进行映射并存储
    					ServerChat.onLineSockets.put(socket, name);
    				}
    				// 发送消息
    				writeMsg(flag, dis);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    			System.out.println("当前有人下线!");
    			// 客户端下线时移除其socket及用户名
    			ServerChat.onLineSockets.remove(socket);
    			try {
    				// 发送用户下线消息
    				writeMsg(1, dis);
    			} catch (Exception e2) {
    				e.printStackTrace();
    			}
    		}
    
    	}
    
    	/**
    	 * 发送消息到客户端
    	 * @param flag 消息标识
    	 * @param dis 数据输入流
    	 */
    	private void writeMsg(int flag, DataInputStream dis) {
    		String msg = null;
    		try {
    			if (flag == 1) {
    				StringBuilder rs = new StringBuilder();
    				// 获取所有在线的用户名
    				Collection<String> onlineNames = ServerChat.onLineSockets.values();
    				if (onlineNames != null && onlineNames.size() > 0) {
    					for (String name : onlineNames) {
    						// 将用户与用户之间通过分隔符进行分隔
    						rs.append(name + Constants.SPILIT);
    					}
    					// 截取去除最后一个分隔符
    					msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));
    					// 向所有客户端发送在线客户端用户名
    					sendMsgToAllClient(flag, msg);
    				}
    				// 标识是2是群发,3是私聊
    			} else if (flag == 2 || flag == 3) {
    					// 读取新的消息
    					String newMsg = dis.readUTF();
    					// 获取当前客户端对应的用户名
    					String sendName = ServerChat.onLineSockets.get(socket);
    					StringBuilder msgFinal = new StringBuilder();
    					// 格式化时间
    					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
    					if (flag == 2) {
    						// 发送者用户名与时间拼接
    						msgFinal.append(sendName).append("   ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
    						// 发送的消息拼接
    						msgFinal.append("     ").append(newMsg).append("\r\n");
    						// 向所有客户端发送消息
    						sendMsgToAllClient(flag, msgFinal.toString());
    					} else {
    						// 私聊
    						msgFinal.append(sendName).append("   ").append(sdf.format(System.currentTimeMillis())).append("\r\n");
    						msgFinal.append("        ").append(newMsg).append("\r\n");
    						String destName = dis.readUTF();
    						// 向当个用户发送消息
    						this.sendMsgToOne(destName, msgFinal.toString());
    					}
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	/**
    	 * 发送消息给指定用户
    	 * @param destName 目标客户端用户名
    	 * @param msg 消息
    	 * @throws IOException
    	 */
    	private void sendMsgToOne(String destName, String msg) throws IOException {
    		Set<Socket> allOneLineSockets = ServerChat.onLineSockets.keySet();
    		for (Socket sk: allOneLineSockets) {
    			// 获取目标客户端的socket
    			if (ServerChat.onLineSockets.get(sk).trim().equals(destName)) {
    				// 创建输出流
    				DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
    				// 发送标识
    				dos.writeInt(2);
    				// 发送消息
    				dos.writeUTF(msg);
    				dos.flush();
    				break;
    			}
    		}
    
    	}
    	/**
    	 * 发送消息给所有的客户端用户
    	 * @param flag
    	 * @param msg
    	 */
    	private void sendMsgToAllClient(int flag, String msg) {
    		Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
    		for (Socket sk: allOnLineSockets) {
    			try {
    				// 创建输出流
    				DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
    				// 发送标识
    				dos.writeInt(flag);
    				// 发送消息
    				dos.writeUTF(msg);
    				dos.flush();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    
  • 代码中使用的常量定义代码

    package com.learnclub.utils;
    
    public class Constants {
    	// 端口号
    	public static final int PORT = 7778;
    	// 分隔符
    	public static final String SPILIT = "0044666ZZZZXXX@@@";
    }
    
    

客户效果图展示(实现代码及逻辑分析下篇在讲)

  • 登录界面GUI

    image.png

  • 聊天界面GUI

    image.png

  • 多用户在线及多人、私聊聊天效果图

    image.png

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © 大苹果

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章

Mysql 表被标记为崩溃,最后一次自动化修复失败

当 MySQL 日志中出现“表被标记为崩溃且自动修复失败”的提示时,通常表示 MySQL 在启动过程中检测到某个表的文件系统或存储引擎层出现了问题。这种情况通常会导致表无法正常使用,需要手动进行修复。以下是处理这种情况的一些步骤和方法: ### 1. **查看具体错误信息** 首先,需要查看 MySQL 错误日志以获取更多的错误信息。这可以帮助确定崩溃的表和具体的问题。 ```bash # 查看 MySQL 错误日志 tail -f /var/log/mysql/error.log ``` ### 2. **修复表** 不同的存储引擎有不同的修复工具,以下是常用的修复方法: #### **使用 `myisamchk` 修复 MyISAM 表** 如果崩溃的表使用的是 MyISAM 存储引擎,可以使用 `myisamchk` 工具来修复表。 1. 停止 MySQL 服务: ```bash sudo systemctl stop mysql ``` 2. 使用 `myisamchk` 修复表:

2024/8/11👍 0

mermaid.min.js的使用及介绍

`mermaid.min.js` 是一个 JavaScript 库,用于在浏览器中轻松绘制复杂的图表和流程图。Mermaid 通过使用简单的标记语言来描述图表,使得用户无需深入学习图表绘制的底层技术即可创建专业的图形。 在这篇文章中,我们将介绍 `mermaid.min.js` 的功能、应用场景、如何在项目中使用,以及一些常用的示例。 ## Mermaid 的功能 Mermaid 支持多种类型的图表,包括但不限于: 1. **流程图(Flowchart)**: - 用于表示流程或过程中的步骤。 - 支持各种节点和连线风格。 2. **序列图(Sequence Diagram)**: - 用于表示对象之间的交互。 - 展示消息的发送和接收过程。 3. **类图(Class Diagram)**: - 用于表示类及其关系。 - 支持类的属性和方法定义。 4. **状态图(State Diagram)**: - 用于描述系统状态及其转换。 - 适用于复杂的状态机建模。 5. **甘

2024/8/9👍 0

docker之Dockerfile及docker compose配置文件的说明及使用

Docker 的配置文件对于镜像构建和容器化应用的管理至关重要。在 Docker 中,主要的配置文件是 Dockerfile 和 docker-compose.yml。Dockerfile 用于构建镜像,而 docker-compose.yml 用于定义多容器应用。下面将详细介绍这两个文件的配置选项和用法。 ## Dockerfile 配置详解 **Dockerfile** 是一个文本文件,其中包含一组指令,用于定义如何构建 Docker 镜像。以下是 Dockerfile 中常用的指令及其用法: ### FROM `FROM` 指令用于指定基础镜像,是 Dockerfile 中的第一条指令。 ```dockerfile FROM node:14 ``` 上例中,`node:14` 是官方的 Node.js 版本 14 的镜像。 ### LABEL `LABEL` 用于为镜像添加元数据,以键值对形式存在。 ```dockerfile LABEL maintainer="your-email@example.com" LABE

2024/8/9👍 0