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

 JAVA   小卒子   2023-12-15 08:48   79

功能说明

  • 客户端登录功能

    • 可以启动客户端进行登录。
    • 客户登录需要输入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