Spring快速应用WebSocket

9 篇文章 0 订阅
3 篇文章 0 订阅

最近做一个新项目,由于作为项目负责人比较忙,比较久没更了,今天抽个时间写一点,也是给自己做个日记。

 

实现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~~~

直接复制过去,地址改一下,  你想干嘛写进去,就能用啦。。。。。。。。。。。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Run_the_ant

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值