功能说明
-
客户端登录功能
- 可以启动客户端进行登录。
- 客户登录需要输入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
-
聊天界面GUI
-
多用户在线及多人、私聊聊天效果图