Java websocket + redis 实现多人单聊天室,多人多聊天室, 一对一聊天

多人,单聊天室版

FEATURE

  1. 多人聊天, 界面简洁美观, 使用ueditor支持发送文字,图片信息
  2. 群聊成员列表, 登入登出公告
  3. 存储聊天记录, 查看历史消息

技术点

  1. 使用CopyOnWriteMap存储websocketServer对象,线程安全
  2. redis存储消息记录
  3. ConcurrentLinkedQueue存储聊天成员

TODO

  1. 没有处理高并发,高并发情况对服务器和内存都会产生极大压力 解决方案 采取实现分布式
  2. 当前是所有成员在一个聊天室,计划按照聊天室ID隔离出多聊天室(使用Redis存储)

主逻辑代码
@ServerEndpoint(value="/websocketServer", configurator=SpringConfigurator.class, encoders = { CommonMessageEncoder.class, SystemMessageEncoder.class, HistoryMessageEncoder.class})
public class WebsocketServer {
	//存储每个客户端对应的websocketServer实例与登录名map
    private static CopyOnWriteMap webSocketUsernameMap = new CopyOnWriteMap();
    
    private MessageDao messageDao = (MessageDao)ContextLoader.getCurrentWebApplicationContext().getBean("messageDao");
    
    //在线成员
    private static ConcurrentLinkedQueue members = new ConcurrentLinkedQueue();

    //每个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 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

  1. 多人聊天,多聊天室, 互相独立, 界面简洁美观, 使用ueditor支持发送文字,图片信息
  2. 群聊成员列表, 登入登出公告
  3. 存储聊天记录, 查看历史消息

技术点

  1. 使用CopyOnWriteMap存储Session对象,线程安全
  2. redis存储消息记录
  3. 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 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);
    }
}
效果图


一对一聊天

技术点

  1. 一对一关系维护
  2. redis存储聊天记录

TODU

  1. 下线时关闭关联的聊天
  2. 实现分布式

主逻辑代码
//在线好友列表控制器
@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 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);
    }
}


效果图


你可能感兴趣的:(java,redis,js,jquery)