# WebSocket
WebSocket协议支持(在受控环境中运行不受信任的代码的)客户端与(选择加入该代码的通信的)远程主机之间进行全双工通信。用于此的安全模型是Web浏览器常用的基于原始的安全模式。协议包括一个开放的握手以及随后的TCP层上的消息帧。该技术的目标是为基于浏览器的、需要和服务器进行双向通信的(服务器不能依赖于打开多个HTTP连接(例如,使用XMLHttpRequest或<iframe>和长轮询))应用程序提供一种通信机制。
# socket消息推送流程
1.后台创建socket服务;
2.用户登录后与后台建立socket连接,默认使用websocket,如果浏览器不支持则使用scokjs连接;
3.建立连接后,服务端可以向用户推送信息;
javaweb中,socket的实现方式有多种,这里使用Spring-webscoket的方式实现。
# demo
搭建环境
在SpringMVC的项目基础上,导入websocket的相关jar包。
<dependency><groupId>org.springframework</groupId><artifactId>spring-websocket</artifactId><version>4.1.9.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-messaging</artifactId><version>4.1.9.RELEASE</version></dependency>
websocket服务端实现类
@Configuration@EnableWebMvc@EnableWebSocketpublic class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{private static final Logger logger = LoggerFactory.getLogger(WebSocketConfig.class);@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {System.out.println("==========================注册socket");//注册websocket server实现类,"/webSocketServer"访问websocket的地址registry.addHandler(msgSocketHandle(),"/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor());//使用socketjs的注册方法registry.addHandler(msgSocketHandle(),"/sockjs/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor()).withSockJS();}/**** @return 消息发送的Bean*/@Bean(name = "msgSocketHandle")public WebSocketHandler msgSocketHandle(){return new MsgScoketHandle();}}
这里使用的config配置的形式注册bean和配置,所以需要在SpringMVC的配置文件中添加对类的自动扫描
<mvc:annotation-driven /><context:component-scan base-package="com.wqh.websocket"/>
拦截器类
主要是获取到当前连接的用户,并把用户保存到WebSocketSession中
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {private static final Logger logger = LoggerFactory.getLogger(WebSocketHandshakeInterceptor.class);/*** 握手前* @param request* @param response* @param webSocketHandler* @param attributes* @return* @throws Exception*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {logger.info("握手操作");if (request instanceof ServletServerHttpRequest){ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;HttpSession session = servletServerHttpRequest.getServletRequest().getSession(false);if(session != null){//从session中获取当前用户User user = (User) session.getAttribute("user");attributes.put("user",user);}}return true;}/*** 握手后* @param serverHttpRequest* @param serverHttpResponse* @param webSocketHandler* @param e*/@Overridepublic void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {}}
socket处理消息类
@Componentpublic class MsgScoketHandle implements WebSocketHandler {/**已经连接的用户*/private static final ArrayList<WebSocketSession> users;static {//保存当前连接用户users = Lists.newArrayList();}/*** 建立链接* @param webSocketSession* @throws Exception*/@Overridepublic void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {//将用户信息添加到list中users.add(webSocketSession);System.out.println("=====================建立连接成功==========================");User user = (User) webSocketSession.getAttributes().get("user");if(user != null){System.out.println("当前连接用户======"+user.getName());}System.out.println("webSocket连接数量====="+users.size());}/*** 接收消息* @param webSocketSession* @param webSocketMessage* @throws Exception*/@Overridepublic void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {User user = (User) webSocketSession.getAttributes().get("user");System.out.println("收到用户:"+user.getName()+"的消息");System.out.println(webSocketMessage.getPayload().toString());System.out.println("===========================================");}/*** 异常处理* @param webSocketSession* @param throwable* @throws Exception*/@Overridepublic void handleTransportError(WebSocketSession webSocketSession, Throwable throwable){if (webSocketSession.isOpen()){//关闭sessiontry {webSocketSession.close();} catch (IOException e) {}}//移除用户users.remove(webSocketSession);}/*** 断开链接* @param webSocketSession* @param closeStatus* @throws Exception*/@Overridepublic void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {users.remove(webSocketSession);User user = (User) webSocketSession.getAttributes().get("user");System.out.println(user.getName()+"断开连接");}@Overridepublic boolean supportsPartialMessages() {return false;}/*** 发送消息给指定的用户* @param user* @param messageInfo*/public void sendMessageToUser(User user, TextMessage messageInfo){for (WebSocketSession session : users) {User sessionUser = (User) session.getAttributes().get("user");//根据用户名去判断用户接收消息的用户if(user.getName().equals(sessionUser.getName())){try {if (session.isOpen()){session.sendMessage(messageInfo);System.out.println("发送消息给:"+user.getName()+"内容:"+messageInfo);}break;} catch (IOException e) {e.printStackTrace();}}}}}
controller及页面
这里简单的模拟登录,前台传入登录参数,直接将参数保存到session中。
@RequestMapping("websocket")@Controllerpublic class UserController {@Autowiredprivate MsgScoketHandle msgScoketHandle;@RequestMapping("login")public String login(User user, HttpServletRequest request){user.setId(UUID.randomUUID().toString().replace("-",""));request.getSession().setAttribute("user",user);return "/index";}@ResponseBody@RequestMapping("sendMsg")public String sendMag(String content,String toUserName){User user = new User();user.setName(toUserName);TextMessage textMessage = new TextMessage(content);msgScoketHandle.sendMessageToUser(user,textMessage);return "200";}}
登录页面省略,直接socket连接页面,这里使用sockjs来创建连接,所以需要先添加js文件
sockjs.min.js
<script>$(document).ready(function() {var ws;if ('WebSocket' in window) {ws = new WebSocket("ws://"+window.location.host+"/webSocketServer");} else if ('MozWebSocket' in window) {ws = new MozWebSocket("ws://"+window.location.host+"/webSocketServer");} else {//如果是低版本的浏览器,则用SockJS这个对象,对应了后台“sockjs/webSocketServer”这个注册器,//它就是用来兼容低版本浏览器的ws = new SockJS("http://"+window.location.host+"/sockjs/webSocketServer");}ws.onopen = function (evnt) {};//接收到消息ws.onmessage = function (evnt) {alert(evnt.data);$("#msg").html(evnt.data);};ws.onerror = function (evnt) {console.log(evnt)};ws.onclose = function (evnt) {}$("#btn1").click(function () {ws.send($("#text").val());});$("#btn2").bind("click",function () {var url = "${pageContext.request.contextPath}/websocket/sendMsg";var content = $("#text").val();var toUserName = "admin"$.ajax({data: "content=" + content + "&toUserName=" + toUserName,type: "get",dataType: 'text',async: false,contentType: "application/x-www-form-urlencoded;charset=UTF-8",encoding: "UTF-8",url: url,success: function (data) {alert(data.toString());},error: function (msg) {alert(msg);},});})});</script><body>当前登录用户:${pageContext.session.getAttribute("user").name}<br><input type="text" id="text"><button id="btn1" value="发送给后台">发送给后台</button><button id="btn2" value="发送给其他用户">发送给其他用户</button><div id="msg"></div></body></html>
启动项目
在控制可以看到socket注册成功
访问页面,第一个用户使用admin登录,第二个使用1234登录
首先将消息发送给后台,后台打印消息
使用1234用户发送消息给admin
# 爬坑
在Springmvc项目中都会指定连接访问的后缀,比如.do、.action,但是这里会导致按照以上配置会导致前端连接socket服务时404。
我的解决办法是修改web.xml,将DispatcherServlet的<url-pattern>改为/。。。但是新的问题又出现了,页面无法加载资源文件,所以还需要在SpringMVC.xml中添加对静态资源的配置,这里具体的mapping和location看自己的具体项目。
<mvc:resources mapping="/css/**" location="/css/" /><mvc:resources mapping="/images/**" location="/images/" /><mvc:resources mapping="/js/**" location="/js/" />
本文介绍了如何在Spring MVC项目中利用WebSocket进行实时消息推送。首先解释了WebSocket协议的作用,然后详细阐述了socket消息推送的流程,包括后台创建socket服务、用户登录后的连接建立以及服务端信息推送。接着,通过一个demo展示了Spring-WebSocket的配置和实现,包括环境搭建、拦截器、消息处理类和控制器。在实施过程中,作者还分享了遇到的坑以及解决办法,如调整DispatcherServlet配置和静态资源映射。
2289

被折叠的 条评论
为什么被折叠?



