最近利用webSocket完成了整個聊天的模塊..這邊做一個關於ssm的webSocket的基本使用總結:(附一個完整的demo案例)
按慣例應該要先介紹webSocket.直接甩鏈接,自己點連接看吧..
https://baike.baidu.com/item/WebSocket/1953845?fr=aladdin
http://www.ruanyifeng.com/blog/2017/05/websocket.html
簡單直接的上代碼:
1依賴:
<properties>
<!-- spring版本號 -->
<spring.version>4.0.2.RELEASE</spring.version>
</properties>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>${spring.version}</version>
</dependency>
2編寫處理類,實現webSocketHandler處理
package cn.websocket;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import cn.entity.Message;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Socket處理器
*/
@Component
public class MyWebSocketHandler implements WebSocketHandler {
public static final Map<Long, WebSocketSession> userSocketSessionMap;
static {
userSocketSessionMap = new HashMap<Long, WebSocketSession>();
}
/**
* 建立連接後
*/
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Long uid = (Long) session.getAttributes().get("uid");
if (userSocketSessionMap.get(uid) == null) {
userSocketSessionMap.put(uid, session);
}
}
/**
* 消息處理,在客戶端通過Websocket API發送的消息會經過這裏,然後進行相應的處理
*/
public void handleMessage(WebSocketSession session,WebSocketMessage<?> message) throws Exception {
if (message.getPayloadLength() == 0)
return;
Message msg = new Gson().fromJson(message.getPayload().toString(),Message.class);
msg.setDate(new Date());
sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
}
/**
* 消息傳輸錯誤處理
*/
public void handleTransportError(WebSocketSession session,Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 移除Socket會話
while (it.hasNext()) {
Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket會話已經移除:用戶ID" + entry.getKey());
break;
}
}
}
/**
* 關閉連接後
*/
public void afterConnectionClosed(WebSocketSession session,CloseStatus closeStatus) throws Exception {
System.out.println("Websocket:" + session.getId() + "已經關閉");
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 移除Socket會話
while (it.hasNext()) {
Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().getId().equals(session.getId())) {
userSocketSessionMap.remove(entry.getKey());
System.out.println("Socket會話已經移除:用戶ID" + entry.getKey());
break;
}
}
}
public boolean supportsPartialMessages() {
return false;
}
/**
* 給所有在線用戶發送消息
*
* @param message
* @throws IOException
*/
public void broadcast(final TextMessage message) throws IOException {
Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
// 多線程羣發
while (it.hasNext()) {
final Entry<Long, WebSocketSession> entry = it.next();
if (entry.getValue().isOpen()) {
// entry.getValue().sendMessage(message);
new Thread(new Runnable() {
public void run() {
try {
if (entry.getValue().isOpen()) {
entry.getValue().sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
/**
* 給某個用戶發送消息
*
* @param userName
* @param message
* @throws IOException
*/
public void sendMessageToUser(Long uid, TextMessage message)throws IOException {
WebSocketSession session = userSocketSessionMap.get(uid);
if (session != null && session.isOpen()) {
session.sendMessage(message);
}
}
}
3創建握手攔截.實現HandshakeInterceptor 接口
package cn.websocket;
import java.util.Map;
import javax.servlet.http.HttpSession;
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;
/**
* Socket建立連接(握手)和斷開
* @author Hm1995
*
*/
public class HandShake implements HandshakeInterceptor {
public boolean beforeHandshake(ServerHttpRequest request,ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
System.out.println("Websocket:用戶[ID:"+ ((ServletServerHttpRequest) request).getServletRequest()
.getSession(false).getAttribute("uid") + "]已經建立連接");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
// 標記用戶
Long uid = (Long) session.getAttribute("uid");
if (uid != null) {
attributes.put("uid", uid);
} else {
return false;
}
}
return true;
}
public void afterHandshake(ServerHttpRequest request,ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
}
}
4編寫配置處理器:
package ecan.bi.toc.websocket.service;
import javax.annotation.Resource;
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;
/**
* websocket服務註冊
* WebScoket配置處理器
*/
@Component
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Resource
MyWebSocketHandler handler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(handler, "/socketServer/webSocket").addInterceptors(new HandShake());
registry.addHandler(handler, "/socketjs/socketServer").addInterceptors(new HandShake()).withSockJS();
}
}
配置之後.方便頁面根據url來連接到webSocket.
連接成功之後頁面js的其他方法以及整個頁面:取名talk.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getServerName() + ":"
+ request.getServerPort() + path + "/";
String basePath2 = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="<%=basePath2%>resources/jquery.js"></script>
<style>
textarea {
height: 300px;
width: 100%;
resize: none;
outline: none;
}
input[type=button] {
float: right;
margin: 5px;
width: 50px;
height: 35px;
border: none;
color: white;
font-weight: bold;
outline: none;
}
.clear {
background: red;
}
.send {
background: green;
}
.clear:active {
background: yellow;
}
.send:active {
background: yellow;
}
.msg {
width: 100%;
height: 25px;
outline: none;
}
#content {
border: 1px solid gray;
width: 100%;
height: 400px;
overflow-y: scroll;
}
.from {
background-color: green;
width: 80%;
border-radius: 10px;
height: 30px;
line-height: 30px;
margin: 5px;
float: left;
color: white;
padding: 5px;
font-size: 22px;
}
.to {
background-color: gray;
width: 80%;
border-radius: 10px;
height: 30px;
line-height: 30px;
margin: 5px;
float: right;
color: white;
padding: 5px;
font-size: 22px;
}
.name {
color: gray;
font-size: 12px;
}
.tmsg_text {
color: white;
background-color: rgb(47, 47, 47);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.fmsg_text {
color: white;
background-color: rgb(66, 138, 140);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.sfmsg_text {
color: white;
background-color: rgb(148, 16, 16);
font-size: 18px;
border-radius: 5px;
padding: 2px;
}
.tmsg {
clear: both;
float: right;
width: 80%;
text-align: right;
}
.fmsg {
clear: both;
float: left;
width: 80%;
}
</style>
<script>
var path = '<%=basePath%>';
var uid=${uid eq null?-1:uid};
if(uid==-1){
location.href="<%=basePath2%>";
}
var from=uid;
var fromName='${name}';
var to=uid==1?2:1;
var websocket;
if ('WebSocket' in window) {
websocket = new WebSocket("ws://" + path + "/ws?uid="+uid);
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://" + path + "/ws"+uid);
} else {
websocket = new SockJS("http://" + path + "/ws/sockjs"+uid);
}
websocket.onopen = function(event) {
console.log("WebSocket:已連接");
console.log(event);
};
websocket.onmessage = function(event) {
var data=JSON.parse(event.data);
console.log("WebSocket:收到一條消息",data);
var textCss=data.from==-1?"sfmsg_text":"fmsg_text";
$("#content").append("<div class='fmsg'><label class='name'>"+data.fromName+" "+data.date+"</label><div class='"+textCss+"'>"+data.text+"</div></div>");
scrollToBottom();
};
websocket.onerror = function(event) {
console.log("WebSocket:發生錯誤 ");
console.log(event);
};
websocket.onclose = function(event) {
console.log("WebSocket:已關閉");
console.log(event);
}
function sendMsg(){
var v=$("#msg").val();
if(v==""){
return;
}else{
var data={};
data["from"]=from;
data["fromName"]=fromName;
data["to"]=to;
data["text"]=v;
websocket.send(JSON.stringify(data));
$("#content").append("<div class='tmsg'><label class='name'>我 "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"</label><div class='tmsg_text'>"+data.text+"</div></div>");
scrollToBottom();
$("#msg").val("");
}
}
function scrollToBottom(){
var div = document.getElementById('content');
div.scrollTop = div.scrollHeight;
}
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小時
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
function send(event){
var code;
if(window.event){
code = window.event.keyCode; // IE
}else{
code = e.which; // Firefox
}
if(code==13){
sendMsg();
}
}
function clearAll(){
$("#content").empty();
}
</script>
</head>
<body>
歡迎:${sessionScope.name }
<div id="content"></div>
<input type="text" placeholder="請輸入要發送的信息" id="msg" class="msg" onkeydown="send(event)">
<input type="button" value="發送" class="send" onclick="sendMsg()" >
<input type="button" value="清空" class="clear" onclick="clearAll()">
</body>
</html>
第二個廣播頁面:取名broadcast.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath= request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="<%=basePath%>resources/jquery.js"></script>
<script type="text/javascript">
var path='<%=basePath%>';
function broadcast(){
$.ajax({
url:path+'msg/broadcast',
type:"post",
data:{text:$("#msg").val()},
dataType:"json",
success:function(data){
alert("發送成功");
}
});
}
</script>
</head>
<body>
發送廣播
<textarea style="width:100%;height:300px;" id="msg" ></textarea>
<input type="button" value="發送" onclick="broadcast()">
</body>
</html>
當然,自己導一個jquery進去
第三個頁面:index.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>WebSocket示例</title>
<script type="text/javascript" src="resources/jquery.js"></script>
</head>
<body>
<form action="msg/login" method="post">
用戶名:
<select name="id">
<option value="1">張三</option>
<option value="2">李四</option>
</select><br>
密碼:
<input name="password" type="text" value="123456">
<input type="submit" value="登錄">
</form>
</body>
</html>
ps:注意三個jsp的層級關係:
springMvc.xml和web.xml配置:
web.xml:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
spring-mvc.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
">
<mvc:annotation-driven />
<mvc:resources location="/resources/" mapping="/resources/**" />
<context:component-scan base-package="cn.*" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="1" />
</bean>
</beans>
實體類和Controller代碼:
實體類Message.java
package cn.entity;
import java.util.Date;
/**
* 消息類
*
* @author HuangM
* @Date 2020年5月14日16:28:26
*/
public class Message {
// 發送者
public Long from;
// 發送者名稱
public String fromName;
// 接收者
public Long to;
// 發送的文本
public String text;
// 發送日期
public Date date;
public Long getFrom() {
return from;
}
public void setFrom(Long from) {
this.from = from;
}
public Long getTo() {
return to;
}
public void setTo(Long to) {
this.to = to;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getFromName() {
return fromName;
}
public void setFromName(String fromName) {
this.fromName = fromName;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
User.java:
package cn.entity;
public class User {
private Long id;
private String name;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
MsgController.java:
package cn.controller;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.socket.TextMessage;
import cn.entity.User;
import cn.entity.Message;
import cn.websocket.MyWebSocketHandler;
import com.google.gson.GsonBuilder;
@Controller
@RequestMapping("/msg")
public class MsgController {
@Resource
MyWebSocketHandler handler;
Map<Long, User> users = new HashMap<Long, User>();
// 模擬一些數據
@ModelAttribute
public void setReqAndRes() {
User u1 = new User();
u1.setId(1L);
u1.setName("張三");
users.put(u1.getId(), u1);
User u2 = new User();
u2.setId(2L);
u2.setName("李四");
users.put(u2.getId(), u2);
}
// 用戶登錄
@RequestMapping(value = "login", method = RequestMethod.POST)
public ModelAndView doLogin(User user, HttpServletRequest request) {
request.getSession().setAttribute("uid", user.getId());
request.getSession().setAttribute("name",users.get(user.getId()).getName());
return new ModelAndView("redirect:talk");
}
// 跳轉到交談聊天頁面
@RequestMapping(value = "talk", method = RequestMethod.GET)
public ModelAndView talk() {
return new ModelAndView("talk");
}
// 跳轉到發佈廣播頁面
@RequestMapping(value = "broadcast", method = RequestMethod.GET)
public ModelAndView broadcast() {
return new ModelAndView("broadcast");
}
// 發佈系統廣播(羣發)
@ResponseBody
@RequestMapping(value = "broadcast", method = RequestMethod.POST)
public void broadcast(String text) throws IOException {
Message msg = new Message();
msg.setDate(new Date());
msg.setFrom(-1L);
msg.setFromName("系統廣播");
msg.setTo(0L);
msg.setText(text);
handler.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
}
}
一些必要的spring環境依賴....自己搞定.搞不定的話,回爐重造.
配置完畢.效果:
開啓另外一個瀏覽器.登陸李四.
張三收到消息:
完成交互.
跑不起來的同志去我的碼雲下載項目導入自己的開發工具即可.