SpringMVC integrates websocket to realize multi-chat room chat function

Effect

When realizing the graduation project, there is a function in the middle that needs to realize the communication between different users in different chat rooms.
For example:
insert image description here

project demo

SpringMVC integrates websocket to realize multi-chat room chat function

Principle explanation

Create multiple different chat rooms and implement hierarchical management of users. After the user enters the system as a doctor, the user chooses to chat online, the system changes the user's current status, uses his account number and name as a temporary identity, and creates a new WebSocketsession named doctor account, and then creates a USER_SOCKETSESSION_MAP to store information for everyone in the room. Store WebSocketsession in USER_SOCKETSESSION_MAP to realize the function of multi-person chat room. Then create a ROOM room with self-increasing numbers as discriminators, and store USER_SOCKETSESSION_MAP in ROOM to distinguish each chat room. When the user logs in as a patient, the patient selects a doctor and enters the doctor's details page. If the doctor is online, he can visit the doctor. After the user selects a doctor and enters the chat room, the background will put the account of the doctor to be accessed into the WebSocketsession, and the system will compare the WebSocketsession in the USER_SOCKETSESSION_MAP of each ROOM in the background, and when the matching is completed, put the WebSocketsession into the corresponding ROOM in the USER_SOCKETSESSION_MAP. Implement hierarchical multi-user chat operations for rooms.

Implementation steps

1. Differentiate between chat rooms

Modify the receiving protocol format of WebSocket so that it can receive uri ending with userid. The code for modifying the WebSocket protocol is as follows:

registry.addHandler(webSocketHandler,"/ws/{id}").addInterceptors(chatHandshakeInterceptor);
registry.addHandler(webSocketHandler,"/ws/sockjs/{id}").addInterceptors(chatHandshakeInterceptor).withSockJS();

In the system, create a USER_SOCKETSESSION_MAP of type HashMap, and use userid as a sign to distinguish different users in each room, and put webSocketSession into it. Then create a ROOM of type HashMap to store different room information keys as the sign of each room. The code to create USER_SOCKETSESSION_MAP and ROOM is as follows:

final Map<String, WebSocketSession> USER_SOCKETSESSION_MAP;
final Map<Integer, Map<String, WebSocketSession>> ROOM; 
static {
    
    
	USER_SOCKETSESSION_MAP = new HashMap<String,WebSocketSession>();
	ROOM=new HashMap<Integer, Map<String, WebSocketSession>>();
} 

When the patient chooses the attending doctor to inquire, the system writes the corresponding doctor account into the WebSocketsession. When the websocketsession is acquired in the background, it first judges whether the room ROOM is empty at this time. If it is an empty room, the initialization operation is performed, and the websocketsession and the login user id are put into the USER_SOCKETSESSION_MAP. After the initialization key is 0, the key and USER_SOCKETSESSION_MAP are put into the ROOM. The initialization code is as follows:

if(ROOM==null || ROOM.isEmpty()){
    
    
Map<String, WebSocketSession> user_map= new HashMap<String, WebSocketSession>();
	user_map.put(loginUser.getId(), webSocketSession);
	ROOM.put(0, user_map);}

If the room is not empty, perform a traversal search. First, temporarily set the USER_SOCKETSESSION_MAP of each ROOM layer as entry1, and then temporarily set the WebSocketsession of each USER_SOCKETSESSION_MAP layer as entry2. Then compare the obtained Websocketsession with the uri in each entry2. If the uri is the same, put the current user id and uri into the websocketsession together, put the websocketsession into the USER_SOCKETSESSION_MAP in the corresponding ROOM, and set the flag value to skip the creation of the room later. Finally break out of the loop. Find the room code as follows:

boolean flag=true;
here:
for(Map.Entry<Integer,Map<String,WebSocketSession>>entry1:ROOM.entrySet()){
    
    
for(Map.Entry<String,WebSocketSession>entry2:entry1.getValue().entrySet()){
    
    
if(entry2.getValue().getUri().equals(webSocketSession.getUri())){
    
    
			key=entry1.getKey();
			(ROOM.get(key)).put(loginUser.getId(), webSocketSession);
			flag=false;
			break here; 
		}
	}
}

If the room is not empty and the room cannot be found, put the websockets session and the login user id into USER_SOCKETSESSION_MAP, extract all the values ​​of the current ROOM room, sort them, get the maximum value, set the key to the maximum value plus one, and then create a new ROOM room and put the obtained key value and USER_SOCKETSESSION_MAP into the ROOM. Not empty and create a new room The code is as follows:

if(flag){
    
    
	//无房间自增长添加房间
	Set<Integer> set = ROOM.keySet();
	Object[] obj = set.toArray();
	Arrays.sort(obj);
    key=obj.length;
	Map<String, WebSocketSession> user_map= new HashMap<String, WebSocketSession>();
	user_map.put(loginUser.getId(), webSocketSession);
	ROOM.put(obj.length, user_map);
}

Create different rooms and distinguish different users through two variables of HashMap type, and realize multi-user chat rooms in different rooms.

2. Send a message

If the client has a message, you need to determine which chat room the client sent the message from:

@Override
	/**
     * 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息
     */
	//处理消息:当一个新的WebSocket到达的时候,会被调用(在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理)
	public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> message) throws Exception {
    
    
//		判断哪个房间
		here:
		for(Map.Entry<Integer, Map<String, WebSocketSession>> entry1 : ROOM.entrySet()){
    
    
			for(Map.Entry<String, WebSocketSession> entry2 : entry1.getValue().entrySet()){
    
    
				if(entry2.getValue().getUri().equals(webSocketSession.getUri())){
    
    
					key=entry1.getKey();
					break here; 
				}
			}
		}
		
		//如果消息没有任何内容,则直接返回
		if(message.getPayloadLength()==0)return;
		//反序列化服务端收到的json消息
		Message msg = GsonUtils.fromJson(message.getPayload().toString(), Message.class);
		msg.setDate(new Date());
		//处理html的字符,转义:
		String text = msg.getText();
		//转换为HTML转义字符表示
		String htmlEscapeText = HtmlUtils.htmlEscape(text);
		msg.setText(htmlEscapeText);
		System.out.println("消息:"+message.getPayload().toString());
		//判断是群发还是单发
		if(msg.getTo()==null||msg.getTo().equals("-1")){
    
    
			//群发
			sendMessageToAll(new TextMessage(GsonUtils.toJson(msg)));
		}else{
    
    
			//单发
			sendMessageToUser(msg.getTo(), new TextMessage(GsonUtils.toJson(msg)));
		}
	}

Then send the message to the client

private void sendMessageToUser(String id, TextMessage message) throws IOException{
    
    
		//获取到要接收消息的用户的session
		WebSocketSession webSocketSession = ROOM.get(key).get(id);
		if (webSocketSession != null && webSocketSession.isOpen()) {
    
    
			//发送消息
			webSocketSession.sendMessage(message);
		}
	}
	
	private void sendMessageToAll(final TextMessage message){
    
    
		//对用户发送的消息内容进行转义
		
		//获取到所有在线用户的SocketSession对象
		Set<Entry<String, WebSocketSession>> entrySet = ROOM.get(key).entrySet();
		for (Entry<String, WebSocketSession> entry : entrySet) {
    
    
			//某用户的WebSocketSession
			final WebSocketSession webSocketSession = entry.getValue();
			//判断连接是否仍然打开的
			if(webSocketSession.isOpen()){
    
    
				//开启多线程发送消息(效率高)
				new Thread(new Runnable() {
    
    
					public void run() {
    
    
						try {
    
    
							if (webSocketSession.isOpen()) {
    
    
								webSocketSession.sendMessage(message);
							}
						} catch (IOException e) {
    
    
							e.printStackTrace();
						}
					}

				}).start();
				
			}
		}
	}

The page side accepts the message
contact.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ page language="java" import="java.util.*"%>
<%@ page language="java" import="java.sql.*"%>
<%
	String path = request.getContextPath();
	String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/";
	String baseUrlPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
			+ path + "/";
%>
<% String path1 = request.getContextPath()+"/123456";%>
<%=path1 %>
<script type="text/javascript">
	var path = '<%=basePath%>';
	
	var uid = '${sessionScope.loginUser.id}';
	//发送人编号
	var from = '${sessionScope.loginUser.id}';
	var fromName = '${sessionScope.loginUser.nickname}';
	
	//接收人编号
	var to = "-1";
	
	// 创建一个Socket实例
	//参数为URL,ws表示WebSocket协议。onopen、onclose和onmessage方法把事件连接到Socket实例上。每个方法都提供了一个事件,以表示Socket的状态。
	var websocket;
	//不同浏览器的WebSocket对象类型不同
	//alert("ws://" + path + "/ws?uid="+uid);
	if ('WebSocket' in window) {
    
    
		var url="ws://" + path + "/ws/"+uid;
		websocket = new WebSocket(url);
		console.log("=============WebSocket");
		//火狐
	} else if ('MozWebSocket' in window) {
    
    
		websocket = new MozWebSocket("ws://" + path + "ws");
		console.log("=============MozWebSocket");
	} else {
    
    
		websocket = new SockJS("http://" + path + "ws/sockjs");
		console.log("=============SockJS");
	}
	
	console.log("ws://" + path + "ws");
	
	//打开Socket,
	websocket.onopen = function(event) {
    
     
		console.log("WebSocket:已连接");
	}
	
	// 监听消息
	//onmessage事件提供了一个data属性,它可以包含消息的Body部分。消息的Body部分必须是一个字符串,可以进行序列化/反序列化操作,以便传递更多的数据。
	websocket.onmessage = function(event) {
    
     
		console.log('Client received a message',event);
		//var data=JSON.parse(event.data);
		var data=$.parseJSON(event.data);
		console.log("WebSocket:收到一条消息",data);
		
		//2种推送的消息
		//1.用户聊天信息:发送消息触发
		//2.系统消息:登录和退出触发
		
		//判断是否是欢迎消息(没用户编号的就是欢迎消息)
		if(data.from==undefined||data.from==null||data.from==""){
    
    
			//===系统消息
			$("#contentUl").append("<li><b>"+data.date+"</b><em>系统消息:</em><span>"+data.text+"</span></li>");
			//刷新在线用户列表
			$("#chatOnline").html("在线用户("+data.userList.length+")人");
			$("#chatUserList").empty();
			$(data.userList).each(function(){
    
    
				$("#chatUserList").append("<li>"+this.nickname+"</li>");
				$("#illnessoption").append("<option>"+this.nickname+"("+this.id+")</option>");
			});
			
		}else{
    
    
			//===普通消息
			//处理一下个人信息的显示:
			if(data.fromName==fromName){
    
    
				data.fromName="我";
				$("#contentUl").append("<li><span  style='display:block; float:right;'><em>"+data.fromName+"</em><span>"+data.text+"</span><b>"+data.date+"</b></span></li><br/>");
			}else{
    
    
				$("#contentUl").append("<li><b>"+data.date+"</b><em>"+data.fromName+"</em><span>"+data.text+"</span></li><br/>");
			}
			
		}
		
		scrollToBottom();
	}; 
	
	// 监听WebSocket的关闭
	websocket.onclose = function(event) {
    
     
		$("#contentUl").append("<li><b>"+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</b><em>系统消息:</em><span>连接已断开!</span></li>");
		scrollToBottom();
		console.log("WebSocket:已关闭:Client notified socket has closed",event); 
	}; 
	
	//监听异常
	websocket.onerror = function(event) {
    
    
		$("#contentUl").append("<li><b>"+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</b><em>系统消息:</em><span>连接异常,建议重新登录</span></li>");
		scrollToBottom();
		console.log("WebSocket:发生错误 ",event);
	};
	
	//onload初始化
	$(function(){
    
    
		//发送消息
		$("#sendBtn").on("click",function(){
    
    
			sendMsg();
		});
		
		//给退出聊天绑定事件
		$("#exitBtn").on("click",function(){
    
    
			closeWebsocket();
			location.href="${pageContext.request.contextPath}/index.jsp";
		});
		
		//给输入框绑定事件
		$("#msg").on("keydown",function(event){
    
    
			keySend(event);
		});
		
		//初始化时如果有消息,则滚动条到最下面:
		scrollToBottom();
		
	});

	//使用ctrl+回车快捷键发送消息
	function keySend(e) {
    
    
		var theEvent = window.event || e; 
		var code = theEvent.keyCode || theEvent.which; 
		if (theEvent.ctrlKey && code == 13) {
    
    
			var msg=$("#msg");
			if (msg.innerHTML == "") {
    
    
				msg.focus();
				return false;
			}
			sendMsg();
		}
	}
	
	//发送消息
	function sendMsg(){
    
    
		//对象为空了
		if(websocket==undefined||websocket==null){
    
    
			//alert('WebSocket connection not established, please connect.');
			alert('您的连接已经丢失,请退出聊天重新进入');
			return;
		}
		//获取用户要发送的消息内容
		var msg=$("#msg").val();
		if(msg==""){
    
    
			return;
		}else{
    
    
			var data={
    
    };
			data["from"]=from;
			data["fromName"]=fromName;
			data["to"]=to;
			data["text"]=msg;
			//发送消息
			websocket.send(JSON.stringify(data));
			//发送完消息,清空输入框
			$("#msg").val("");
		}
	}

	//关闭Websocket连接
	function closeWebsocket(){
    
    
		if (websocket != null) {
    
    
			websocket.close();
			websocket = null;
		}
		
	}
	
	//div滚动条(scrollbar)保持在最底部
	function scrollToBottom(){
    
    
		//var div = document.getElementById('chatCon');
		var div = document.getElementById('up');
		div.scrollTop = div.scrollHeight;
	}	
	//格式化日期
	Date.prototype.Format = function (fmt) {
    
     //author: meizz 
	    var o = {
    
    
	        "M+": this.getMonth() + 1, //月份 
	        "d+": this.getDate(), //日 
	        "h+": this.getHours(), //小时 
	        "m+": this.getMinutes(), //分 
	        "s+": this.getSeconds(), //秒 
	        "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
	        "S": this.getMilliseconds() //毫秒 
	    };
	    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	    for (var k in o)
	    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
	    return fmt;
	}			
</script>
<!-- 聊天窗口 -->
	<!--聊天区域开始-->
	<div class="chatArea" id="chatArea">
		<div class="inChatArea">
			<div class="chatCon">
				<div id="chatSidebar" class="chatSidebar">
					<h2 id="chatOnline">在线用户(0)</h2>
					<ul id="chatUserList">
					</ul>
				</div>
				<div class="up" id="up">
					<ul id="contentUl">
						<!-- <li><b>14:08</b><em>江山如此多娇</em><span>今天天气不大家出来嗨!!!!!</span></li>-->
					</ul>
				</div>
				<div class="down">
					<textarea class="textInfo" id="msg" title="按ctrl+enter直接发送"></textarea>
					<button class="btn" id="sendBtn"></button>
				</div>
				
			
			</div>
			<!-- 
	    <div class="ad">
        	<iframe src="http://m.itheimacast.icoc.in/" width="315" height="635" scrolling="no" frameborder="no" />
        </div>
         -->
		</div>
	</div>
	<!--聊天区域结束-->

Please do not reprint without authorization

Guess you like

Origin blog.csdn.net/qq_38922932/article/details/106410227
Recommended