多人,单聊天室版
FEATURE
- 多人聊天, 界面简洁美观, 使用ueditor支持发送文字,图片信息
- 群聊成员列表, 登入登出公告
- 存储聊天记录, 查看历史消息
技术点
- 使用CopyOnWriteMap存储websocketServer对象,线程安全
- redis存储消息记录
- ConcurrentLinkedQueue存储聊天成员
TODO
- 没有处理高并发,高并发情况对服务器和内存都会产生极大压力 解决方案 采取实现分布式
- 当前是所有成员在一个聊天室,计划按照聊天室ID隔离出多聊天室(使用Redis存储)
主逻辑代码
@ServerEndpoint(value="/websocketServer", configurator=SpringConfigurator.class, encoders = { CommonMessageEncoder.class, SystemMessageEncoder.class, HistoryMessageEncoder.class}) public class WebsocketServer { //存储每个客户端对应的websocketServer实例与登录名map private static CopyOnWriteMap<WebsocketServer, String> webSocketUsernameMap = new CopyOnWriteMap<WebsocketServer, String>(); private MessageDao messageDao = (MessageDao)ContextLoader.getCurrentWebApplicationContext().getBean("messageDao"); //在线成员 private static ConcurrentLinkedQueue<String> members = new ConcurrentLinkedQueue<String>(); //每个webscoket客户端与服务器会话 private Session session; public WebsocketServer() { } @OnOpen public void onOpen(Session session) { this.session = session; } @OnClose public void onClose() { String username = webSocketUsernameMap.get(this); removeMember(username); webSocketUsernameMap.remove(this); for (WebsocketServer webSocket : webSocketUsernameMap.keySet()) { try { sendMsg(webSocket, new SystemMessageResponse(MessageType.SYS_MSG, username, "exit", members)); } catch (Exception e) { e.printStackTrace(); } } } @OnMessage public void onMessage(String message, Session session) { JSONObject messageObject = new JSONObject(message); String type = messageObject.getString("messageType"); String content = messageObject.getString("message"); if (type.equals(MessageType.COM_MSG)) { //群发消息 Date time = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (WebsocketServer item : webSocketUsernameMap.keySet()) { try { sendMsg(item, new CommonMessageResponse(MessageType.COM_MSG, sdf.format(time), webSocketUsernameMap.get(this), content)); } catch (Exception e) { e.printStackTrace(); } } ChatMessage msg = new ChatMessage(sdf.format(time), webSocketUsernameMap.get(this), content); messageDao.save(msg); } else if (type.equals(MessageType.SYS_MSG)) { //链接成功后客户端会发送登录名 在此进行记录 webSocketUsernameMap.put(this, content); addMember(content); for (WebsocketServer webSocket : webSocketUsernameMap.keySet()) { try { sendMsg(webSocket, new SystemMessageResponse(MessageType.SYS_MSG, content, "enter", members)); } catch (Exception e) { e.printStackTrace(); } } } else { try { List<ChatMessage> historyMessage = messageDao.getList(); sendMsg(this, new HistoryMessageResponse(MessageType.HIS_MSG, historyMessage)); } catch (Exception e) { e.printStackTrace(); } } } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } private void sendMsg(WebsocketServer webSocket, Object message) throws Exception { webSocket.session.getBasicRemote().sendObject(message); } public static int getMembersCount() { return members.size(); } public static void addMember(String member) { members.add(member); } public static void removeMember(String member) { members.remove(member); } }
效果图
多人,多聊天室版
FEATURE
- 多人聊天,多聊天室, 互相独立, 界面简洁美观, 使用ueditor支持发送文字,图片信息
- 群聊成员列表, 登入登出公告
- 存储聊天记录, 查看历史消息
技术点
- 使用CopyOnWriteMap存储Session对象,线程安全
- redis存储消息记录
- redis存储聊天成员
@ServerEndpoint(value="/websocketServer/{chatRoomId}", configurator=SpringConfigurator.class, encoders = { CommonMessageEncoder.class, SystemMessageEncoder.class, HistoryMessageEncoder.class}) public class WebsocketServer { private ChatRoomDao chatRoomDao = (ChatRoomDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatRoomDao"); private MessageDao messageDao = (MessageDao)ContextLoader.getCurrentWebApplicationContext().getBean("messageDao"); private ChatroomMemberDao chatroomMemberDao = (ChatroomMemberDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatroomMemberDao"); // private Session session; public WebsocketServer() { } @OnOpen public void onOpen(Session session) { // this.session = session; } @OnClose public void onClose(Session session, @PathParam("chatRoomId") String chatRoomId) { String username = chatRoomDao.get(chatRoomId, session); chatroomMemberDao.remove(chatRoomId, username); chatRoomDao.remove(chatRoomId, session); for (Session sesion : chatRoomDao.getKeys(chatRoomId)) { try { sendMsg(sesion, new SystemMessageResponse(MessageType.SYS_MSG, username, "exit", chatroomMemberDao.getAll(chatRoomId))); } catch (Exception e) { e.printStackTrace(); } } } @OnMessage public void onMessage(String message, Session session, @PathParam("chatRoomId") String chatRoomId) { JSONObject messageObject = new JSONObject(message); String type = messageObject.getString("messageType"); String content = messageObject.getString("message"); if (type.equals(MessageType.COM_MSG)) { //群发消息 Date time = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (Session sesion : chatRoomDao.getKeys(chatRoomId)) { try { sendMsg(sesion, new CommonMessageResponse(MessageType.COM_MSG, sdf.format(time), chatRoomDao.get(chatRoomId, session), content)); } catch (Exception e) { e.printStackTrace(); } } ChatMessage msg = new ChatMessage(sdf.format(time), chatRoomDao.get(chatRoomId, session), content); messageDao.save(chatRoomId, msg); } else if (type.equals(MessageType.SYS_MSG)) { chatRoomDao.save(chatRoomId, session, content); chatroomMemberDao.save(chatRoomId, content); for (Session sesion : chatRoomDao.getKeys(chatRoomId)) { try { sendMsg(sesion, new SystemMessageResponse(MessageType.SYS_MSG, content, "enter", chatroomMemberDao.getAll(chatRoomId))); } catch (Exception e) { e.printStackTrace(); } } } else { try { List<ChatMessage> historyMessage = messageDao.getList(chatRoomId); sendMsg(session, new HistoryMessageResponse(MessageType.HIS_MSG, historyMessage)); } catch (Exception e) { e.printStackTrace(); } } } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } private void sendMsg(Session session, Object message) throws Exception { session.getBasicRemote().sendObject(message); } }效果图
//在线好友列表控制器 @ServerEndpoint(value="/websocketServer", configurator=SpringConfigurator.class, encoders = {SystemMessageEncoder.class, CommonMessageEncoder.class, HistoryMessageEncoder.class})//这边的session会被websocket中调用发送commonmessage,所以需要引入CommonMessageEncoder public class WebsocketServer { private ChatRoomDao chatRoomDao = (ChatRoomDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatRoomDao"); private ChatroomMemberDao chatroomMemberDao = (ChatroomMemberDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatroomMemberDao"); public WebsocketServer() { } @OnOpen public void onOpen(Session session) { } @OnClose public void onClose(Session session) { String username = chatRoomDao.getUname(session); chatroomMemberDao.remove(username); chatRoomDao.remove(session); for (Session sesion : chatRoomDao.getSessionKeys()) { try { sendMsg(sesion, new SystemMessageResponse(MessageType.SYS_MSG, username, "exit", chatroomMemberDao.getAll())); } catch (Exception e) { e.printStackTrace(); } } } @OnMessage public void onMessage(String message, Session session) { JSONObject messageObject = new JSONObject(message); String content = messageObject.getString("message"); String messageType = messageObject.getString("messageType"); if (messageType.equals(MessageType.SYS_MSG)) { chatRoomDao.save(session, content); chatroomMemberDao.save(content); for (Session sesion : chatRoomDao.getSessionKeys()) { try { sendMsg(sesion, new SystemMessageResponse(MessageType.SYS_MSG, content, "enter", chatroomMemberDao.getAll())); } catch (Exception e) { e.printStackTrace(); } } } } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } private void sendMsg(Session session, Object message) throws Exception { session.getBasicRemote().sendObject(message); } }
//一对一聊天控制器 @ServerEndpoint(value="/chatServer", configurator=SpringConfigurator.class, encoders = {SystemMessageEncoder.class, CommonMessageEncoder.class, HistoryMessageEncoder.class}) public class ChatServer { private ChatRoomDao chatRoomDao = (ChatRoomDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatRoomDao"); private ChatSessionStorageDao chatSessionStorageDao = (ChatSessionStorageDao)ContextLoader.getCurrentWebApplicationContext().getBean("chatSessionStorageDao"); private HistoryMessageDao historyMessageDao = (HistoryMessageDao)ContextLoader.getCurrentWebApplicationContext().getBean("historyMessageDao"); public ChatServer() { } @OnOpen public void onOpen(Session session) { } @OnClose public void onClose(Session session) { chatSessionStorageDao.closeSession(session); } @OnMessage public void onMessage(String message, Session session) { JSONObject messageObject = new JSONObject(message); String messageType = messageObject.getString("messageType"); String from = messageObject.getString("from"); String to = messageObject.getString("to"); if (messageType.equals(MessageType.SYS_MSG)) { chatSessionStorageDao.save(from, to, from, session); List<ChatMessage> historyMessages = historyMessageDao.get(from, to); if (historyMessages.size() > 0) { try { sendMsg(session, new HistoryMessageResponse(MessageType.HIS_MSG, historyMessages)); } catch (Exception e) { e.printStackTrace(); } } } else if (messageType.equals(MessageType.COM_MSG)) { String sendMessage = messageObject.getString("message"); //先从一对一session存储Map中查找session Session toSession = chatSessionStorageDao.getSession(from, to, to); if (toSession == null) { //没找到就从所有的session列表中查找 toSession = chatRoomDao.getSession(to); } if (toSession == null) { try { sendMsg(session, new SystemMessageResponse(MessageType.SYS_MSG, to, "", null)); } catch (Exception e) { e.printStackTrace(); } } else { Date time = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { historyMessageDao.save(from, to, new ChatMessage(sdf.format(time), from, sendMessage)); sendMsg(session, new CommonMessageResponse(MessageType.COM_MSG, sdf.format(time), from, sendMessage)); sendMsg(toSession, new CommonMessageResponse(MessageType.COM_MSG, sdf.format(time), from, sendMessage)); } catch (Exception e) { e.printStackTrace(); } } } } @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } private void sendMsg(Session session, Object message) throws Exception { session.getBasicRemote().sendObject(message); } }
效果图