Tio - IM
https://www.tiocloud.com/doc/tio/85?pageNumber=1
<dependency>
<groupId>org.t-io</groupId>
<artifactId>tio-core</artifactId>
<version>3.7.0.v20201010-RELEASE</version>
</dependency>
<dependency>
<groupId>org.t-io</groupId>
<artifactId>tio-websocket-server</artifactId>
<version>3.7.0.v20201010-RELEASE</version>
</dependency>
##http 配置
http.port = 80
http.page = classpath:page
http.404 = /showcase/404
http.500 = /showcase/500
# 页面文件缓存时间,开发时设置成0,生产环境可以设置成1小时(3600),10分钟(600)等,单位:秒
http.maxLiveTimeOfStaticRes=0
# file of keystore,如果以classpath:开头,则从classpath中查找,否则从文件路径中查找
# --例1: classpath:config/ssl/keystore.jks
# --例2: /ssl/ca/keystore.jks
#ssl.keystore=classpath:ssl/cert-1538199102261_t-io.org.jks
# file of truststore,如果以classpath:开头,则从classpath中查找,否则从文件路径中查找
# --例1: classpath:config/ssl/keystore.jks
# --例2: /ssl/ca/keystore.jks
#ssl.truststore=classpath:ssl/cert-1538199102261_t-io.org.jks
# password for keystore
#ssl.pwd=08gUMx4x
1、C/S
1.1 Entity
public class Const {
/**
* 服务器地址
*/
public static final String SERVER = "127.0.0.1";
/**HelloPacket
* 监听端口
*/
public static final int PORT = 6789;
/**
* 心跳超时时间
*/
public static final int TIMEOUT = 5000;
/**
* 用于群聊的group id
*/
public static final String GROUP_ID = "showcase-websocket";
}
public class HelloPacket extends Packet {
private static final long serialVersionUID = -172060606924066412L;
/**
* 消息头的长度
*/
public static final int HEADER_LENGHT = 4;
public static final String CHARSET = "utf-8";
private byte[] body;
/**
* @return the body
*/
public byte[] getBody() {
return body;
}
/**
* @param body the body to set
*/
public void setBody(byte[] body) {
this.body = body;
}
}
1.2 Client
public class HelloClientAioHandler implements ClientAioHandler {
private static final HelloPacket heartbeatPacket = new HelloPacket();
/**
* 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
* 总的消息结构:消息头 + 消息体
* 消息头结构: 4个字节,存储消息体的长度
* 消息体结构: 对象的json串的byte[]
*/
@Override
public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
// 收到的数据组不了业务包,则返回null以告诉框架数据不够
if (readableLength < HelloPacket.HEADER_LENGHT) {
return null;
}
// 读取消息体的长度
int bodyLength = buffer.getInt();
// 数据不正确,则抛出AioDecodeException异常
if (bodyLength < 0) {
throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
}
// 计算本次需要的数据长度
int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
// 收到的数据是否足够组包
int isDataEnough = readableLength - neededLength;
// 不够消息体长度(剩下的buffe组不了消息体)
if (isDataEnough < 0) {
return null;
} else {
// 组包成功
HelloPacket imPacket = new HelloPacket();
if (bodyLength > 0) {
byte[] dst = new byte[bodyLength];
buffer.get(dst);
imPacket.setBody(dst);
}
return imPacket;
}
}
/**
* 编码:把业务消息包编码为可以发送的ByteBuffer
* 总的消息结构:消息头 + 消息体
* 消息头结构: 4个字节,存储消息体的长度
* 消息体结构: 对象的json串的byte[]
*/
@Override
public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
HelloPacket helloPacket = (HelloPacket) packet;
byte[] body = helloPacket.getBody();
int bodyLen = 0;
if (body != null) {
bodyLen = body.length;
}
// bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
//创建一个新的bytebuffer
ByteBuffer buffer = ByteBuffer.allocate(allLen);
// 设置字节序
buffer.order(tioConfig.getByteOrder());
// 写入消息头----消息头的内容就是消息体的长度
buffer.putInt(bodyLen);
// 写入消息体
if (body != null) {
buffer.put(body);
}
return buffer;
}
/**
* 处理消息
*/
@Override
public void handler(Packet packet, ChannelContext channelContext) throws Exception {
HelloPacket helloPacket = (HelloPacket) packet;
byte[] body = helloPacket.getBody();
if (body != null) {
String str = new String(body, HelloPacket.CHARSET);
System.out.println("收到消息:" + str);
}
}
/**
* 此方法如果返回null,框架层面则不会发心跳;如果返回非null,框架层面会定时发本方法返回的消息包
*/
@Override
public Packet heartbeatPacket(ChannelContext channelContext) {
return heartbeatPacket;
}
}
public class HelloClientStarter {
/**
* 服务器节点
*/
public static Node serverNode = new Node(Const.SERVER, Const.PORT);
/**
* handler, 包括编码、解码、消息处理
*/
public static ClientAioHandler tioClientHandler = new HelloClientAioHandler();
/**
* 事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
*/
public static ClientAioListener aioListener = null;
/**
* 断链后自动连接的,不想自动连接请设为null
*/
private static ReconnConf reconnConf = new ReconnConf(5000L);
/**
* 一组连接共用的上下文对象
*/
public static ClientTioConfig clientTioConfig = new ClientTioConfig(tioClientHandler, aioListener, reconnConf);
public static TioClient tioClient = null;
public static ClientChannelContext clientChannelContext = null;
/**
* 启动程序入口
*/
public static void main(String[] args) throws Exception {
clientTioConfig.setHeartbeatTimeout(Const.TIMEOUT);
tioClient = new TioClient(clientTioConfig);
clientChannelContext = tioClient.connect(serverNode);
//连上后,发条消息玩玩
send();
}
private static void send() throws Exception {
HelloPacket packet = new HelloPacket();
packet.setBody("hello world".getBytes(HelloPacket.CHARSET));
Tio.send(clientChannelContext, packet);
}
}
1.3 Serve
public class HelloServerAioHandler implements ServerAioHandler {
/**
* 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
* 总的消息结构:消息头 + 消息体
* 消息头结构: 4个字节,存储消息体的长度
* 消息体结构: 对象的json串的byte[]
*/
@Override
public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
//提醒:buffer的开始位置并不一定是0,应用需要从buffer.position()开始读取数据
//收到的数据组不了业务包,则返回null以告诉框架数据不够
if (readableLength < HelloPacket.HEADER_LENGHT) {
return null;
}
//读取消息体的长度
int bodyLength = buffer.getInt();
//数据不正确,则抛出AioDecodeException异常
if (bodyLength < 0) {
throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
}
//计算本次需要的数据长度
int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
//收到的数据是否足够组包
int isDataEnough = readableLength - neededLength;
// 不够消息体长度(剩下的buffe组不了消息体)
if (isDataEnough < 0) {
return null;
} else {
//组包成功
HelloPacket imPacket = new HelloPacket();
if (bodyLength > 0) {
byte[] dst = new byte[bodyLength];
buffer.get(dst);
imPacket.setBody(dst);
}
return imPacket;
}
}
/**
* 编码:把业务消息包编码为可以发送的ByteBuffer
* 总的消息结构:消息头 + 消息体
* 消息头结构: 4个字节,存储消息体的长度
* 消息体结构: 对象的json串的byte[]
*/
@Override
public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
HelloPacket helloPacket = (HelloPacket) packet;
byte[] body = helloPacket.getBody();
int bodyLen = 0;
if (body != null) {
bodyLen = body.length;
}
//bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
//创建一个新的bytebuffer
ByteBuffer buffer = ByteBuffer.allocate(allLen);
//设置字节序
buffer.order(tioConfig.getByteOrder());
//写入消息头----消息头的内容就是消息体的长度
buffer.putInt(bodyLen);
//写入消息体
if (body != null) {
buffer.put(body);
}
return buffer;
}
/**
* 处理消息
*/
@Override
public void handler(Packet packet, ChannelContext channelContext) throws Exception {
HelloPacket helloPacket = (HelloPacket) packet;
byte[] body = helloPacket.getBody();
if (body != null) {
String str = new String(body, HelloPacket.CHARSET);
System.out.println("收到消息:" + str);
HelloPacket resppacket = new HelloPacket();
resppacket.setBody(("收到了你的消息,你的消息是:" + str).getBytes(HelloPacket.CHARSET));
Tio.send(channelContext, resppacket);
}
}
}
public class HelloServerStarter {
/**
* handler, 包括编码、解码、消息处理
*/
public static ServerAioHandler aioHandler = new HelloServerAioHandler();
/**
* 事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
*/
public static ServerAioListener aioListener = null;
/**
* 一组连接共用的上下文对象
*/
public static ServerTioConfig serverTioConfig = new ServerTioConfig("hello-tio-server", aioHandler, aioListener);
/**
* tioServer对象
*/
public static TioServer tioServer = new TioServer(serverTioConfig);
/**
* 有时候需要绑定ip,不需要则null
*/
public static String serverIp = null;
/**
* 监听的端口
*/
public static int serverPort = Const.PORT;
/**
* 启动程序入口
*/
public static void main(String[] args) throws IOException {
serverTioConfig.setHeartbeatTimeout(Const.TIMEOUT);
tioServer.start(serverIp, serverPort);
}
}
2、 B/S
2.1 Serve
public class HttpServerInit {
private static final Logger log = LoggerFactory.getLogger(HttpServerInit.class);
public static HttpConfig httpConfig;
public static HttpRequestHandler requestHandler;
public static HttpServerStarter httpServerStarter;
public static ServerTioConfig serverTioConfig;
public static void init() throws Exception {
//启动端口
int port = P.getInt("http.port");
// html/css/js等的根目录,支持classpath:,也支持绝对路径
String pageRoot = P.get("http.page");
httpConfig = new HttpConfig(port, null, null, null);
httpConfig.setPageRoot(pageRoot);
httpConfig.setMaxLiveTimeOfStaticRes(P.getInt("http.maxLiveTimeOfStaticRes"));
httpConfig.setPage404(P.get("http.404"));
httpConfig.setPage500(P.get("http.500"));
httpConfig.setUseSession(false);
httpConfig.setCheckHost(false);
//第二个参数也可以是数组
requestHandler = new DefaultHttpRequestHandler(httpConfig, ShowcaseWebsocketStarter.class);
httpServerStarter = new HttpServerStarter(httpConfig, requestHandler);
serverTioConfig = httpServerStarter.getServerTioConfig();
httpServerStarter.start(); //启动http服务器
String protocol = SslUtils.isSsl(serverTioConfig) ? "https" : "http";
if (log.isInfoEnabled()) {
log.info("\r\nTio Http Server启动完毕:\r\n访问地址:{}://127.0.0.1:{}", protocol, port);
} else {
System.out.println("\r\nTio Http Server启动完毕:,\r\n访问地址:" + protocol + "://127.0.0.1:" + port);
}
}
}
public abstract class ShowcaseServerConfig {
/**
* 协议名字(可以随便取,主要用于开发人员辨识)
*/
public static final String PROTOCOL_NAME = "showcase";
public static final String CHARSET = "utf-8";
/**
* 监听的ip
*/
public static final String SERVER_IP = null;//null表示监听所有,并不指定ip
/**
* 监听端口
*/
public static final int SERVER_PORT = 9326;
/**
* 心跳超时时间,单位:毫秒
*/
public static final int HEARTBEAT_TIMEOUT = 1000 * 60;
/**
* ip数据监控统计,时间段
* @author tanyaowu
*
*/
public interface IpStatDuration {
Long DURATION_1 = Time.MINUTE_1 * 5;
Long[] IPSTAT_DURATIONS = new Long[] { DURATION_1 };
}
}
public class ShowcaseWsMsgHandler implements IWsMsgHandler {
private static Logger log = LoggerFactory.getLogger(ShowcaseWsMsgHandler.class);
public static final ShowcaseWsMsgHandler me = new ShowcaseWsMsgHandler();
private ShowcaseWsMsgHandler() {}
/**
* 握手时走这个方法,业务可以在这里获取cookie,request参数等
*/
@Override
public HttpResponse handshake(HttpRequest request, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
String clientip = request.getClientIp();
String myname = request.getParam("name");
Tio.bindUser(channelContext, myname);
log.info("收到来自{}的ws握手包\r\n{}", clientip, request.toString());
return httpResponse;
}
/**
* @param httpRequest
* @param httpResponse
* @param channelContext
* @throws Exception
* @author tanyaowu
*/
@Override
public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
//绑定到群组,后面会有群发
Tio.bindGroup(channelContext, Const.GROUP_ID);
int count = Tio.getAll(channelContext.tioConfig).getObj().size();
String msg = "{name:'admin',message:'" + channelContext.userid + " 进来了,共【" + count + "】人在线" + "'}";
//用tio-websocket,服务器发送到客户端的Packet都是WsResponse
WsResponse wsResponse = WsResponse.fromText(msg, ShowcaseServerConfig.CHARSET);
//群发
Tio.sendToGroup(channelContext.tioConfig, Const.GROUP_ID, wsResponse);
}
/**
* 字节消息(binaryType = arraybuffer)过来后会走这个方法
*/
@Override
public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
return null;
}
/**
* 当客户端发close flag时,会走这个方法
*/
@Override
public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
Tio.remove(channelContext, "receive close flag");
return null;
}
/**
* 字符消息(binaryType = blob)过来后会走这个方法
*/
@Override
public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) throws Exception {
WsSessionContext wsSessionContext = (WsSessionContext) channelContext.get();
// 获取 websocket 握手包
HttpRequest httpRequest = wsSessionContext.getHandshakeRequest();
if (log.isDebugEnabled()) {
log.debug("握手包:{}", httpRequest);
}
if (Objects.equals("心跳内容", text)) {
return null;
}
//channelContext.getToken()
//String msg = channelContext.getClientNode().toString() + " 说:" + text;
String msg = "{name:'" + channelContext.userid + "',message:'" + text + "'}";
//用tio-websocket,服务器发送到客户端的Packet都是WsResponse
WsResponse wsResponse = WsResponse.fromText(msg, ShowcaseServerConfig.CHARSET);
//群发
Tio.sendToGroup(channelContext.tioConfig, Const.GROUP_ID, wsResponse);
//返回值是要发送给客户端的内容,一般都是返回null
return null;
}
}
public class ShowcaseWebsocketStarter {
private WsServerStarter wsServerStarter;
private ServerTioConfig serverTioConfig;
/**
*
* @author tanyaowu
*/
public ShowcaseWebsocketStarter(int port, ShowcaseWsMsgHandler wsMsgHandler) throws Exception {
wsServerStarter = new WsServerStarter(port, wsMsgHandler);
serverTioConfig = wsServerStarter.getServerTioConfig();
serverTioConfig.setName(ShowcaseServerConfig.PROTOCOL_NAME);
//设置ip统计时间段
serverTioConfig.ipStats.addDurations(ShowcaseServerConfig.IpStatDuration.IPSTAT_DURATIONS);
//设置心跳超时时间
serverTioConfig.setHeartbeatTimeout(ShowcaseServerConfig.HEARTBEAT_TIMEOUT);
// if (P.getInt("ws.use.ssl", 1) == 1) {
// // 通过wss来访问,要有SSL证书(证书必须和域名相匹配,否则可能访问不了ssl)
//// String keyStoreFile = "classpath:config/ssl/keystore.jks";
//// String trustStoreFile = "classpath:config/ssl/keystore.jks";
//// String keyStorePwd = "214323428310224";
//
// String keyStoreFile = P.get("ssl.keystore", null);
// String trustStoreFile = P.get("ssl.truststore", null);
// String keyStorePwd = P.get("ssl.pwd", null);
// serverTioConfig.useSsl(keyStoreFile, trustStoreFile, keyStorePwd);
// }
}
/**
* @author tanyaowu
* @throws IOException
*/
public static void start() throws Exception {
ShowcaseWebsocketStarter appStarter = new ShowcaseWebsocketStarter(ShowcaseServerConfig.SERVER_PORT, ShowcaseWsMsgHandler.me);
appStarter.wsServerStarter.start();
}
public static void main(String[] args) throws Exception {
//启动http server,这个步骤不是必须的,但是为了用页面演示websocket,所以先启动http
P.use("app.properties");
HttpServerInit.init();
//启动websocket server
start();
}
}
2.2 Browser
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>tio-ws演示</title>
</head>
<body>
<div id="app">
<iframe src="./im.html" style="width:500px; height:850px;"></iframe>
<iframe src="./im.html" style="width:500px; height:850px;"></iframe>
<iframe src="./im.html" style="width:500px; height:850px;"></iframe>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>IM Now!</title>
<link rel="stylesheet" type="text/css" href="css/im.css" />
</head>
<body>
<div id="container">
<div class="header">
<span style="float: left;">微信聊天界面</span>
<span id="mtime" style="float: right;"></span>
</div>
<ul class="content"></ul>
<div class="footer">
<div id="user_face_icon">
<img src="image/t2.jpg" alt="">
</div>
<input id="messageText" type="text" placeholder="说点什么吧..." >
<span id="btn" onclick="sendMsg()">发送</span>
</div>
</div>
<script src="js/tiows.js"></script>
<script src="js/name.js"></script>
<script src="js/imHandler.js"></script>
<script src="js/im.js"></script>
</body>
</html>
*{margin:0;padding:0;list-style:none;}#container{width:450px;height:780px;background:#eee;margin:5pxauto0;position:relative;box-shadow:20px20px55px#777;}.header{background:#000;height:40px;color:#fff;line-height:34px;font-size:20px;padding:010px;}.footer{width:430px;height:50px;background:#666;position:absolute;bottom:0;padding:10px;}.footerinput{width:275px;height:45px;outline:none;font-size:20px;text-indent:10px;position:absolute;border-radius:6px;right:80px;}.footerspan{display:inline-block;width:62px;height:48px;background:#ccc;font-weight:900;line-height:45px;cursor:pointer;text-align:center;position:absolute;right:10px;border-radius:6px;}.footerspan:hover{color:#fff;background:#999;}#user_face_icon{display:inline-block;background:red;width:60px;height:60px;border-radius:30px;position:absolute;bottom:6px;left:14px;cursor:pointer;overflow:hidden;}img{width:60px;height:60px;}.content{font-size:20px;width:435px;height:662px;overflow:auto;padding:5px;}.contentli{margin-top:10px;padding-left:10px;width:412px;display:block;clear:both;overflow:hidden;}.contentliimg{float:left;}.contentlispan{background:#7cfc00;padding:10px;border-radius:10px;float:left;margin:6px10px010px;max-width:310px;border:1pxsolid#ccc;box-shadow:003px#ccc;}.contentliimg.imgleft{float:left;}.contentliimg.imgright{float:right;}.contentlispan.nickleft{float:left;background:#eee;padding:0px;border-radius:0px;margin:6px10px010px;max-width:310px;border:0pxsolid#ccc;box-shadow:000px#ccc;width:90%;text-align:left;font-size:16px;color:#aaa;}.contentlispan.nickright{float:right;background:#eee;padding:0px;border-radius:0px;margin:6px10px010px;max-width:310px;border:0pxsolid#ccc;box-shadow:000px#ccc;width:90%;text-align:right;font-size:16px;color:#aaa;}.contentlidiv.sysinfo{font-size:16px;border-radius:5px;background:#ddd;color:#fff;text-align:center;width:auto;}.contentlispan.spanleft{float:left;background:#fff;}.contentlispan.spanright{float:right;background:#7cfc00;}
const ws_protocol = 'ws'; // ws 或 wss
const ip = '127.0.0.1'
const port = 9326
const heartbeatTimeout = 5000; // 心跳超时时间,单位:毫秒
const reconnInterval = 1000; // 重连间隔时间,单位:毫秒
const binaryType = 'blob'; // 'blob' or 'arraybuffer';//arraybuffer是字节
const handler = new IMHandler()
let mtime = "";
let myname = "";
let tiows;
function initWs () {
myname=getRandomName();
const queryString = 'name=' + myname;
const param = "";
tiows = new tio.ws(ws_protocol, ip, port, queryString, param, handler, heartbeatTimeout, reconnInterval, binaryType)
tiows.connect()
}
//回车事件绑定
document.onkeydown = function(e){
const ev = document.all ? window.event : e;
if(ev.keyCode===13) {
sendMsg();
}
}
function showTime()
{
const myDate = new Date();
let m = myDate.getMinutes();
if (m<10) m="0"+m;
let s = myDate.getSeconds();
if (s<10) s="0"+s;
mtime=myDate.getHours()+":"+m+":"+s;
document.getElementById("mtime").innerText=mtime;
}
function sendMsg () {
const msg = document.getElementById('messageText');
tiows.send(msg.value);
msg.value="";
}
function clearMsg () {
document.getElementById('contentId').innerHTML = ''
}
initWs ()
setInterval(showTime,1000);
const IMHandler = function () {
this.onopen = function (event, ws) {
// ws.send('hello 连上了')
//document.getElementById('contentId').innerHTML += 'hello 连上了<br>';
}
/**
* 收到服务器发来的消息
* @param {*} event
* @param {*} ws
*/
this.onmessage = function (event, ws) {
const data = event.data;
const msgBody = eval('(' + data + ')');
let ico, imgcls, spancls;
const content = document.getElementsByTagName('ul')[0];
let nickcls;
if (myname === msgBody.name) {
// 头像
ico = "image/t2.jpg";
imgcls = "imgright";
spancls = "spanright";
nickcls = "nickright";
} else {
// 头像
ico = "image/t1.jpg";
imgcls = "imgleft";
spancls = "spanleft";
nickcls = "nickleft"
}
if (msgBody.name === "admin") {
content.innerHTML += '<li><div class="sysinfo">' + msgBody.message + '</div></li>';
} else {
content.innerHTML += '<li><img src="' + ico + '" class="' + imgcls + '"><span class="' + nickcls + '">' + msgBody.name + '</span><span class="' + spancls + '">' + msgBody.message + '</span></li>';
}
console.log("-----------")
content.scrollTop = content.scrollHeight;
}
this.onclose = function (e, ws) {
// error(e, ws)
}
this.onerror = function (e, ws) {
// error(e, ws)
}
/**
* 发送心跳,框架会自动定时调用该方法,请在该方法中发送心跳
* @param {*} ws
*/
this.ping = function (ws) {
// log("发心跳了")
ws.send('心跳内容')
}
};
function getRandomName(){
const firstNames = ["赵", "钱", "孙", "李"];
const secondNames = ["三", "四", "五", "六"];
const firstLength = firstNames.length;
const secondLength = secondNames.length;
const i = parseInt(Math.random() * firstLength);
const j = parseInt(Math.random() * secondLength);
return firstNames[i] + secondNames[j];
}
if (typeof(tio) == "undefined") {
tio = {};
}
tio.ws = {};
/**
* @param {*} ws_protocol wss or ws
* @param {*} ip
* @param {*} port
* @param {*} paramStr 加在ws url后面的请求参数,形如:name=张三&id=12
* @param {*} param 作为tio.ws对象的参数,由业务自己使用,框架不使用
* @param {*} handler
* @param {*} heartbeatTimeout 心跳时间 单位:毫秒
* @param {*} reconnInterval 重连间隔时间 单位:毫秒
* @param {*} binaryType 'blob' or 'arraybuffer';//arraybuffer是字节
*/
tio.ws = function (ws_protocol, ip, port, paramStr, param, handler, heartbeatTimeout, reconnInterval, binaryType) {
this.url = ws_protocol + '://' + ip + ':' + port
this.binaryType = binaryType || 'arraybuffer'
if (paramStr) {
this.url += '?' + paramStr
this.reconnUrl = this.url + "&"
} else {
this.reconnUrl = this.url + "?"
}
this.reconnUrl += "tiows_reconnect=true";
this.handler = handler
this.heartbeatTimeout = heartbeatTimeout
this.reconnInterval = reconnInterval
this.lastInteractionTime = function () {
if (arguments.length === 1) {
this.lastInteractionTimeValue = arguments[0]
}
return this.lastInteractionTimeValue
}
this.heartbeatSendInterval = heartbeatTimeout / 2
this.connect = function (isReconnect) {
let _url = this.url;
if (isReconnect) {
_url = this.reconnUrl;
}
const ws = new WebSocket(_url);
this.ws = ws
ws.binaryType = this.binaryType; // 'arraybuffer'; // 'blob' or 'arraybuffer';//arraybuffer是字节
const self = this;
ws.onopen = function (event) {
self.handler.onopen.call(self.handler, event, ws)
self.lastInteractionTime(new Date().getTime())
self.pingIntervalId = setInterval(function () {
self.ping(self)
}, self.heartbeatSendInterval)
}
ws.onmessage = function (event) {
self.handler.onmessage.call(self.handler, event, ws)
self.lastInteractionTime(new Date().getTime())
}
ws.onclose = function (event) {
clearInterval(self.pingIntervalId) // clear send heartbeat task
try {
self.handler.onclose.call(self.handler, event, ws)
} catch (error) {}
self.reconn(event)
}
ws.onerror = function (event) {
self.handler.onerror.call(self.handler, event, ws)
}
return ws
}
this.reconn = function (event) {
const self = this;
setTimeout(function () {
self.ws = self.connect(true)
}, self.reconnInterval)
}
this.ping = function () {
const iv = new Date().getTime() - this.lastInteractionTime(); // 已经多久没发消息了
// 单位:秒
if ((this.heartbeatSendInterval + iv) >= this.heartbeatTimeout) {
this.handler.ping(this.ws)
}
};
this.send = function(data){
this.ws.send(data);
};
}