效果
在实现毕业设计时,中间有个功能需要实现不同聊天室不同用户之间的交流。
例如:
项目演示
SpringMVC整合websocket实现多聊天室聊天功能
原理讲解
创建多个不同的聊天室和实现对用户的分层管理。用户以医生的身份进入系统后,用户选择在线聊天,系统更改用户当前状态,并以其账号和姓名作为临时身份,并新建一个名称为医生账号的WebSocketsession,之后建立USER_SOCKETSESSION_MAP为房间里的每个人存储信息。将WebSocketsession存储到USER_SOCKETSESSION_MAP中,实现多人聊天室的功能。之后建立一个以自增长的数字为辨别符ROOM房间,将USER_SOCKETSESSION_MAP存储到ROOM中,用以区分每个聊天室。当用户以患者身份登录后,患者选者医生,并进入医生详情页,如果医生在线了话,则可以访问医生。用户选择医生后进入聊天室,后台会将要访问的医生账号放入WebSocketsession,系统在后台对比每个ROOM房间中USER_SOCKETSESSION_MAP里的WebSocketsession,当匹配完成后,将其WebSocketsession放进USER_SOCKETSESSION_MAP里对应的ROOM房间中。实现对房间分层多用户聊天操作。
实现步骤
1.区分聊天室
修改WebSocket的接收协议格式,使其可以接收以userid结尾的uri。其修改WebSocket协议的代码如下:
registry.addHandler(webSocketHandler,"/ws/{id}").addInterceptors(chatHandshakeInterceptor);
registry.addHandler(webSocketHandler,"/ws/sockjs/{id}").addInterceptors(chatHandshakeInterceptor).withSockJS();
在系统中要创建一个类型为HashMap的USER_SOCKETSESSION_MAP,并用userid作为标志区分每个房间里不同的用户,并将webSocketSession放入其中。之后创建类型为HashMap的ROOM用来存放不同的房间信息key作为每个房间的标志。创建USER_SOCKETSESSION_MAP和ROOM的代码如下:
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>>();
}
当患者选择要询问的主治医生后,系统将对应的医生账号写入WebSocketsession中去,当后台获取websocketsession后首先判断此时的房间ROOM是否为空,若为空房间则进行初始化操作,将websocketsession和登录用户id放入USER_SOCKETSESSION_MAP中,在初始化key为0,后将key和USER_SOCKETSESSION_MAP放入ROOM中。初始化代码如下:
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);}
如果房间不为空则进行遍历查找,首先将ROOM分层一个一个的USER_SOCKETSESSION_MAP临时设为entry1,后将每个USER_SOCKETSESSION_MAP分层一个一个的WebSocketsession临时设为entry2。之后将得到的Websocketsession与每一个entry2中的uri对比,如果uri一样,则把当前的用户id和uri一起放入websocketsession中,将websocketsession放入对应ROOM中的USER_SOCKETSESSION_MAP中去,并设置flag值跳过后面的创建房间。最后跳出循环。寻找房间代码如下:
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;
}
}
}
如果房间不为空且找不到房间时,将websocketsession和登录用户id放入USER_SOCKETSESSION_MAP中,提取当前ROOM房间的所有值,并进行排序,取得最大值,将key设置为最大值加一,后创建新的ROOM房间并将取到的key值和USER_SOCKETSESSION_MAP放入ROOM中去。不为空并创建新房间代码如下:
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);
}
通过两个HashMap类型的变量完成对不同房间的创建和对不同用户的区分,实现不同房间的多用户聊天室。
2.发送消息
如果客户端有消息进来,需要先判断客户端的这条信息是从哪个聊天室发来的:
@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)));
}
}
之后再对消息进行客户端的发送
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();
}
}
}
页面端接受消息
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>
<!--聊天区域结束-->
未经授权,请勿转载