WebSocket通信
引言:
WebSocket主要用过后端向前端访问,这样避免了一些项目上执行时间比较久的,不必前端一直请求数据,定时刷新什么的。
比如说,需要实现一个等待时间比较久的功能,提交某类单子,或者充值支付等,不方边使用异步操作,需要实时返回消息给用户,可以选用,也可以用百分比的形式返回后端执行的情况。
一、WebSocket 简介
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
二、WebSocket客户端
websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间双向的通信。
在websocket中有两个方法: websocket允许通过JavaScript建立与远程服务器的连接,从而实现客户端与服务器间双向的通信。
在websocket中有两个方法:
- send() 向远程服务器发送数据
- close() 关闭该websocket链接
websocket同时还定义了几个监听函数
- onopen 当网络连接建立时触发该事件
- onerror 当网络发生错误时触发该事件
- onclose 当websocket被关闭时触发该事件
- onmessage 当websocket接收到服务器发来的消息的时触发的事件,也是通信中最重要的一个监听事件。
三、代码实战
请求与接收消息的页面
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
var url='ws://'+window.location.host+'/websocket/${cid}';
debugger;
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8083/checkcentersys/websocket/20");
socket = new WebSocket(url);
//打开事件
socket.onopen = function() {
console.log("Socket 已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function() {
console.log("Socket已关闭");
};
//发生了错误事件
socket.onerror = function() {
alert("Socket发生了错误");
//此时可以尝试刷新页面
}
}
请求页面与推送消息
[http://127.0.0.1:18080/checkcenter/socket/2 第一次请求
http://127.0.0.1:18080/checkcenter/socket/push/2?message=%E4%B8%80%E5%A4%A9](http://127.0.0.1:18080/checkcenter/socket/push/2?message=一天)
对2窗口发送消息
//页面请求
@GetMapping("/socket/{cid}")
public ModelAndView socket(@PathVariable String cid) {
ModelAndView mav=new ModelAndView("/socket");
mav.addObject("cid", cid);
return mav;
}
/**
* 发送消息 推送数据接口
* @param cid
* @param message
* @return
*/
@ResponseBody
@RequestMapping("/socket/push/{cid}")
public String pushToWeb(@PathVariable String cid,String message) {
try {
WebSocketServer.sendInfo(message,cid);
} catch (IOException e) {
e.printStackTrace();
return "error" + cid+"#"+e.getMessage();
}
return "success" + cid;
}
相关socket服务方法
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @description: WebSocketServer消息服务
* @author: Shenshuaihu
* @version: 1.0
* @data: 2019-03-18 17:02
*/
@ServerEndpoint("/websocket/{sid}")
@Component
@Slf4j
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//接收sid
private String sid="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("sid") String sid) {
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:"+sid+",当前在线人数为" + getOnlineCount());
this.sid=sid;
try {
sendMessage("连接成功");
} catch (IOException e) {
log.error("websocket IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*
**/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 群发自定义消息
* */
public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
log.info("推送消息到窗口"+sid+",推送内容:"+message);
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(sid == null) {
item.sendMessage(message);
} else if(item.sid.equals(sid)){
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
以下是我在一个项目上应用到的,是调用一个用时比较久的API,把实时消息返回给用户。
function toText(id) {
$("#message").attr("disabled", true);
$.ajax({
// url : BaseUrl+'/to/text',
url : '/to/text',
type : "POST",
data : {"id" : id ,"sid" : sid } ,
success : function(data) {
if(data.success) {
layer.close(messageWin);
$('#message').addClass("hide"); //取消继续隐藏
// 刷新数据
$(".layui-laypage-btn")[0].click();
} else {
layer.close(messageWin);
//eg1
layer.confirm(data.data, {icon: 3, title:'失败提示'}, function(index){
layer.close(index);
});
$('#message').addClass("hide"); //取消继续隐藏
}
$("#message").attr("disabled", false);
},
error : function() {
}
}); // ajax结束
}
function f1() {
$('#message').removeClass("hide");
messageWin = layer.open({
type : 1,
shade : 0.4,
title : '正在执行',
shadeClose: true, //点击遮罩关闭层
area : [ '300px', '250px' ], //显示空间
content : $('#message'), //捕获的元素
cancel : function(index) {
layer.close(messageWin);
$('#message').addClass("hide"); //取消继续隐藏
}
});
// 改变状态
}
var socket;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
var url='ws://'+window.location.host+'/websocket/' + sid;
console.log("您的浏览器支持WebSocket");
socket = new WebSocket(url);
//打开事件
socket.onopen = function() {
console.log("Socket 已打开");
socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
sendMessage(msg.data);
};
//关闭事件
socket.onclose = function() {
console.log("Socket已关闭");
};
//发生了错误事件
socket.onerror = function() {
alert("Socket发生了错误");
}
}
function sendMessage(message) {
$('#socket').html('转换中,请等待,请勿关闭窗口!!! ' + '\n' + message);
if (message === "保存文件") {
console.log("messageWin 已关闭");
}
}
需要注意的是后端发送消息时要对于sid,免得推送错了。本章部分内容是整合过的文章,如有改善请告知。
网友评论