前言
WebSocket与前端消息通讯应用,提供连接建立、关闭,发生错误,数据接收、发送,权限校验(待补充)等方法
* 支持监控在线连接数
* 支持发送消息并回调结果,类似HTTP形式
一、WebSocket是什么?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
websocket的特点
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
二、使用步骤
1.消息通讯处理服务代码
import java.io.IOException;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.SendHandler;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.linkcm.sxssy.espms.job.websocket.model.MsgResult;
/***
* <b>function: WebSocket服务
* 提供连接建立、关闭,发生错误,数据接收、发送,权限校验(待补充)等方法
* 支持监控在线连接数
* 支持发送消息并回调结果,类似HTTP形式
* </b>
* @project sxssy-espms-job
* @package com.linkcm.sxssy.espms.job.websocket.Service
* @filename WebSocketService.java
* @createtime 2021年4月13日 下午4:45:15
* @author peibinchen
*/
@ServerEndpoint("/websocket/{token}/{userName}")
@Component
public class WebSocketService {
private final static Logger log = LoggerFactory.getLogger(WebSocketService.class);
// 存放每个客户端对应的WebSocket对象
private final static ConcurrentHashMap<String, WebSocketService> webSocketServices =
new ConcurrentHashMap<String, WebSocketService>();
// 存放待处理的ICallBack回调对象
private final static ConcurrentHashMap<String, ICallBack> callBackMap =
new ConcurrentHashMap<String, ICallBack>();
// 当前在线连接数
private final static AtomicInteger onlineCount = new AtomicInteger();
// 回调超时时间,单位:毫秒
public final static int WAIT_TIME = 30000;
// 回调超时时间,单位:毫秒
public final static String REAL_TIME_FALG = "【realTime】";
// 回调超时时间,单位:毫秒
public final static String REAL_TIME_KEY_VALUE_SPLIT = "=>";
// 与某个客户端的连接会话
private Session session;
private String token;
private String userName;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "token") String token, @PathParam(value = "userName") String userName) {
this.session = session;
this.token = token;
this.userName = userName;
boolean security = isAuth(token, userName);
if (security == false) {
try {
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Illegal token"));
} catch (IOException e) {
log.error("close session ocurre error: {} .", e.getMessage());
}
return;
}
webSocketServices.put(this.session.getId() + "_" + userName, this);
//在线数加1
addOnlineCount();
log.info("有新窗口开始监听:"+session.getId()+", userName:"+this.userName+", 当前在线会话数为:" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketServices.remove(this.session.getId());
//在线数减1
subOnlineCount();
log.info("有一连接关闭!当前在线会话数为:" + getOnlineCount());
}
/**
* 出现错误时处理方法
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误",error);
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("【服务端】收到来自窗口"+session.getId()+"的信息:"+message);
if (isHeartMessage(message)) return;
/*
* 如果消息格式为:"【realTime】"+{key}+"=>"+{data},则表示是实时消息,特殊处理,并执行回调函数
*/
if(message.startsWith(REAL_TIME_FALG)) {
String dataMsg = message.substring(REAL_TIME_FALG.length());
if(!dataMsg.contains(REAL_TIME_KEY_VALUE_SPLIT)) {
log.error("实时消息格式有误:{}", dataMsg);
}
String[] dataArr = dataMsg.split(REAL_TIME_KEY_VALUE_SPLIT);
String key = dataArr[0]; // 回调函数存储的key值
String data = dataArr[1]; // 真实数据部分
log.info("data:{}", data);
ICallBack callBack = callBackMap.get(key); // 获取回调函数
if(callBack != null) {
callBack.solve(data); // 执行回调函数
callBackMap.remove(key); // 移除待处理回调函数
}
}
}
/**
* 推送消息
* SendHandler为发送成功后的处理器
* SendHandler sendHandler = new SendHandler() {
@Override
public void onResult(SendResult result) {
log.info(result.toString());
}
};
*/
public void sendMessage(String message, SendHandler sendHandler) {
try {
// 异步发送消息
this.session.getAsyncRemote().sendText(message, sendHandler);
} catch (Exception e) {
log.error("send to application: {} fail.", message);
}
}
/**
* 推送消息(异步)
*/
public void sendMessage(String message) {
try {
// 异步发送消息
this.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
log.error("send to application: {} fail.", message);
}
}
/**
* 推送消息,anyc=true为异步,anyc为false为同步
*/
public void sendMessage(String message, boolean anyc) {
try {
sendMessage(this.session, message, anyc);
} catch (Exception e) {
log.error("send to application: {} fail.", message);
}
}
/**
* 推送消息,anyc=true为异步,anyc为false为同步
*/
public void sendMessage(Session session, String message, boolean anyc) {
try {
if(anyc) {
// 异步发送消息
session.getAsyncRemote().sendText(message);
} else {
// 同步发送消息
session.getBasicRemote().sendText(message);
}
} catch (Exception e) {
log.error("send to application: {} fail.", message);
}
}
/**
* sendForBack: 发送消息并支持回调函数
* @author chenpb
* @param key 回调key值,用于回调时判断,执行对应回调函数
* @param msg 发送的消息
* @param callBack 回调函数
*/
public void sendForBack(String key, String msg, ICallBack callBack) {
callBackMap.put(key, callBack);
this.sendMessage(msg);
}
/**
* sendForBack: 发送消息并等待回调结果
* 实时消息格式为:"【realTime】"+{key}+"=>"+{data}
* @author chenpb
* @param key
* @param msg
* @return
* @throws InterruptedException
*/
public MsgResult sendForBack(String key, String message) throws InterruptedException {
final MsgResult msgResult = new MsgResult();
// 发送消息
this.sendForBack(key, message, new ICallBack(){
@Override
public void solve(String result) {
log.info("回调数据:{}", result);
synchronized (msgResult) {
msgResult.setResult(result);
msgResult.notifyAll();
}
}
});
// 等待处理,不管有没超时,最终都会移除回调函数
synchronized(msgResult) {
long start = System.currentTimeMillis();
log.info("等待开始");
msgResult.wait(WAIT_TIME); // 超时时间
long end = System.currentTimeMillis();
log.info("等待结束,等待时间:{}s", (end - start)/1000);
WebSocketService.getCallBackMap().remove(key);
log.info("待回调函数数量:{}", WebSocketService.getCallBackMap().size());
}
return msgResult;
}
/**
* 判断是否是心跳消息
*/
public boolean isHeartMessage(String message) {
// ToDo ...
return false;
}
/**
* 鉴权
*/
private boolean isAuth(String token, String userName) {
// ToDo ...
return true;
}
public int getOnlineCount() {
return onlineCount.get();
}
private void addOnlineCount() {
onlineCount.incrementAndGet();
}
private void subOnlineCount() {
onlineCount.decrementAndGet();
}
public static ConcurrentHashMap<String, WebSocketService> getAgentserverendpoints() {
return webSocketServices;
}
public static ConcurrentHashMap<String, ICallBack> getCallBackMap() {
return callBackMap;
}
public WebSocketService getAgentServerEndpoint(String userName) {
for(Entry<String, WebSocketService> entry : webSocketServices.entrySet()) {
if(entry.getKey().equals(userName)) {
return entry.getValue();
}
}
return null;
}
public Session getSession() {
return session;
}
}
2.回调接口
代码如下(示例):
/***
* <b>function: 回调接口</b>
* @project sxssy-espms-job
* @package com.linkcm.sxssy.espms.job.common.job
* @filename IcallBack.java
* @createtime 2021年4月13日 下午3:16:04
* @author peibinchen
*/
public interface ICallBack {
public void solve(String result);
}
3.http接口服务
提供WebSocket模拟http接口形式的实时消息通讯接口
import io.swagger.annotations.ApiOperation;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.linkcm.sxssy.espms.job.common.bean.ResponseResult;
import com.linkcm.sxssy.espms.job.common.util.RandomFlagUtils;
import com.linkcm.sxssy.espms.job.common.util.RestResultUtils;
import com.linkcm.sxssy.espms.job.websocket.Service.WebSocketService;
import com.linkcm.sxssy.espms.job.websocket.model.MsgResult;
import com.linkcm.sxssy.espms.job.websocket.model.ReqMsgData;
/***
* <b>function: websocket http接口服务</b>
* @project sxssy-espms-job
* @package com.linkcm.sxssy.espms.job.websocket.Controller
* @filename WebSocketController.java
* @createtime 2021年4月14日 上午9:51:37
* @author peibinchen
*/
@Slf4j
@RestController
@RequestMapping("/websocket")
public class WebSocketController {
/**
* test: 测试消息发送并回调方法
* @author chenpb
* @return
* @throws InterruptedException
*/
@ApiOperation(value = "", notes = "")
@GetMapping(value = "/test")
public ResponseResult<Object> test() throws InterruptedException {
log.info("********* websocket实时推送数据开始 **************");
/*
* 测试数据
*/
String key = "test";
String jsonData = "【realTime】" + key + "=>测试";
ConcurrentHashMap<String, WebSocketService> service =
WebSocketService.getAgentserverendpoints();
MsgResult msgResult = new MsgResult();
for(Entry<String, WebSocketService> entry : service.entrySet()) {
WebSocketService point = entry.getValue();
// 获取待发送消息
log.info("发送的消息:" + jsonData);
// 发送消息
msgResult = point.sendForBack(key, jsonData);
}
log.info("********* websocket实时推送数据结束 **************");
return RestResultUtils.successResult(msgResult.getResult());
}
/**
* test: 消息发送并回调
* @author chenpb
* @return
* @throws InterruptedException
*/
@ApiOperation(value = "", notes = "")
@PostMapping(value = "/sendForBack")
public ResponseResult<Object> sendForBack(@RequestBody ReqMsgData reqMsgData) throws InterruptedException {
log.info("********* websocket实时推送消息开始 **************");
String key = RandomFlagUtils.randomDateTime();
String msg = WebSocketService.REAL_TIME_FALG + key + WebSocketService.REAL_TIME_KEY_VALUE_SPLIT + reqMsgData.getData();
ConcurrentHashMap<String, WebSocketService> service =
WebSocketService.getAgentserverendpoints();
MsgResult msgResult = new MsgResult();
for(Entry<String, WebSocketService> entry : service.entrySet()) {
WebSocketService point = entry.getValue();
// 获取待发送消息
log.info("发送的消息:" + msg);
// 发送消息
msgResult = point.sendForBack(key, msg);
}
log.info("********* websocket实时推送消息结束 **************");
return RestResultUtils.successResult(msgResult.getResult());
}
}
4.回调结果对象
import lombok.Data;
/***
* <b>function: 回调结果对象</b>
* @project sxssy-espms-job
* @package com.linkcm.sxssy.espms.job.common.job
* @filename MsgResult.java
* @createtime 2021年4月13日 下午4:05:56
* @author peibinchen
*/
@Data
public class MsgResult {
public String result;
}
5.http请求参数实体
import lombok.Data;
/***
* <b>function: http请求参数实体</b>
* @project sxssy-espms-job
* @package com.linkcm.sxssy.espms.job.websocket.model
* @filename ReqMsgData.java
* @createtime 2021年4月13日 下午5:22:56
* @author peibinchen
*/
@Data
public class ReqMsgData {
private String data;
}
参考资料
https://www.jianshu.com/p/4e678e639161