在我们的日出业务中,会遇到有实时刷新数据,实时监控系统某种状态或信息等情况。
通常我们会想到通过ajax定时发起请求、轮询等方式实现这种功能,通过这几种方式都是由客户端发起的,时间间隔设置的很短,数据能实时刷新,但会占用系统的资源,如果请求基数很大,可能会导致系统内存溢出、资源耗尽等情况发生。如果我们将时间间隔设置的比较长,那么我们的实时性需求也就无法实现。
在H5中,WebSockt成为了我们的一直选择方案,webSocket现在已被很多浏览器所支持,所以使用场景的兼容性也有了一定的保障
- webSockt可以实现客户端、服务端的双向对话,webSockt是建立在tcp协议只上的,数据格式比较轻量,性能开销小,通信高效。可以发送文本,也可以发送二进制数据。没有同源限制,客户端可以与任意服务器通信。
1.服务端实
WebSocket 控制器类
在springmvc中,我们可以通过@ServerEndPoint注解,指定socket的路径,配置、编码方式。webSocket接口的定义,我们可以类比为Controller的api的定义,通过ip:port/path去访问。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.server.standard.SpringConfigurator;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
/**
* webSocket 控制器
*
* @author nickBi
* create on 2018/7/4.
*/
@ServerEndpoint(value = "/websocket", configurator = SpringConfigurator.class, encoders = NBWebSocketEncoder.class)
public class NBWebSocket {
//日志记录
private Logger logger = LoggerFactory.getLogger(TPPWebSocket.class);
//记录每个用户下多个终端的连接
private static Set<TPPWebSocket> userSocket = new HashSet<>();
//需要session来对用户发送数据, 获取连接特征userId
private Session session;
/**
* @param @param session websocket连接的session属性
* @param @throws IOException
* @Title: onOpen
* @Description: websocekt连接建立时的操作
*/
@OnOpen
public void onOpen(Session session) throws IOException {
this.session = session;
userSocket.add(this);
logger.debug("webSocket open");
}
/**
* @Title: onClose
* @Description: 连接关闭的操作
*/
@OnClose
public void onClose() {
userSocket.remove(this);
logger.debug("webSocket closed!");
}
/**
* @param @param message 收到的消息
* @param @param session 该连接的session属性
* @Title: onMessage
* @Description: 收到消息后的操作
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.debug("收到消息" + message);
if (session == null) logger.debug("session null");
}
/**
* @param @param session 该连接的session
* @param @param error 发生的错误
* @Title: onError
* @Description: 连接发生错误时候的操作
*/
@OnError
public void onError(Session session, Throwable error) {
logger.debug("连接发送错误");
error.printStackTrace();
}
/**
* @param @param message 发送的消息
* @param @return 发送成功返回true,反则返回false
* @Title: sendMessage
* @Description: 发送消息给用户下的所有终端
*/
public Boolean sendMessage(String message) {
userSocket.stream().forEach(ws -> {
try {
ws.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
public Boolean sendMessage(Object message) {
userSocket.stream().forEach(ws -> {
try {
ws.session.getBasicRemote().sendObject(message);
} catch (IOException e) {
e.printStackTrace();
} catch (EncodeException e) {
e.printStackTrace();
}
});
return true;
}
}
NBWebSocket的自定义编码器
如需传递Object类型,则需要定义自己的message编码器,如果传递的是string信息则无需编写,此处我们使用的jackson,将对象转化为jsonString再传递到前端(非必须
)。
import com.jeeplus.common.json.JsonUtils;
import com.lingrit.tpp.core.result.JsonResult;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
/**
* webSocket 编码器
*
* @author nickBi
* create on 2018/7/4.
*/
public class NBWebSocketEncoder implements Encoder.Text<JsonResult> {
@Override
public String encode(JsonResult jsonResult) {
return JsonUtils.writeValueAsString(jsonResult);
}
@Override
public void init(EndpointConfig endpointConfig) {
}
@Override
public void destroy() {
}
}
WebSocketService
在此,我们可以发送消息到前端
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* @author nickBi
* create on 2018/7/4.
*/
@Service
public class NBWebSocketService {
private Logger logger = LoggerFactory.getLogger(NBWebSocketService.class);
//声明websocket连接类
private NBWebSocket nbWebSocket = new NBWebSocket();
/**
* @param @param message 消息
* @param @return 发送成功返回true,否则返回false
* @Title: sendToAllTerminal
* @Description: 调用websocket类给用户下的所有终端发送消息
*/
public Boolean sendToAllTerminal(String message) {
logger.debug(message);
if (nbWebSocket.sendMessage(message)) {
return true;
} else {
return false;
}
}
public Boolean sendToAllTerminal(Object message) {
logger.debug(message.toString());
if (nbWebSocket.sendMessage(message)) {
return true;
} else {
return false;
}
}
}
JsonUtils工具类
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* json工具类 使用jackson
*
*/
public abstract class JsonUtils {
private static Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class);
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
}
/**
* 解析JSON对象
*/
public static <T> T parseJsonObject(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, clazz);
} catch (Exception e) {
LOGGER.error("JSON对象解析失败", e);
throw new RuntimeException("JSON对象解析失败");
}
}
/**
* 解析JSON集合
*/
public static <T> List<T> parseJsonList(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructParametricType(ArrayList.class, clazz));
} catch (Exception e) {
LOGGER.error("JSON集合解析失败", e);
throw new RuntimeException("JSON集合解析失败");
}
}
/**
* 解析JSON集合,class是集合类型
* @param <T>
* @param json
* @param clazz
* @return
*/
public static <T> List<T> parseCollectionJsonList(String json, Class<T> clazz){
try {
return objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz));
} catch (Exception e) {
LOGGER.error("JSON集合解析失败", e);
throw new RuntimeException("JSON集合解析失败");
}
}
/**
* 转化为json字符串
*/
public static String writeValueAsString(Object object){
try{
return objectMapper.writeValueAsString(object);
}catch (Exception e){
LOGGER.error("转换为JSON字符串失败", e);
throw new RuntimeException("JSON集合解析失败");
}
}
public static void main(String[] args){
int[] integerJson = new int[] {4, 5, 1, 2,8, 9, 10, 10, 1, 2, 3, 4, 5, 6 , 9, 11, 10, 11, 9, 10, 1, 2, 10};
List<Map<String, Integer>> list = new ArrayList<Map<String, Integer>>();
for(int i=0; i<integerJson.length; i++){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("nums", i);
map.put("vl", integerJson[i]);
list.add(map);
}
System.out.println(writeValueAsString(list));
}
JsonResult
import com.alibaba.fastjson.JSON;
/**
* 统一API响应结果封装
*/
public class JsonResult {
private int code;
private String message;
private String debugMessage;
private Object data;
private boolean success;
public JsonResult setCode(int resultCode) {
this.code =resultCode;
return this;
}
public int getCode() {
return code;
}
public JsonResult setCode(int code) {
this.code = code;
return this;
}
public String getMessage() {
return message;
}
public JsonResult setMessage(String message) {
this.message = message;
return this;
}
public Object getData() {
return data;
}
public JsonResult setData(Object data) {
this.data = data;
return this;
}
public String getDebugMessage() {
return debugMessage;
}
public JsonResult setDebugMessage(String debugMessage) {
this.debugMessage = debugMessage;
return this;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
public void setSuccess(boolean success) {
this.success = success;
}
}
客户端实现
客户端以网页端为例说明,socket接口地址为前面我们定义的路径,并用WS最为WebSocket的标志:ws://localhost:8080/websocket
<head lang="en">
<meta charset="UTF-8">
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<title>webSocket-用户66</title>
<script type="text/javascript">
$(function () {
var websocket;
if ('WebSocket' in window) {
console.log("此浏览器支持websocket");
websocket = new WebSocket("ws://localhost:8080/websocket");
} else if ('MozWebSocket' in window) {
alert("此浏览器只支持MozWebSocket");
} else {
alert("此浏览器只支持SockJS");
}
websocket.onopen = function (evnt) {
$("#tou").html("链接服务器成功!")
};
websocket.onmessage = function (evnt) {
console.log(evnt);
$("#msg").html($("#msg").html() + "<br/>" + evnt.data + "batchNo:" + evnt.data[0].batchNo);
};
websocket.onerror = function (evnt) {
};
websocket.onclose = function (evnt) {
$("#tou").html("与服务器断开了链接!")
}
$('#send').bind('click', function () {
send();
});
function send() {
if (websocket != null) {
var message = document.getElementById('message').value;
websocket.send(message);
} else {
alert('未与服务器链接.');
}
}
});
</script>
</head>
<body>
<div class="page-header" id="tou">
webSocket测试
</div>
<div class="well" id="msg"></div>
<div class="col-lg">
<div class="input-group">
<input type="text" class="form-control" placeholder="发送信息..." id="message">
<span class="input-group-btn">
<button class="btn btn-default" type="button" id="send">发送</button>
</span>
</div>
</div>
</body>
</html>
网友评论