本文内容是基于腾讯云TRTCCalling API 实现的简单webSoketDemo,内容包含客户端、服务端消息互动,客户端重连、心跳机制。
腾讯云TRTCSDK 腾讯云TRTCCalling
API(桌面浏览器)
腾讯云LocalStream
webSoket通讯
效果图
Controller类
package com.cgjd.web.controller.system;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
/**
* webSoket demo
* @author : ljl
* @date : 14:35 2021/3/19
*/
@Controller
public class TestWebSoketController {
@GetMapping("/soket")
public String soket(){
return "webSoketDemo";
}
}
Service类
package com.cgjd.socket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocketService 通讯
*
* @author : ljl
* @date : 14:06 2021/2/24
*/
@Component
@Slf4j
@ServerEndpoint(value = "/app/websocketServer/{identityId}", configurator = MySpringConfigurator.class)
public class WebSocketService {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// 线程安全全局变量
private static ThreadLocal<Integer> t1 = new ThreadLocal<Integer>();
//connect key为身份ID,value为此对象this
public static Map<String, Object> clients = new ConcurrentHashMap<String, Object>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
public Session session;
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(@PathParam("identityId") String identityId, Session session) {
this.session = session;
clients.put(identityId, this); // this = WebSocketService对象
log.info("=========={}连接成功!当前在线人数为{}", identityId, clients.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(@PathParam("identityId") String identityId) {
clients.remove(identityId);
log.info("=========={}退出!当前在线人数为{}", identityId, clients.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param identityId 客户端发送过来的消息
* @param message 可选的参数
*/
@OnMessage
public void onMessage(@PathParam("identityId") String identityId, String message) {
if ("all".equals(message)){
String str = "系统管理员对所有人说你好";
sendToAll(str);
}else {
String str = "系统管理员" + "对你【" + identityId + "】说:已收到消息【" + message + "】";
sendToUser(identityId, str);
}
}
/**
* 发生错误时调用
*
* @param identityId
* @param error
*/
@OnError
public void onError(@PathParam("identityId") String identityId, Throwable error) {
log.error("=========={}发送错误{}", identityId, error);
}
/**
* 给所有人发送消息
*
* @param msg 发送的消息
*/
public static void sendToAll(String msg) {
//群发消息
for (String key : clients.keySet()) {
WebSocketService client = (WebSocketService) clients.get(key);
if (client == null){
log.info("==========连接中不存在【{}】用户",key);
break;
}
synchronized (client) {
try {
client.session.getBasicRemote().sendText(msg);
log.info("==========已群发消息:{}", msg);
} catch (IOException e) {
clients.remove(client);
log.error("{}", e);
try {
client.session.close();
} catch (IOException e1) {
log.error("{}", e);
}
}
}
}
}
/**
* 发送给指定用户
*
* @param user 用户名
* @param msg 发送的消息
*/
public static void sendToUser(String user, String msg) {
WebSocketService client = (WebSocketService) clients.get(user);
if (client == null){
log.info("==========连接中不存在【{}】用户",user);
return;
}
synchronized (client) {
try {
client.session.getBasicRemote().sendText(msg);
log.info("==========已发送消息:{}", msg);
} catch (IOException e) {
clients.remove(user);
log.error("{}", e);
try {
client.session.close();
} catch (IOException e1) {
log.error("{}", e);
}
}
}
}
}
Config配置类
package com.cgjd.socket;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import javax.websocket.server.ServerEndpointConfig;
/**
* @author : ljl
* @date : 9:41 2021/2/25
*/
/**
* 以websocketConfig.java注册的bean是由自己管理的,需要使用配置托管给spring管理
*/
public class MySpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
private static volatile BeanFactory context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
MySpringConfigurator.context = applicationContext;
}
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return context.getBean(clazz);
}
}
package com.cgjd.socket;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @author : ljl
* @date : 9:35 2021/2/25
*/
@Configuration
@ConditionalOnWebApplication
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
*
* 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public MySpringConfigurator mySpringConfigurator() {
return new MySpringConfigurator();
}
}
Html
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title>WebSoket Demo</title>
<script th:src="@{/js/jquery.min.js}"></script>
<script type="text/JavaScript">
var lockReconnect = false;//避免重复连接
var ws = null; //WebSocket的引用
var wsUrl = null; //这个要与后端提供的相同
var log = null; //记录客户端与服务器的通话
$(function() {
wsUrl = document.getElementById("wsUrlId").value;
createWebSocket(wsUrl);/**启动连接**/
// 强制退出
window.onunload = function() {
ws.close();
}
});
//创建WebSocket连接,如果不确定浏览器是否支持,可以使用socket.js做连接
function createWebSocket(url){
url = document.getElementById("wsUrlId").value;
try {
if ('WebSocket' in window) {
ws = new WebSocket( url);
} else {
// ws = new SockJS("http://" + url);
}
initEventHandle();
} catch (e) {
reconnect(wsUrl);
}
}
function reconnect(url) {
if(lockReconnect) return;
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
createWebSocket(url);
console.log("正在重连,当前时间"+new Date())
lockReconnect = false;
}, 5000); //这里设置重连间隔(ms)
}
// 记录客户端与服务器的通话
log = function (s) {
if (document.readyState !== "complete") {
log.buffer.push(s);
} else {
document.getElementById("contentId").value += (s + "\n");
}
}
/*********************初始化开始**********************/
function initEventHandle() {
// 连接成功建立后响应
ws.onopen = function() {
console.log("成功连接到" + wsUrl);
//心跳检测重置
heartCheck.reset().start();
}
// 收到服务器消息后响应
ws.onmessage = function(e) {
log("onmessage(), 接收到服务器消息: " + e.data);
console.log("onmessage(), 接收到服务器消息: " + e.data);
//如果获取到消息,心跳检测重置
//拿到任何消息都说明当前连接是正常的
heartCheck.reset().start();
//Json转换成Object
// var msg = eval('(' + e.data + ')');
//
// if(msg.message == "heartBeat"){
// //忽略心跳的信息,因为只要有消息进来,断线重连就会重置不会触发
// }else{
// //处理消息的业务逻辑
// }
}
// 连接关闭后响应
ws.onclose = function() {
console.log("关闭连接");
reconnect(wsUrl);//重连
}
ws.onerror = function () {
reconnect(wsUrl);//重连
};
}
/***************初始化结束***********************/
//心跳检测
var heartCheck = {
timeout: 3000,//毫秒
timeoutObj: null,
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
var msg = document.getElementById("messageId");
ws.send(msg.value);
console.log("发送心跳:"+msg.value);
self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout)
}, this.timeout)
}
}
//发送字符串消息
function SendMsg() {
var msg = document.getElementById("messageId");
console.log("SendMsg(), msg: " + msg.value);
if (ws != null) {
log("发送 Socket 消息: " + msg.value);
console.log("发送 Socket 消息: " + msg.value);
ws.send(msg.value);
}
else {
log("Socket 还未创建!, msg: " + msg.value);
console.log("Socket 还未创建!, msg: " + msg.value);
}
}
function createWebSocket(){
wsUrl = document.getElementById("wsUrlId").value;
try {
if ('WebSocket' in window) {
ws = new WebSocket( wsUrl);
} else {
// ws = new SockJS("http://" + url);
}
initEventHandle();
} catch (e) {
}
}
// 关闭连接
function CloseConnect () {
console.log("CloseConnect()");
if (ws != null) {
ws.close();
}
}
</script>
</head>
<body οnlοad="Display()">
<div id="valueLabel"></div>
<textarea rows="20" cols="30" id="contentId"></textarea>
<br/>
<input name="wsUrl" id="wsUrlId" value="ws://localhost:8080/app/websocketServer/1001"/>
<button id="createButton" onClick="javascript:createWebSocket()">Create</button>
<button id="closeButton" onClick="javascript:CloseConnect()">Close</button>
<br/>
<input name="message" id="messageId" value="1001@Hello, Server!"/>
<button id="sendButton" onClick="javascript:SendMsg()">Send</button>
<p>ws://localhost:8080/app/websocketServer/1001</p>
</body>
</html>