项目重点
-
WebSocket的使用
-
@ServerEndpoint
-
该注解标注一个WebSocket服务器,该注解有几个注意点:
- value 表示能够使用 该 WebSocket 的 url.
- enconders 表示在写入对象进行的编码方式(向 session对象写入数据最终还是需要转换为 String)
-
@OnOpen 参数列表为: Session
- 在存在新连接时触发该信号
-
@OnClose 无参数列表
- 连接被关闭时触发信息
-
@OnMessage 参数列表为 String , Session
- 有消息写入到 WebSocket
-
心跳检测保持与后台连接
- 前台设置定时器来与后台交互保持连接。
-
-
项目实现
- 配置信息被省略
- MsgWebSocket.java
package cn.tblack.ssm.socket;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.web.socket.server.standard.SpringConfigurator;
/**
* <span>网页套接字。 该套接字需要被标注, value为一个url,也是能够使用该WebSocket的名字 </span>
*
* @author TD唐登
* @Date:2019年9月9日
* @Version: 1.0(测试版)
*/
@ServerEndpoint(value = "/msgWebSocket", configurator = SpringConfigurator.class)
public class MsgWebSocket {
// 记录当前在线人数
private static long onlineCnt = 0;
// 记录当前已经连接的次数总和
private static long connectCnt = 0;
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
// 若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static ConcurrentHashMap<MsgWebSocket, String> webSocketMap = new ConcurrentHashMap<>();
private Session session;
@OnOpen
/**
* @ 存在新连接
*
* @param session
*/
public void onOpen(Session session) throws IOException {
this.session = session;
addOnlineCnt();
long cnt = getConnectCnt();
webSocketMap.put(this, "[游客" + cnt + "]");
// 群发信息
sendMessageToAll("---------------[ 游客 " + cnt + "]上线了!");
}
@OnClose
/**
* @ 连接被关闭
*/
public void onClose() throws IOException {
subOnlineCnt();
String disconnect = webSocketMap.get(this);
webSocketMap.remove(this);
sendMessageToAll("---------------" + disconnect + "下线!");
System.out.println("有一连接被关闭! 当前在线人数为: " + getOnlineCount());
}
@OnMessage
/**
* @ 接收到消息
*
* @param message
* @param session
*/
public void onMessage(String message, Session session) throws IOException {
if("_-_-1998610_ping_-_-".equals(message)) {
return;
}
// 找到发送消息的客户端
String user = webSocketMap.get(this);
// 群发信息
for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {
entry.getKey().sendMessage(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss "))
+ user + ": " + message);
}
}
@OnError
/**
* @ 产生错误
*
* @param session
* @param error
*/
public void onError(Session session, Throwable error) {
System.out.println("有错误发生: " + error);
}
/**
* @ 向单个对象发送消息
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* @ 消息群发
*
* @param message
* @throws IOException
*/
private void sendMessageToAll(String message) throws IOException {
// 群发信息
for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {
entry.getKey().sendMessage(message);
}
}
public static synchronized long getOnlineCount() {
return onlineCnt;
}
public static synchronized long getConnectCnt() {
return connectCnt;
}
public static synchronized void addOnlineCnt() {
++connectCnt;
++onlineCnt;
}
public static synchronized void subOnlineCnt() {
--onlineCnt;
}
/**
* @ 拿到当前在线人的名字
* @return
*/
public static ArrayList<String> getOnlineList() {
ArrayList<String> list = new ArrayList<>();
for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {
list.add(entry.getValue());
}
return list;
}
}
- OnlineCntSocket.java
package cn.tblack.ssm.socket;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.web.socket.server.standard.SpringConfigurator;
import cn.tblack.ssm.encoder.ClassEncoder;
/**
* <span>用于发送实际在线人数的WebSocket</span>
*
* @author TD唐登
* @Date:2019年9月9日
* @Version: 1.0(测试版)
*/
@ServerEndpoint(value = "/onlineCntSocket", configurator = SpringConfigurator.class, encoders = ClassEncoder.class)
public class OnlineCntSocket {
/**
* @ 用于统计在线人数的Socket
*/
private static CopyOnWriteArraySet<OnlineCntSocket> webSocketSet = new CopyOnWriteArraySet<>();
/* @拿到套接字的会话信息 */
private Session session;
@OnOpen
/**
* @ 向连接成功的套接字发送在线人数
*/
public void onOpen(Session session) throws IOException {
this.session = session;
webSocketSet.add(this);
sendMessageToAll();
sendOnlineListToAll();
}
@OnClose
/**
* @ 套接字断开连接。 向所有套接字发送信息
*/
public void onClose() throws IOException {
webSocketSet.remove(this);
sendMessageToAll();
sendOnlineListToAll();
}
@OnMessage
/**
* @ 接收到信息
*/
public void onMessage(String message, Session session) {
if("_-_-1998610_ping_-_-".equals(message)) {
try {
session.getBasicRemote().sendText("ping");
} catch (IOException e) {
e.printStackTrace();
}
}
}
@OnError
/**
* @ 发生错误
*/
public void onError(Session session, Throwable error) {
System.out.println("发生错误: " + error);
}
/**
* @throws IOException @ 向所有当前活跃的套接字发送当前在线人数
*/
public void sendMessageToAll() throws IOException {
for (OnlineCntSocket socket : webSocketSet) {
socket.sendMessage(MsgWebSocket.getOnlineCount() + "");
}
}
/**
* @throws IOException @ 向单个套接字发送数据
*/
public void sendMessage(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
/**
* @ 向全部套接字发送当前在线列表
*/
public void sendOnlineListToAll() {
ArrayList<String> list = MsgWebSocket.getOnlineList();
for (OnlineCntSocket onlineCntSocket : webSocketSet) {
onlineCntSocket.sendOnlienList(list);
}
}
/**
* @ 向单个套接字发送当前在线列表
*/
public void sendOnlienList(ArrayList<String> list) {
try {
session.getBasicRemote().sendObject(list);
} catch (IOException e) {
e.printStackTrace();
} catch (EncodeException e) {
e.printStackTrace();
}
}
}
- 类编码器
package cn.tblack.ssm.encoder;
import java.util.ArrayList;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import com.google.gson.Gson;
/**
* <span>用于在 WebSocket 中传递对象所使用的编码器</span>
*
* @author TD唐登
* @Date:2019年9月10日
* @Version: 1.0(测试版)
*/
public class ClassEncoder implements Encoder.Text<ArrayList<String>> {
@Override
public void init(EndpointConfig config) {
}
@Override
public void destroy() {
}
@Override
/**
* @ 编码器, 将Object对象类型转换为json类型进行返回
*/
public String encode(ArrayList<String> object) throws EncodeException {
Gson gson = new Gson();
return gson.toJson(object);
}
}
- 前端页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"
isELIgnored="false"%>
<!DOCTYPE HTML>
<html>
<head>
<title>聊天室</title>
<!-- Bootstrap -->
<link rel="stylesheet"
href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<style type="text/css">
#text {
margin-left: 15px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="panel panel-primary">
<div class="panel-heading">聊天室</div>
<div id="msg" class="panel-body"></div>
<div class="panel-footer">
在线人数<span id="onlineCount"></span>人
</div>
<ul class="online-list" id="online-list">
</ul>
</div>
</div>
</div>
</div>
<input id="text" type="text" />
<button onclick="send()">发送</button>
<button onclick="closeWebSocket()">关闭连接</button>
<div id="message"></div>
</body>
<script src="js/jquery.min.js"></script>
<script type="text/javascript">
var msgWebSocket = null; //用于聊天使用的WebSocket
var onlineCntSocket = null; //用于统计在线人数的WebSocket
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
msgWebSocket = new WebSocket(
"ws://localhost:8080/chatRoom-ssm/msgWebSocket");
onlineCntSocket = new WebSocket(
"ws://localhost:8080/chatRoom-ssm/onlineCntSocket");
} else {
alert("对不起!你的浏览器不支持webSocket")
}
onlineCntSocket.onmessage = function(event) {
//解析数据。 如果是 json 数据, 则表示接收到的为在线列表
if (isJson(event.data)) {
console.log("当前在线列表为: " + event.data);
//将 Json 字符串转换为对象
var array = JSON.parse(event.data);
//清空之前的列表
$("#online-list").empty();
$("#online-list").append("<li class='list-span'>在线人数列表:</li>");
//遍历对象并打印出在线人数列表
for (var i = 0; i < array.length; i++) {
$("#online-list").append("<li>" + array[i] + "</li>")
}
}
//否则则表示接收到的就是在线人数
else {
//拿到后台返回的数据
if(event.data == "ping"){
heartCheck.reset().start();
console.log("ping");
return;
}
$("#onlineCount").text(event.data);
console.log("在线人数: " + event.data);
}
}
//连接发生错误的回调方法
msgWebSocket.onerror = function() {
setMessageInnerHTML("error");
};
//连接成功建立的回调方法
msgWebSocket.onopen = function(event) {
heartCheck.reset().start();
console.log("event:" + event);
setMessageInnerHTML("加入连接");
};
//接收到消息的回调方法
msgWebSocket.onmessage = function(event) {
setMessageInnerHTML(event.data);
};
//连接关闭的回调方法
msgWebSocket.onclose = function() {
setMessageInnerHTML("断开连接");
};
//监听窗口关闭事件,当窗口关闭时,主动去关闭msgWebSocket连接,
// 防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function() {
msgWebSocket.close();
onlineCntSocket.close();
};
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
$("#msg").append(innerHTML + "<br/>")
};
//关闭连接
function closeWebSocket() {
heartCheck.reset();
msgWebSocket.close();
onlineCntSocket.close();
}
//发送消息
function send() {
var message = $("#text").val();
msgWebSocket.send(message);
$("#text").val("");
}
function sendMsg() {
msgWebSocket.send(msg);
}
//判断是否是 json 数据
function isJson(str) {
try {
if (typeof JSON.parse(str) == "object") {
return true;
}
} catch (e) {
}
return false;
}
//心跳机制
var heartCheck = {
timeout : 5000, //5秒钟发一次心跳
timeoutObj : null,
serverTimeoutObj : null,
reset : function() {
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start : function() {
var self = this;
this.timeoutObj = setTimeout(function() {
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
msgWebSocket.send("_-_-1998610_ping_-_-");
onlineCntSocket.send("_-_-1998610_ping_-_-");
self.serverTimeoutObj = setTimeout(function() {//如果超过一定时间还没重置,说明后端主动断开了
closeWebSocket();
}, self.timeout)
}, this.timeout)
}
};
</script>
</html>