建站不止于增删改查,还有很多很有魅力的地方。对于通信聊天这块已经青睐好久了,前段时间在做的j2ee项目运用到Spring+SpringMVC+MyBatis的框架集合,是关于一个社交平台的网站,类似于facebook,twitter,微博等。在做完基本的CURD(例如评论模块)后,开始研究网站通信并应用于项目中。


提到通信,大家都知道Socket。确实,运用Socket能在服务器与客户端之间建立一个数据交换的通道。之前用java SE写过的Socket通信 —模拟用户登录简单地实现了服务器与客户端传送消息。但是再细想一下,如果要在项目中实现网页聊天功能,把Socket用到j2ee项目中,或许就没那么简单了。这时转向baidu与google寻找答案,原来,有WebSocket这套协议,关于WebSocket,来自IBM这两篇文章已经介绍地很详细了:WebSocket 实战,使用 HTML5 WebSocket 构建实时 Web 应用。


Spring Framework 4 includes a new spring-websocket module with comprehensive WebSocket support. It is compatible with the Java WebSocket API standard (JSR-356) and also provides additional value-add as explained in the rest of the introduction.来自Spring官方文档:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html

非常庆幸的是,在Spring 4.0以上开始支持WebSocket了,并给出一套API供开发者使用。

下面就开始讲解WebSocket如何应用于SSM框架,说明其中的工作原理,并在最后给出网页聊天效果图。


一:客户端(js)新建WebSocket对象,指定要进行握手连接的服务器地址:

var webSocket = new WebSocket("ws://"+socketPath+"/ws");
webSocket.onopen = function(event){console.log("连接成功");console.log(event);
};
webSocket.onerror = function(event){console.log("连接失败");console.log(event);
};
webSocket.onclose = function(event){console.log("Socket连接断开");console.log(event);
};
webSocket.onmessage = function(event){//接受来自服务器的消息//...
}

讲解:

在新建WebSocket对象时,给出的参数字符串中ws表明协议使用的是WebSocket协议,socketPath就是要连接的服务器地址,在下文会进一步说明。
如果成功连接,就会执行onopen;如果连接失败,就会执行onerror;如果连接断开,就会执行onclose,如果服务器有消息发送过来,就会执行onmessage。


二:服务端导入Spring WebSocket相关jar依赖:

<!--WebSocket 依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-messaging</artifactId><version>4.0.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-websocket</artifactId><version>4.0.5.RELEASE</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.3.1</version></dependency>

讲解:

关于SpringMVC,Mybatis的jar包依赖就不列出来了。本文重点为如何在SSM框架上应用WebSocket。


三:服务器添加WebSocket服务:

package web.webSocket;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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;/*** Component注解告诉SpringMVC该类是一个SpringIOC容器下管理的类* 其实@Controller, @Service, @Repository是@Component的细化*/
@Component
@EnableWebSocket
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {@AutowiredMyWebSocketHandler handler;public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {//添加websocket处理器,添加握手拦截器webSocketHandlerRegistry.addHandler(handler, "/ws").addInterceptors(new MyHandShakeInterceptor());//添加websocket处理器,添加握手拦截器webSocketHandlerRegistry.addHandler(handler, "/ws/sockjs").addInterceptors(new MyHandShakeInterceptor()).withSockJS();}
}

讲解:

1.首先说说上文提到的客户端指定握手连接的服务器地址:在jsp中定义socketPath为 String socketPath = request.getServerName()+”:”+request.getServerPort()+path+”/”;其中path的定义为 String path = request.getContextPath();相信做SSM项目的你们都很清楚了。getServerName()先得到服务器机的ip地址;getServerPort()得到相应的端口号;getContextPath()得到的是上下文路径,其实就是发布了的项目文件夹的文件名,我发布了的项目文件夹名为web,在这个文件夹下有META-INF,WEB-INF和一个默认的index.jsp,WEB-INF内的页面是不允许外界访问的,所以当我们要访问里面的jsp页面时唯一的方法就是通过springMVC的映射,不是吗?最后我把项目发布到远程服务器上并通过外网进行测试连接到的路径为: “ws://139.129.47.176:8089/web//ws”
2.我在139.129.47.176:8089/web/后面加上一些事先规定好的映射匹配字符就能访问页面。因此我总结的就是:139.129.47.176:8089/web/就能得到我Tomcat容器下的SpringIOC容器,里面都是我写好的controller,service接口对象。
3.注意到上面代码中有@Component注解,已经给出注释了,就是相当于告诉SpringMVC这是SpringIOC容器下管理的类,和@Controller注解其实是一样的,通过139.129.47.176:8089/web/能访问到Controller并做映射,通过139.129.47.176:8089/web/同样可以访问MyWebSocketConfig这个类在SpringIOC下的对象,从而服务端进行WebSocket服务。
4.写到这里,相信熟悉SSM运行流程的你们都应该懂WebSocket的路径了。
5.在上面的代码中出现到MyWebSocketHandler handler;与new MyHandShakeInterceptor()。其中handler规定了服务端WebSocket的处理。而MyHandShakeInterceptor是客户端与服务端握手连接前后拦截器。


四:握手拦截器MyHandShakeInterceptor:

package web.webSocket;import entity.User;
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;import javax.servlet.http.HttpSession;
import java.util.Map;/*** websocket握手拦截器* 拦截握手前,握手后的两个切面*/
public class MyHandShakeInterceptor implements HandshakeInterceptor {public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) serverHttpRequest).getServletRequest().getSession(false).getAttribute("user") + "]已经建立连接");if (serverHttpRequest instanceof ServletServerHttpRequest) {ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;HttpSession session = servletRequest.getServletRequest().getSession(false);// 标记用户User user = (User) session.getAttribute("user");if(user!=null){map.put("uid", user.getUserId());//为服务器创建WebSocketSession做准备System.out.println("用户id:"+user.getUserId()+" 被加入");}else{System.out.println("user为空");return false;}}return true;}public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {}
}

讲解:

1.拦截的概念就是在一个操作前,与在这个操作后的两个时间切面将要进行的动作。

2.客户端与服务端握手连接前将键名”uid“,值为用户id的这个键值对加入到指定参数map中。为服务器建立与相应客户端连接的WebSocketSession打下基础。


五:MyWebSocketHandler,WebSocket处理器:

package web.webSocket;import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import entity.Message;
import service.youandmeService;@Component
public class MyWebSocketHandler implements WebSocketHandler{@Autowiredprivate youandmeService youandmeService;//当MyWebSocketHandler类被加载时就会创建该Map,随类而生public static final Map<Integer, WebSocketSession> userSocketSessionMap;static {userSocketSessionMap = new HashMap<Integer, WebSocketSession>();}//握手实现连接后public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {int uid = (Integer) webSocketSession.getAttributes().get("uid");if (userSocketSessionMap.get(uid) == null) {userSocketSessionMap.put(uid, webSocketSession);}}//发送信息前的处理public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {if(webSocketMessage.getPayloadLength()==0)return;//得到Socket通道中的数据并转化为Message对象Message msg=new Gson().fromJson(webSocketMessage.getPayload().toString(),Message.class);Timestamp now = new Timestamp(System.currentTimeMillis());msg.setMessageDate(now);//将信息保存至数据库youandmeService.addMessage(msg.getFromId(),msg.getFromName(),msg.getToId(),msg.getMessageText(),msg.getMessageDate());//发送Socket信息sendMessageToUser(msg.getToId(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));}public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {}/*** 在此刷新页面就相当于断开WebSocket连接,原本在静态变量userSocketSessionMap中的* WebSocketSession会变成关闭状态(close),但是刷新后的第二次连接服务器创建的* 新WebSocketSession(open状态)又不会加入到userSocketSessionMap中,所以这样就无法发送消息* 因此应当在关闭连接这个切面增加去除userSocketSessionMap中当前处于close状态的WebSocketSession,* 让新创建的WebSocketSession(open状态)可以加入到userSocketSessionMap中* @param webSocketSession* @param closeStatus* @throws Exception*/public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {System.out.println("WebSocket:"+webSocketSession.getAttributes().get("uid")+"close connection");Iterator<Map.Entry<Integer,WebSocketSession>> iterator = userSocketSessionMap.entrySet().iterator();while(iterator.hasNext()){Map.Entry<Integer,WebSocketSession> entry = iterator.next();if(entry.getValue().getAttributes().get("uid")==webSocketSession.getAttributes().get("uid")){userSocketSessionMap.remove(webSocketSession.getAttributes().get("uid"));System.out.println("WebSocket in staticMap:" + webSocketSession.getAttributes().get("uid") + "removed");}}}public boolean supportsPartialMessages() {return false;}//发送信息的实现public void sendMessageToUser(int uid, TextMessage message)throws IOException {WebSocketSession session = userSocketSessionMap.get(uid);if (session != null && session.isOpen()) {session.sendMessage(message);}}
}

讲解:

1.这个处理器的@Component注解就是告诉Spring将这个类的对象注入到IOC容器中,这样在MyWebSocketConfig中才可以通过@Autowired将其自动装载,进而使用。
2.简单地说说这个处理器,握手实现连接后会执行afterConnectionEstablished()方法,这个方法就是将握手连接后为与客户端实现通信而建立的WebSocketSession加入到静态变量userSocketSessionMap中。
3.当客户端断开连接后会执行afterConnectionClosed(),这时需要将与客户端对应的WebSocketSession从userSocketSessionMap中移除,原因已在注释中给出,简直血的教训,调试了好久才发现….
4.客户端一有消息发送至服务器就会自动执行handleMessage()方法,其中Message msg=new Gson().fromJson(webSocketMessage.getPayload().toString(),Message.class);将JSON形式的数据解析成Message对象,Message的定义稍后给出。
5.服务器发送信息至客户端只需要一句话,就是通过在服务器中WebSocketSession的sendMessage()方法,详情都在代码中。


六:客户端发送信息与接受信息:

发送:

var data = {};//新建data对象,并规定属性名与相应的值data['fromId'] = sendUid;data['fromName'] = sendName;data['toId'] = to;data['messageText'] = $(".contactDivTrue_right_input").val();webSocket.send(JSON.stringify(data));//将对象封装成JSON后发送至服务器

接收:

var message = JSON.parse(event.data);//将数据解析成JSON形式

讲解:

  1. 发送信息时需要将对象转换为JSON形式的数据,因为服务器本来就是将JSON数据转换成对象的。
  2. 客户端接收信息时将数据解析成JSON形式后就能在js中获取相应的数据。

七:Message类:

package entity;import java.sql.Timestamp;
import java.util.Date;/*** Created by Administrator on 2016/8/15.*/
public class Message {private int messageId;private int fromId;private String fromName;private int toId;private String messageText;private Timestamp messageDate;public Message() {}public int getMessageId() {return messageId;}public void setMessageId(int messageId) {this.messageId = messageId;}public int getFromId() {return fromId;}public void setFromId(int fromId) {this.fromId = fromId;}public String getFromName() {return fromName;}public void setFromName(String fromName) {this.fromName = fromName;}public int getToId() {return toId;}public void setToId(int toId) {this.toId = toId;}public String getMessageText() {return messageText;}public void setMessageText(String messageText) {this.messageText = messageText;}public Timestamp getMessageDate() {return messageDate;}public void setMessageDate(Timestamp messageDate) {this.messageDate = messageDate;}@Overridepublic String toString() {return "Message{" +"messageId=" + messageId +", fromId=" + fromId +", fromName='" + fromName + '\'' +", toId=" + toId +", messageText='" + messageText + '\'' +", messageDate=" + messageDate +'}';}
}

SSM框架+WebSocket实现网页聊天(Spring+SpringMVC+MyBatis+WebSocket)相关推荐

  1. SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    登录 | 注册 收藏成功 确定 收藏失败,请重新收藏 确定 查看所有私信查看所有通知 暂没有新通知 想要绕过微信小程序开发中的坑吗?不妨来听这个,今晚8点,1小时帮你搞定! 14小时以前 CSDN日报 ...

  2. SSM框架详细整合教程(Spring+SpringMVC+MyBatis)

    动机 使用maven已经有一段时间了,但项目是别人搭建好的,因此一直想着自己要学习搭建一下.网上找了些资料后,结合自己实验,花了点时间就搞好,老样子,写在博客上,免得日后忘记. 本文链接:http:/ ...

  3. [转]SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    使用SSM(spring.SpringMVC和Mybatis)已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方.之前没有记录SSM整合 ...

  4. 基于SSM的家校通系统(Spring+SpringMVC+Mybatis+mysql)

    这是一基于SSM框架开发的家校通. 源代码: 基于SSM的家校通系统(Spring+SpringMVC+Mybatis+mysql).zip-Java文档类资源-CSDN下载这是一基于SSM(spri ...

  5. 基于ssm的志愿者管理系统(idea+spring+springmvc+mybatis+jsp)

    一.系统简介 本项目采用idea工具开发,jsp+spring+spring-mvc+mybatis+jquery技术编写,数据库采用的是mysql,navicat开发工具. 系统一共分为4个角色分别 ...

  6. java框架ssm整合_SSM三大框架整合详细教程(Spring+SpringMVC+MyBatis)

    使用 SSM ( Spring . SpringMVC 和 Mybatis )已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方.之前没 ...

  7. 基于SSM的台球室俱乐部管理系统Spring+SpringMVC+MyBatis

    摘 要 随着科学技术的不断提高,计算机科学与技术日趋成熟,计算机应用到生产和生活的各个领域,发挥了越来越重要的作用.作为计算机应用的一部分,使用计算机对理发店信息进行管理,具有手工操作无法比拟的优点, ...

  8. java SSM 框架 微信自定义菜单 快递接口 SpringMVC mybatis redis shiro ehcache websocket

    获取[下载地址] QQ: 313596790 官网 http://www.fhadmin.org/ A 代码编辑器,在线模版编辑,仿开发工具编辑器,pdf在线预览,文件转换编码 B 集成代码生成器 [ ...

  9. java SSM 框架 微信自定义菜单 快递接口 SpringMVC mybatis redis shiro

    A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 ,0个代码不用写,生 ...

最新文章

  1. docker之Dockerfile实践
  2. python exe运行报 编码错误_python运行显示编码错误
  3. 使用Memcache缓存mysql数据库操作的原理和缓存过程浅析
  4. c语言编程题2^0+2^1+……+2e63,牛客网刷题33(2道题)
  5. markdown使用markdown-viewer生成目录_谷歌浏览器查看m文件
  6. JAVA反射系列之Method,java.lang.reflect.Method的使用。
  7. 关于 K8S 探针(startupProbe、livenessProbe、readinessProbe)的最佳实践
  8. 快递公司type字典
  9. avod论文理解与代码解读
  10. java基于ssm的房屋租赁管理系统
  11. Android音频开发(二):录制音频(WAV及MP3格式)
  12. 阿里巴巴宣布5.4亿战略投资中国万网==互联网电子商务绑定互联网基础服务为手机电子商务开拓铺路
  13. 拷贝(添加)本地音乐到iPhone、iPad设备(最新iTunes12.7)
  14. Spring的ioc控制反转
  15. adb命令查看手机电量_adb获取电池信息以及电量消耗信息
  16. MLY -- 13.Error analysis:look at dev set examples to evaluate ideas
  17. Android 手机设备信息获取使用详解
  18. 全国最新行政区划sql文件
  19. 目前流行的个人台式计算机属于,目前个人计算机的主要机型.doc
  20. 联想Z475拆修-板号KL6C

热门文章

  1. Arduino使用敲击模块和光遮断
  2. sigsuspend 与sigwait 的区别
  3. linux centos java kumo图片合成文字 词云插件 字体乱码问题
  4. Andoird conflicts with another tag that has the same ID
  5. ZBB – ZERO Bug Bounce
  6. 使用PMM图形化监控MySQL MGR动态
  7. 攻防世界逆向高手题之dmd-50
  8. 查看路由器拨号的宽带密码
  9. c++基本输入输出 输出不同精度的PI(程序给出的PI值为 3.14159265358979323846)。
  10. 中国大数据综合服务提供商Top100排行榜