参考:
【Java Web开发学习】Spring MVC整合WebSocket通信
很多时候,后端增删改查了一个数据,前端需要实时进行数据刷新,这时候,正常的Http请求就无法满足要求了(不轮询),就需要一个可以实现客户端和服务器端的长连接,双向实时通信。就是websocket。
websocket是java标准库的一部分,位于javax包下,但它只是定义一些接口。
websocket有不同的实现,如Tomcat的,jetty的,Spring的,还有一个名叫TooTallNate组织发布的java-websocket库,atmosphere库,socket.io的java版本等。
这里使用web应用服务器是tomcat8,在javax.websocket接口出来之前,tomcat7就已经对websocket提供支持了。于是在javax.websocket出来之后,tomcat8就开始废弃tomcat7中定义的websocket,tomcat7关于websocket的包位于org.apache.catalina.websocket中。
首先,tomcat8使用的javax.websocket,所以可以直接引用tomcat安装目录下/lIbrary/websocket.jar这个jar包,也可以直接在pom.xml中定义好Maven的依赖
<!-- Web Socket-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
下面直接看服务端代码:
package com.springapp.mvc.websocket;
import com.springapp.mvc.util.JsonUtils;
import org.apache.http.util.TextUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.socket.server.standard.SpringConfigurator;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by qinyy on 12/10/2018.
*/
/**
* 加configurator = SpringConfigurator.class是为了让这个类中可以通过注解的方式 注入实例
* 比如如果不加这个选项的话,那么使用@Autowired 注解引入的实例就是null,无法被spring注入
*/
@ServerEndpoint(value = "/websocket/{id}", configurator = SpringConfigurator.class)
public class WebSocketService
{
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// 维护一个map,用来管理不同用户的不同socket实例
public static HashMap<String,WebSocketService> webSocketMap = new HashMap<String, WebSocketService>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private String currentKey;
private org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(WebSocketService.class.getSimpleName());
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session,@PathParam(value = "id")String id){
this.session = session;
webSocketMap.put(id,this); // 加入map中
currentKey = id;
addOnlineCount(); //在线数加1
logger.info("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
if(!TextUtils.isEmpty(currentKey))
webSocketMap.remove(currentKey);
subOnlineCount(); //在线数减1
logger.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.info("来自客户端的消息:" + message);
}
/**
* 发生错误时调用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
logger.info("发生错误");
error.printStackTrace();
}
public void sendObject(Object o) throws IOException
{
try {
this.session.getBasicRemote().sendText(JsonUtils.encode(o));
} catch (Exception e) {
logger.error(e.getMessage());
}
}
/**
* 向指定client发送信息
* @param id
* @param o
*/
public static void sendObjectToSomebody(String id,Object o) throws IOException
{
if(webSocketMap != null && webSocketMap.containsKey(id))
webSocketMap.get(id).sendObject(o);
}
/**
* 向所有的client发送相同的数据
* @param o
* @throws IOException
*/
public static void sendObjectToAll(Object o) throws IOException
{
Iterator iter = webSocketMap.entrySet().iterator();
while (iter.hasNext())
{
Map.Entry entry = (Map.Entry) iter.next();
WebSocketService ser = (WebSocketService) entry.getValue();
ser.sendObject(o);
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketService.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketService.onlineCount--;
}
}
这里实现了简单的websocket的管理,每次websocket启动,都会从启动的url中获取到client传过来的id,用来标识一个唯一的client,这样就可以实现向指定client发送信息了。这里需要注意的是,取ID时用的注解一定是@ParamPath。不然编译的时候会保存。
下面看下前端js的代码:
// 连接websocket
var webSocket;
function initWebsocket(id)
{
webSocket = new WebSocket("ws://localhost:18081/websocket/{"+id+"}");
webSocket.session
webSocket.onmessage = function (event)
{
onWebsockMessage(event.data);
};
}
// 重写websocket 客户端接受函数
function onWebsockMessage(msg)
{
var bean = JSON.parse(msg);
if(bean.type == 1)
{
// 如果是有新的位置消息上传,更新下对应的operation状态
// 根据operationId选择行
var row = $("table.operation-table").DataTable()
.row( function ( idx, data, node ) {
return data.operationId == bean.data.operationId ?
true : false;
} );
row.data().excutetime = $.myTime.UnixToDate(bean.data.excutetime,true);
row.draw();
}
}
/**
* 获取一个5位的随机数
* @returns {string}
*/
function getRnd5()
{
// 生成一個5位随机数并向server发送
var rnd = "";
for(var i=0;i<5;i++)
rnd+=Math.floor(Math.random()*10);
return rnd;
}
var socketId = getRnd5();
initWebsocket(socketId);