websocket介绍
websocket
最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种
websocket 使用
websocket 连接创建分为前端和服务量部分 ,中间通过nginx转发,废话不多说,直接上代码
1、前端部分
// # js websocket 创建
/**
*
* @param url
* @param onmessageFn
* @param onopenFn
*/
createWebsocket: function (url, onmessageFn, onopenFn) {
const self = this
if(!url) {
console.log(`url不能为空`)
return
}
let websocket = null
const getWsUrl = (url) => {
const protocol = document.location.protocol
const domain = document.domain
const ws = protocol === 'https:' ? 'wss' : 'ws'
const wsUrl = `${ws}://${domain}/${url}`
return wsUrl
}
const wsUrl = getWsUrl(url)
// 判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new ReconnectingWebSocket(wsUrl, null, { reconnectInterval: 10 })
} else {
console.log('当前浏览器 Not support websocket')
}
// 连接发生错误的回调方法
websocket.onerror = function () {
console.log(`${wsUrl} 连接发生错误`)
}
// 连接成功建立的回调方法
websocket.onopen = function () {
console.log(`${wsUrl} 连接成功`)
onopenFn && onopenFn()
}
// 接收到消息的回调方法
websocket.onmessage = function (event) {
console.log(`${wsUrl} 接受消息:${event.data}`)
onmessageFn && onmessageFn(event.data)
}
// 连接关闭的回调方法
websocket.onclose = function (e) {
console.log(`${wsUrl} 连接关闭`)
}
// 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
websocket && websocket.close && websocket.close()
}
return websocket
},
// 关闭websocket
closeWebSocket: function (websocket) {
websocket && websocket.close && websocket.close()
}
2、服务部分
package com.kuaisu.xqt.websocket;
import com.alibaba.fastjson.JSON;
import com.github.dapeng.core.SoaException;
import com.google.gson.Gson;
import com.isuwang.soa.im.message.domain.TSendMessageBodyRequest;
import com.isuwang.soa.im.message.domain.TSendMessageRequest;
import com.kuaisu.platform.helper.AttachmentHelper;
import com.kuaisu.platform.oauth.domain.OauthUserDetails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* 个人工作平台企点qq
*/
@ServerEndpoint(value = "/websocket/personalPlatformQqMsg")
public class PersonalPlatformQqMsgWebsocket {
private static final Logger logger = LoggerFactory.getLogger(PersonalPlatformQqMsgWebsocket.class);
private static final String INFO_PLATFORM = "personalPlatformQqMsg";
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static final CopyOnWriteArraySet<PersonalPlatformQqMsgWebsocket> qqMsgWebsocketSet = new CopyOnWriteArraySet<PersonalPlatformQqMsgWebsocket>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private static Executor singleThreadExecutor = Executors.newSingleThreadExecutor();
private byte[] acceptBytes = null;
@Autowired
protected JedisPool jedisPool;
private Gson gson = new Gson();
public void init() throws Exception {
logger.info("init 开始执行");
singleThreadExecutor.execute(() -> {
try (Jedis jedis = jedisPool.getResource()) {
jedis.subscribe(new JedisPubSub() {
@Override
public void onMessage(String channel, String message) {
// TODO 监听推动消息
logger.info(INFO_PLATFORM + "监听到消息推送");
// TUserBehaviorStatisticsDetail ubs = gson.fromJson(message, TUserBehaviorStatisticsDetail.class);
// try {
// UserBehaviorWebsocket.sendMessage(ubs.allocStaffId.get(), message);
// } catch (IOException e) {
// e.printStackTrace();
// }
}
}, "user_behavior_statistics_topic");
}
});
}
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
//获取用户信息
Principal principal = session.getUserPrincipal();
OauthUserDetails oauthUserDetails = (OauthUserDetails) ((OAuth2Authentication) principal).getUserAuthentication().getPrincipal();
com.kuaisu.platform.oauth.domain.User user = oauthUserDetails.user();
this.user = user;
this.session = session;
qqMsgWebsocketSet.add(this); //加入set中
logger.info(INFO_PLATFORM + "有新连接加入!当前在线人数为:" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
qqMsgWebsocketSet.remove(this); //从set中删除
logger.info(INFO_PLATFORM + "有一连接关闭!当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
logger.info(INFO_PLATFORM + "来自客户端的消息:" + message);
// TODO 客服端发送qq消息
TSendMessageRequest sendMessageRequest = parseMsg(message);
toSendMsg2Server(sendMessageRequest);
//
}
@OnMessage
public void onMessageB(byte[] b, boolean last, Session session) {
// process partial data here, which check on last to see if these is more on the way
if (this.acceptBytes == null) {
this.acceptBytes = new byte[0];
}
this.acceptBytes = addBytes(this.acceptBytes, b);
if (last) { // 接收完毕
// TODO 客服端发送qq消息
try {
String msg = new String(this.acceptBytes, "utf-8");
logger.info(INFO_PLATFORM + "来自客户端的blob消息length:" + msg.length());
this.acceptBytes = null;
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
}
/**
* 发生错误时调用
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
qqMsgWebsocketSet.remove(this); //从set中删除
logger.info(INFO_PLATFORM + "连接发生错误:" + error.getMessage(), error);
}
/**
* 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* 给websocket发送信息
* @param msg
* @throws IOException
*/
public void sendMessage(String msg) throws IOException {
this.session.getBasicRemote().sendText(msg);
}
/**
* 给websocket发送信息
* @param object
* @throws IOException
* @throws EncodeException
*/
public void sendMessage(Object object) throws IOException {
String msg = JSON.toJSONString(object);
sendMessage(msg);
}
/**
* @param data1
* @param data2
* @return data1 与 data2拼接的结果
*/
private static byte[] addBytes(byte[] data1, byte[] data2) {
byte[] data3 = new byte[data1.length + data2.length];
System.arraycopy(data1, 0, data3, 0, data1.length);
System.arraycopy(data2, 0, data3, data1.length, data2.length);
return data3;
}
/**
* 统计连接数
*
* @return
*/
public static int getOnlineCount() {
return qqMsgWebsocketSet.size();
}
/**
* 获取当前的websocket集合
*
* @return
*/
public static CopyOnWriteArraySet<PersonalPlatformQqMsgWebsocket> getWebsocketSet() {
return qqMsgWebsocketSet;
}
}
3、nginx中间层
# WebSocket support (nginx 1.4)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
重点:发送大文本
通过websocket 直接发送大文本,比如base64的附件等,直接通过websocket.send
是不行的,经测试服务端会报异常。那么该如何处理呢?好在websocket是支持二进制、Blod大本文的。在发送之前,将内容用Blod
分装一层即可。代码如下:
// websocket 发送大文本
sendMsgByBlob: function (websocket, msg) {
const blob = new Blob([msg], {
type: 'text/plain'
})
websocket.send( blob)
}