最近做一个新项目,由于作为项目负责人比较忙,比较久没更了,今天抽个时间写一点,也是给自己做个日记。
实现WebSocket我只写了三个类,直接上代码:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/**
* websocket初始化配置发布地址
* @author Run the ant(wangyijie)
* @date 2018年-----
* @version 1.0
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements
WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new CallQueueHandler(),"/testWebSocketServer").addInterceptors(new CalltHandshakeInterceptor());
registry.addHandler(new CallQueueHandler(), "/sockjs/webSocketServer").addInterceptors(new CalltHandshakeInterceptor())
.withSockJS();
}
}
这个类就相当于给服务器开放一个发布地址,用于websocket连接,我这里发布了testWebSocketServer;
那么具体的发布地址就是 ws://[ip]:[port]/项目发布名/testWebSocketServer
继承的这个类WebMvcConfigurerAdapter是一个拦截器,Spring5.0已经废弃了,想详细了解的可自行google/百度
然后呢,我们的WebSocket就发布成功喽。。。
但是呢,也不能让什么人都能连是吧,那不是乱套了, 所以呢,我们要做一个拦截器,在这里呢,我们把我们想进行的验证,以及各种数据缓存起来,比如:那么多的WebSocket连接,我如果想准确的找到其中一个,咋办?
这个时候呢拦截器就有作用啦。我们在每个连接成功的时候呢,用一个标识标志这个连接,当服务器想准确的给某个客户端发消息的时候,就能通过这个标识找到这个连接啦,上代码:
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
/**
* websocket拦截器
* 主要为了将session中存放的uid去除放到websorket的连接数据中
* @author Run the ant(wangyijie)
* @date 2018年----
* @version 1.0
*/
public class CalltHandshakeInterceptor implements HandshakeInterceptor {
private final static Logger logger = LoggerFactory.getLogger(CalltHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if(request.getHeaders().containsKey("Sec-WebSocket-Extensions")) {
request.getHeaders().set("Sec-WebSocket-Extensions", "permessage-deflate");
}
if(request instanceof ServerHttpRequest){
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if(session!=null){
//区分socket连接以定向发送消息
Object uid = session.getAttribute("UID");
if(uid != null){
attributes.put("uid", session.getAttribute("UID"));
}else{
return false;
}
}
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
// TODO Auto-generated method stub
}
}
那这里是什么意思呢, 当有连接访问服务器, 这个拦截器就只拦截Websocket的连接, 就是说当客户端想用websocket连接我的服务器,我先从session中找UID,我这里是UID,你们可以自定义哦,我这里如果有UID,就代表用户登录了,并且这个人是存在的,才能连, 这里根据自己业务处理,让不让连是你的事情啦。然后呢我把这个UID放到websocket连接里面,到时候我用到了也好找啊,嘿嘿。。。
下面这个呢就是主要部分了,是连接的处理类:
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
/**
* websocket处理类
* 主要为了在客户端建立连接时将session和uid绑定关系存放在缓存
* @author Run the ant(wangyijie)
* @date 2018年------
* @version 1.0
*/
@Service
public class CallQueueHandler extends AbstractWebSocketHandler {
private static final Logger logger;
public static final Map<String, WebSocketSession> userSocketSessionMap;
static{
userSocketSessionMap = new ConcurrentHashMap<String, WebSocketSession>();
logger = LoggerFactory.getLogger(CallQueueHandler.class);
}
@Override
protected void handleTextMessage(WebSocketSession session,TextMessage message) {
logger.info("WebSocket handleTextMessage 处理文本消息:sessionId={}", session.getId());
try{
super.handleTextMessage(session, message);
}catch(Exception e){
logger.info("WebSocket handleTextMessage 处理文本消息异常",e);
}
}
/**
* 建立连接后,把登录用户的uid和WebSocketSession保存
*/
@Override
public void afterConnectionEstablished(WebSocketSession session){
logger.info("WebSocket connected 已经建立连接:sessionId={} address={}", session.getId(), session.getLocalAddress().toString());
try{
String uid = session.getAttributes().get("uid") + "";
if(StringUtils.isNotEmpty(uid)){
userSocketSessionMap.put(uid, session);
}
}catch(Exception e){
logger.info("WebSocket connected 已经建立连接异常",e);
}
}
@Override
public void afterConnectionClosed(WebSocketSession session,CloseStatus status){
try{
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 移除当前用户的Socket会话
while (it.hasNext()) {
Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
logger.info("WebSocket 会话已经移除:用户UID" + entry.getKey());
break;
}
}
logger.info("WebSocket Closed 关闭连接:sessionId={} address={}", session.getId(), session.getLocalAddress().toString());
}catch(Exception e){
logger.info("WebSocket Closed 关闭连接异常:sessionId={}", session.getId(),e);
}
}
@Override
protected void handlePongMessage(WebSocketSession session,PongMessage message){
try{
super.handlePongMessage(session, message);
}catch(Exception e){
logger.info("WebSocket handlePongMessage异常:",e);
}
}
@Override
public void handleTransportError(WebSocketSession session,Throwable exception){
try{
if (session.isOpen()) {
session.close();
}
Iterator<Entry<String, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 移除当前抛出异常用户的Socket会话
while (it.hasNext()) {
Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("WebSocket handleTransportError:会话已经移除:用户ID" + entry.getKey());
break;
}
}
super.handleTransportError(session, exception);
}catch(Exception e){
logger.info("WebSocket handleTransportError异常",e);
}
}
@Override
protected void handleBinaryMessage(WebSocketSession session,BinaryMessage message){
try{
super.handleBinaryMessage(session, message);
}catch(Exception e){
logger.info("WebSocket handleBinaryMessage异常",e);
}
}
/**
* 发送给指定用户指定消息
* @param targetUid
* @param messages
*
* @throws IOException
*/
public boolean sendMessage(String targetUid,String messages){
boolean flag = false;
try{
if(null != targetUid){
if(userSocketSessionMap.containsKey(targetUid)){
WebSocketSession session = userSocketSessionMap.get(targetUid);
if (session != null && session.isOpen()) {
TextMessage message = new TextMessage(messages);
session.sendMessage(message);
logger.debug("WebSocket sendMessage 向targetUid={},发送消息成功,messages={}", targetUid,messages);
flag = true;
}else{
logger.debug("WebSocket sendMessage 向targetUid={},发送消息失败,session已经关闭", targetUid);
}
}else{
logger.debug("WebSocket sendMessage 发送消息失败, 未找到targetUid={}对应的对象,", targetUid);
}
}else{
logger.info("WebSocket sendMessage ,发送消息失败,targetUid为空");
}
}catch(Exception e){
logger.info("WebSocket sendMessage: 向targetUid={},发送消息异常 ",targetUid,e);
}
return flag;
}
/**
* 给所有在线用户发送消息
*
* @param message
*/
public void sendMessageToAllUsers(String messages) {
Iterator<Entry<String , WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
while (it.hasNext()) {
final Entry<String, WebSocketSession> entry = it.next();
if (entry.getValue().isOpen()) {
TextMessage message = new TextMessage(messages);
try {
entry.getValue().sendMessage(message);
} catch (IOException e) {
logger.info("WebSocket sendMessageToAllUsers: 向所有在线用户发送消息异常 ",e);
e.printStackTrace();
}
}
}
}
}
这里有2点需要主要,当初我们再刚连接服务器的时候,通过拦截器对websocket连接进行了处理,就是把登录用户的UID放到了该连接的附带参数中,现在呢我们把之前放的参数拿出来放到缓存,UID和连接一一对应,当我想给某个人发消息的时候,只需要知道想给谁发,知道这个人的UID 我就能找到这个人的连接了,对吧。所有在afterConnectionEstablished方法中,在连接成功后进行了绑定关系,后面给某个人发消息或者给所有人发消息,可以看一下代码。很好理解,就是对我的缓存绑定关系Map循环一下而已。。。如果是给一个人发,那就从Map中找到具体某个连接,如果是发所有人(广播),就循环整个Map发就行啦!
OK!到此结束,应该不难理解,代码拷过去直接改个发布地址,改一下拦截器基本就能用啦。。。要是哪里做的不好,欢迎指正啦!
什么? 还要JS怎么做? 我去! 行! 哥有~
var ws;//websocket实例
var lockReconnect = false;//避免重复连接
var wsUrl = "ws://[ip]:[port]/项目发布名/testWebSocketServer";
function createWebSocket(url) {
try {
if ('WebSocket' in window) {
ws = new WebSocket(url);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(url);
} else {
alert("Not WebSocket!");
}
initEventHandle();
} catch (e) {
reconnect(url);
}
}
function initEventHandle() {
ws.onclose = function (evnt) {
//console.log('websocket服务关闭了');
reconnect(wsUrl);
};
ws.onerror = function (evnt) {
//console.log('websocket服务出错了');
reconnect(wsUrl);
};
ws.onopen = function (evnt) {
//心跳检测重置
heartCheck.reset().start();
};
ws.onmessage = function (evnt) {
//如果获取到消息,心跳检测重置
//拿到任何消息都说明当前连接是正常的
//接受消息后的UI变化
doWithMsg(evnt);
heartCheck.reset().start();
}
//收到消息推送
function doWithMsg(e) {
var data = JSON.parse(e.data);
//说吧!!!当你在客户端接收到服务器消息的时候,想干啥!就在这里干!
}
}
function reconnect(url) {
if(lockReconnect) return;
lockReconnect = true;
//没连接上会一直重连,设置延迟避免请求过多
setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 2000);
}
//心跳检测
var heartCheck = {
timeout: 60000,//60秒
timeoutObj: null,
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.timeoutObj = setTimeout(function(){
//这里发送一个心跳,后端收到后,返回一个心跳消息,
//onmessage拿到返回的心跳就说明连接正常
ws.send("HeartBeat");
self.serverTimeoutObj = setTimeout(function(){//如果超过一定时间还没重置,说明后端主动断开了
ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
}, self.timeout)
}, this.timeout)
}
}
//初始化websocket
createWebSocket(wsUrl);
我这里为什么要做心跳呢? 如果你是用在移动端的APP上,稳定的连接,是可以不要的,但是注意哈,这个APP可不是说的那种阉割版的手机浏览器
那么如果你真是的阉割的手机浏览器,然后像在里面嵌入一个H5页面呢, 这个时候就需要心跳了,因为啥呢,因为APP不正经啊,正经的APP是会维护长连接的, 你这APP自己不维护只能业务上去维护了,浏览器本身是不会为你维护的,你得时不时告诉服务器你还活着对吧~~
emmmm~~~
具体实现呢,上面代码业务也不复杂,能看懂的吧。。。真要是不懂,
emmm~~~
直接复制过去,地址改一下, 你想干嘛写进去,就能用啦。。。。。。。。。。。。。