Tio - IM

https://www.tiocloud.com/doc/tio/85?pageNumber=1

  • pom
  1. <dependency>
  2. <groupId>org.t-io</groupId>
  3. <artifactId>tio-core</artifactId>
  4. <version>3.7.0.v20201010-RELEASE</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.t-io</groupId>
  8. <artifactId>tio-websocket-server</artifactId>
  9. <version>3.7.0.v20201010-RELEASE</version>
  10. </dependency>
  • app.properties
  1. ##http 配置
  2. http.port = 80
  3. http.page = classpath:page
  4. http.404 = /showcase/404
  5. http.500 = /showcase/500
  6. # 页面文件缓存时间,开发时设置成0,生产环境可以设置成1小时(3600),10分钟(600)等,单位:秒
  7. http.maxLiveTimeOfStaticRes=0
  8. # file of keystore,如果以classpath:开头,则从classpath中查找,否则从文件路径中查找
  9. # --例1: classpath:config/ssl/keystore.jks
  10. # --例2: /ssl/ca/keystore.jks
  11. #ssl.keystore=classpath:ssl/cert-1538199102261_t-io.org.jks
  12. # file of truststore,如果以classpath:开头,则从classpath中查找,否则从文件路径中查找
  13. # --例1: classpath:config/ssl/keystore.jks
  14. # --例2: /ssl/ca/keystore.jks
  15. #ssl.truststore=classpath:ssl/cert-1538199102261_t-io.org.jks
  16. # password for keystore
  17. #ssl.pwd=08gUMx4x

1、C/S

1.1 Entity

  • Const
  1. public class Const {
  2. /**
  3. * 服务器地址
  4. */
  5. public static final String SERVER = "127.0.0.1";
  6. /**HelloPacket
  7. * 监听端口
  8. */
  9. public static final int PORT = 6789;
  10. /**
  11. * 心跳超时时间
  12. */
  13. public static final int TIMEOUT = 5000;
  14. /**
  15. * 用于群聊的group id
  16. */
  17. public static final String GROUP_ID = "showcase-websocket";
  18. }
  • HelloPacket
  1. public class HelloPacket extends Packet {
  2. private static final long serialVersionUID = -172060606924066412L;
  3. /**
  4. * 消息头的长度
  5. */
  6. public static final int HEADER_LENGHT = 4;
  7. public static final String CHARSET = "utf-8";
  8. private byte[] body;
  9. /**
  10. * @return the body
  11. */
  12. public byte[] getBody() {
  13. return body;
  14. }
  15. /**
  16. * @param body the body to set
  17. */
  18. public void setBody(byte[] body) {
  19. this.body = body;
  20. }
  21. }

1.2 Client

  • HelloClientAioHandler
  1. public class HelloClientAioHandler implements ClientAioHandler {
  2. private static final HelloPacket heartbeatPacket = new HelloPacket();
  3. /**
  4. * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
  5. * 总的消息结构:消息头 + 消息体
  6. * 消息头结构: 4个字节,存储消息体的长度
  7. * 消息体结构: 对象的json串的byte[]
  8. */
  9. @Override
  10. public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
  11. // 收到的数据组不了业务包,则返回null以告诉框架数据不够
  12. if (readableLength < HelloPacket.HEADER_LENGHT) {
  13. return null;
  14. }
  15. // 读取消息体的长度
  16. int bodyLength = buffer.getInt();
  17. // 数据不正确,则抛出AioDecodeException异常
  18. if (bodyLength < 0) {
  19. throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
  20. }
  21. // 计算本次需要的数据长度
  22. int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
  23. // 收到的数据是否足够组包
  24. int isDataEnough = readableLength - neededLength;
  25. // 不够消息体长度(剩下的buffe组不了消息体)
  26. if (isDataEnough < 0) {
  27. return null;
  28. } else {
  29. // 组包成功
  30. HelloPacket imPacket = new HelloPacket();
  31. if (bodyLength > 0) {
  32. byte[] dst = new byte[bodyLength];
  33. buffer.get(dst);
  34. imPacket.setBody(dst);
  35. }
  36. return imPacket;
  37. }
  38. }
  39. /**
  40. * 编码:把业务消息包编码为可以发送的ByteBuffer
  41. * 总的消息结构:消息头 + 消息体
  42. * 消息头结构: 4个字节,存储消息体的长度
  43. * 消息体结构: 对象的json串的byte[]
  44. */
  45. @Override
  46. public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
  47. HelloPacket helloPacket = (HelloPacket) packet;
  48. byte[] body = helloPacket.getBody();
  49. int bodyLen = 0;
  50. if (body != null) {
  51. bodyLen = body.length;
  52. }
  53. // bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
  54. int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
  55. //创建一个新的bytebuffer
  56. ByteBuffer buffer = ByteBuffer.allocate(allLen);
  57. // 设置字节序
  58. buffer.order(tioConfig.getByteOrder());
  59. // 写入消息头----消息头的内容就是消息体的长度
  60. buffer.putInt(bodyLen);
  61. // 写入消息体
  62. if (body != null) {
  63. buffer.put(body);
  64. }
  65. return buffer;
  66. }
  67. /**
  68. * 处理消息
  69. */
  70. @Override
  71. public void handler(Packet packet, ChannelContext channelContext) throws Exception {
  72. HelloPacket helloPacket = (HelloPacket) packet;
  73. byte[] body = helloPacket.getBody();
  74. if (body != null) {
  75. String str = new String(body, HelloPacket.CHARSET);
  76. System.out.println("收到消息:" + str);
  77. }
  78. }
  79. /**
  80. * 此方法如果返回null,框架层面则不会发心跳;如果返回非null,框架层面会定时发本方法返回的消息包
  81. */
  82. @Override
  83. public Packet heartbeatPacket(ChannelContext channelContext) {
  84. return heartbeatPacket;
  85. }
  86. }
  • HelloClientStarter
  1. public class HelloClientStarter {
  2. /**
  3. * 服务器节点
  4. */
  5. public static Node serverNode = new Node(Const.SERVER, Const.PORT);
  6. /**
  7. * handler, 包括编码、解码、消息处理
  8. */
  9. public static ClientAioHandler tioClientHandler = new HelloClientAioHandler();
  10. /**
  11. * 事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
  12. */
  13. public static ClientAioListener aioListener = null;
  14. /**
  15. * 断链后自动连接的,不想自动连接请设为null
  16. */
  17. private static ReconnConf reconnConf = new ReconnConf(5000L);
  18. /**
  19. * 一组连接共用的上下文对象
  20. */
  21. public static ClientTioConfig clientTioConfig = new ClientTioConfig(tioClientHandler, aioListener, reconnConf);
  22. public static TioClient tioClient = null;
  23. public static ClientChannelContext clientChannelContext = null;
  24. /**
  25. * 启动程序入口
  26. */
  27. public static void main(String[] args) throws Exception {
  28. clientTioConfig.setHeartbeatTimeout(Const.TIMEOUT);
  29. tioClient = new TioClient(clientTioConfig);
  30. clientChannelContext = tioClient.connect(serverNode);
  31. //连上后,发条消息玩玩
  32. send();
  33. }
  34. private static void send() throws Exception {
  35. HelloPacket packet = new HelloPacket();
  36. packet.setBody("hello world".getBytes(HelloPacket.CHARSET));
  37. Tio.send(clientChannelContext, packet);
  38. }
  39. }

1.3 Serve

  • HelloServerAioHandler
  1. public class HelloServerAioHandler implements ServerAioHandler {
  2. /**
  3. * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
  4. * 总的消息结构:消息头 + 消息体
  5. * 消息头结构: 4个字节,存储消息体的长度
  6. * 消息体结构: 对象的json串的byte[]
  7. */
  8. @Override
  9. public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
  10. //提醒:buffer的开始位置并不一定是0,应用需要从buffer.position()开始读取数据
  11. //收到的数据组不了业务包,则返回null以告诉框架数据不够
  12. if (readableLength < HelloPacket.HEADER_LENGHT) {
  13. return null;
  14. }
  15. //读取消息体的长度
  16. int bodyLength = buffer.getInt();
  17. //数据不正确,则抛出AioDecodeException异常
  18. if (bodyLength < 0) {
  19. throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
  20. }
  21. //计算本次需要的数据长度
  22. int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
  23. //收到的数据是否足够组包
  24. int isDataEnough = readableLength - neededLength;
  25. // 不够消息体长度(剩下的buffe组不了消息体)
  26. if (isDataEnough < 0) {
  27. return null;
  28. } else {
  29. //组包成功
  30. HelloPacket imPacket = new HelloPacket();
  31. if (bodyLength > 0) {
  32. byte[] dst = new byte[bodyLength];
  33. buffer.get(dst);
  34. imPacket.setBody(dst);
  35. }
  36. return imPacket;
  37. }
  38. }
  39. /**
  40. * 编码:把业务消息包编码为可以发送的ByteBuffer
  41. * 总的消息结构:消息头 + 消息体
  42. * 消息头结构: 4个字节,存储消息体的长度
  43. * 消息体结构: 对象的json串的byte[]
  44. */
  45. @Override
  46. public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
  47. HelloPacket helloPacket = (HelloPacket) packet;
  48. byte[] body = helloPacket.getBody();
  49. int bodyLen = 0;
  50. if (body != null) {
  51. bodyLen = body.length;
  52. }
  53. //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
  54. int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
  55. //创建一个新的bytebuffer
  56. ByteBuffer buffer = ByteBuffer.allocate(allLen);
  57. //设置字节序
  58. buffer.order(tioConfig.getByteOrder());
  59. //写入消息头----消息头的内容就是消息体的长度
  60. buffer.putInt(bodyLen);
  61. //写入消息体
  62. if (body != null) {
  63. buffer.put(body);
  64. }
  65. return buffer;
  66. }
  67. /**
  68. * 处理消息
  69. */
  70. @Override
  71. public void handler(Packet packet, ChannelContext channelContext) throws Exception {
  72. HelloPacket helloPacket = (HelloPacket) packet;
  73. byte[] body = helloPacket.getBody();
  74. if (body != null) {
  75. String str = new String(body, HelloPacket.CHARSET);
  76. System.out.println("收到消息:" + str);
  77. HelloPacket resppacket = new HelloPacket();
  78. resppacket.setBody(("收到了你的消息,你的消息是:" + str).getBytes(HelloPacket.CHARSET));
  79. Tio.send(channelContext, resppacket);
  80. }
  81. }
  82. }
  • HelloServerStarter
  1. public class HelloServerStarter {
  2. /**
  3. * handler, 包括编码、解码、消息处理
  4. */
  5. public static ServerAioHandler aioHandler = new HelloServerAioHandler();
  6. /**
  7. * 事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
  8. */
  9. public static ServerAioListener aioListener = null;
  10. /**
  11. * 一组连接共用的上下文对象
  12. */
  13. public static ServerTioConfig serverTioConfig = new ServerTioConfig("hello-tio-server", aioHandler, aioListener);
  14. /**
  15. * tioServer对象
  16. */
  17. public static TioServer tioServer = new TioServer(serverTioConfig);
  18. /**
  19. * 有时候需要绑定ip,不需要则null
  20. */
  21. public static String serverIp = null;
  22. /**
  23. * 监听的端口
  24. */
  25. public static int serverPort = Const.PORT;
  26. /**
  27. * 启动程序入口
  28. */
  29. public static void main(String[] args) throws IOException {
  30. serverTioConfig.setHeartbeatTimeout(Const.TIMEOUT);
  31. tioServer.start(serverIp, serverPort);
  32. }
  33. }

2、 B/S

2.1 Serve

  • HttpServerInit
  1. public class HttpServerInit {
  2. private static final Logger log = LoggerFactory.getLogger(HttpServerInit.class);
  3. public static HttpConfig httpConfig;
  4. public static HttpRequestHandler requestHandler;
  5. public static HttpServerStarter httpServerStarter;
  6. public static ServerTioConfig serverTioConfig;
  7. public static void init() throws Exception {
  8. //启动端口
  9. int port = P.getInt("http.port");
  10. // html/css/js等的根目录,支持classpath:,也支持绝对路径
  11. String pageRoot = P.get("http.page");
  12. httpConfig = new HttpConfig(port, null, null, null);
  13. httpConfig.setPageRoot(pageRoot);
  14. httpConfig.setMaxLiveTimeOfStaticRes(P.getInt("http.maxLiveTimeOfStaticRes"));
  15. httpConfig.setPage404(P.get("http.404"));
  16. httpConfig.setPage500(P.get("http.500"));
  17. httpConfig.setUseSession(false);
  18. httpConfig.setCheckHost(false);
  19. //第二个参数也可以是数组
  20. requestHandler = new DefaultHttpRequestHandler(httpConfig, ShowcaseWebsocketStarter.class);
  21. httpServerStarter = new HttpServerStarter(httpConfig, requestHandler);
  22. serverTioConfig = httpServerStarter.getServerTioConfig();
  23. httpServerStarter.start(); //启动http服务器
  24. String protocol = SslUtils.isSsl(serverTioConfig) ? "https" : "http";
  25. if (log.isInfoEnabled()) {
  26. log.info("\r\nTio Http Server启动完毕:\r\n访问地址:{}://127.0.0.1:{}", protocol, port);
  27. } else {
  28. System.out.println("\r\nTio Http Server启动完毕:,\r\n访问地址:" + protocol + "://127.0.0.1:" + port);
  29. }
  30. }
  31. }
  • ShowcaseServerConfig
  1. public abstract class ShowcaseServerConfig {
  2. /**
  3. * 协议名字(可以随便取,主要用于开发人员辨识)
  4. */
  5. public static final String PROTOCOL_NAME = "showcase";
  6. public static final String CHARSET = "utf-8";
  7. /**
  8. * 监听的ip
  9. */
  10. public static final String SERVER_IP = null;//null表示监听所有,并不指定ip
  11. /**
  12. * 监听端口
  13. */
  14. public static final int SERVER_PORT = 9326;
  15. /**
  16. * 心跳超时时间,单位:毫秒
  17. */
  18. public static final int HEARTBEAT_TIMEOUT = 1000 * 60;
  19. /**
  20. * ip数据监控统计,时间段
  21. * @author tanyaowu
  22. *
  23. */
  24. public interface IpStatDuration {
  25. Long DURATION_1 = Time.MINUTE_1 * 5;
  26. Long[] IPSTAT_DURATIONS = new Long[] { DURATION_1 };
  27. }
  28. }
  • ShowcaseWsMsgHandler
  1. public class ShowcaseWsMsgHandler implements IWsMsgHandler {
  2. private static Logger log = LoggerFactory.getLogger(ShowcaseWsMsgHandler.class);
  3. public static final ShowcaseWsMsgHandler me = new ShowcaseWsMsgHandler();
  4. private ShowcaseWsMsgHandler() {}
  5. /**
  6. * 握手时走这个方法,业务可以在这里获取cookie,request参数等
  7. */
  8. @Override
  9. public HttpResponse handshake(HttpRequest request, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
  10. String clientip = request.getClientIp();
  11. String myname = request.getParam("name");
  12. Tio.bindUser(channelContext, myname);
  13. log.info("收到来自{}的ws握手包\r\n{}", clientip, request.toString());
  14. return httpResponse;
  15. }
  16. /**
  17. * @param httpRequest
  18. * @param httpResponse
  19. * @param channelContext
  20. * @throws Exception
  21. * @author tanyaowu
  22. */
  23. @Override
  24. public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
  25. //绑定到群组,后面会有群发
  26. Tio.bindGroup(channelContext, Const.GROUP_ID);
  27. int count = Tio.getAll(channelContext.tioConfig).getObj().size();
  28. String msg = "{name:'admin',message:'" + channelContext.userid + " 进来了,共【" + count + "】人在线" + "'}";
  29. //用tio-websocket,服务器发送到客户端的Packet都是WsResponse
  30. WsResponse wsResponse = WsResponse.fromText(msg, ShowcaseServerConfig.CHARSET);
  31. //群发
  32. Tio.sendToGroup(channelContext.tioConfig, Const.GROUP_ID, wsResponse);
  33. }
  34. /**
  35. * 字节消息(binaryType = arraybuffer)过来后会走这个方法
  36. */
  37. @Override
  38. public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
  39. return null;
  40. }
  41. /**
  42. * 当客户端发close flag时,会走这个方法
  43. */
  44. @Override
  45. public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
  46. Tio.remove(channelContext, "receive close flag");
  47. return null;
  48. }
  49. /**
  50. * 字符消息(binaryType = blob)过来后会走这个方法
  51. */
  52. @Override
  53. public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) throws Exception {
  54. WsSessionContext wsSessionContext = (WsSessionContext) channelContext.get();
  55. // 获取 websocket 握手包
  56. HttpRequest httpRequest = wsSessionContext.getHandshakeRequest();
  57. if (log.isDebugEnabled()) {
  58. log.debug("握手包:{}", httpRequest);
  59. }
  60. if (Objects.equals("心跳内容", text)) {
  61. return null;
  62. }
  63. //channelContext.getToken()
  64. //String msg = channelContext.getClientNode().toString() + " 说:" + text;
  65. String msg = "{name:'" + channelContext.userid + "',message:'" + text + "'}";
  66. //用tio-websocket,服务器发送到客户端的Packet都是WsResponse
  67. WsResponse wsResponse = WsResponse.fromText(msg, ShowcaseServerConfig.CHARSET);
  68. //群发
  69. Tio.sendToGroup(channelContext.tioConfig, Const.GROUP_ID, wsResponse);
  70. //返回值是要发送给客户端的内容,一般都是返回null
  71. return null;
  72. }
  73. }
  • ShowcaseWebsocketStarter
  1. public class ShowcaseWebsocketStarter {
  2. private WsServerStarter wsServerStarter;
  3. private ServerTioConfig serverTioConfig;
  4. /**
  5. *
  6. * @author tanyaowu
  7. */
  8. public ShowcaseWebsocketStarter(int port, ShowcaseWsMsgHandler wsMsgHandler) throws Exception {
  9. wsServerStarter = new WsServerStarter(port, wsMsgHandler);
  10. serverTioConfig = wsServerStarter.getServerTioConfig();
  11. serverTioConfig.setName(ShowcaseServerConfig.PROTOCOL_NAME);
  12. //设置ip统计时间段
  13. serverTioConfig.ipStats.addDurations(ShowcaseServerConfig.IpStatDuration.IPSTAT_DURATIONS);
  14. //设置心跳超时时间
  15. serverTioConfig.setHeartbeatTimeout(ShowcaseServerConfig.HEARTBEAT_TIMEOUT);
  16. // if (P.getInt("ws.use.ssl", 1) == 1) {
  17. // // 通过wss来访问,要有SSL证书(证书必须和域名相匹配,否则可能访问不了ssl)
  18. //// String keyStoreFile = "classpath:config/ssl/keystore.jks";
  19. //// String trustStoreFile = "classpath:config/ssl/keystore.jks";
  20. //// String keyStorePwd = "214323428310224";
  21. //
  22. // String keyStoreFile = P.get("ssl.keystore", null);
  23. // String trustStoreFile = P.get("ssl.truststore", null);
  24. // String keyStorePwd = P.get("ssl.pwd", null);
  25. // serverTioConfig.useSsl(keyStoreFile, trustStoreFile, keyStorePwd);
  26. // }
  27. }
  28. /**
  29. * @author tanyaowu
  30. * @throws IOException
  31. */
  32. public static void start() throws Exception {
  33. ShowcaseWebsocketStarter appStarter = new ShowcaseWebsocketStarter(ShowcaseServerConfig.SERVER_PORT, ShowcaseWsMsgHandler.me);
  34. appStarter.wsServerStarter.start();
  35. }
  36. public static void main(String[] args) throws Exception {
  37. //启动http server,这个步骤不是必须的,但是为了用页面演示websocket,所以先启动http
  38. P.use("app.properties");
  39. HttpServerInit.init();
  40. //启动websocket server
  41. start();
  42. }
  43. }

2.2 Browser

  • index.html
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>tio-ws演示</title>
  6. </head>
  7. <body>
  8. <div id="app">
  9. <iframe src="./im.html" style="width:500px; height:850px;"></iframe>
  10. <iframe src="./im.html" style="width:500px; height:850px;"></iframe>
  11. <iframe src="./im.html" style="width:500px; height:850px;"></iframe>
  12. </div>
  13. </body>
  14. </html>
  • im.html
  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>IM Now!</title>
  6. <link rel="stylesheet" type="text/css" href="css/im.css" />
  7. </head>
  8. <body>
  9. <div id="container">
  10. <div class="header">
  11. <span style="float: left;">微信聊天界面</span>
  12. <span id="mtime" style="float: right;"></span>
  13. </div>
  14. <ul class="content"></ul>
  15. <div class="footer">
  16. <div id="user_face_icon">
  17. <img src="image/t2.jpg" alt="">
  18. </div>
  19. <input id="messageText" type="text" placeholder="说点什么吧..." >
  20. <span id="btn" onclick="sendMsg()">发送</span>
  21. </div>
  22. </div>
  23. <script src="js/tiows.js"></script>
  24. <script src="js/name.js"></script>
  25. <script src="js/imHandler.js"></script>
  26. <script src="js/im.js"></script>
  27. </body>
  28. </html>
  • im.css
  1. *{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;}
  • im.js
  1. const ws_protocol = 'ws'; // ws 或 wss
  2. const ip = '127.0.0.1'
  3. const port = 9326
  4. const heartbeatTimeout = 5000; // 心跳超时时间,单位:毫秒
  5. const reconnInterval = 1000; // 重连间隔时间,单位:毫秒
  6. const binaryType = 'blob'; // 'blob' or 'arraybuffer';//arraybuffer是字节
  7. const handler = new IMHandler()
  8. let mtime = "";
  9. let myname = "";
  10. let tiows;
  11. function initWs () {
  12. myname=getRandomName();
  13. const queryString = 'name=' + myname;
  14. const param = "";
  15. tiows = new tio.ws(ws_protocol, ip, port, queryString, param, handler, heartbeatTimeout, reconnInterval, binaryType)
  16. tiows.connect()
  17. }
  18. //回车事件绑定
  19. document.onkeydown = function(e){
  20. const ev = document.all ? window.event : e;
  21. if(ev.keyCode===13) {
  22. sendMsg();
  23. }
  24. }
  25. function showTime()
  26. {
  27. const myDate = new Date();
  28. let m = myDate.getMinutes();
  29. if (m<10) m="0"+m;
  30. let s = myDate.getSeconds();
  31. if (s<10) s="0"+s;
  32. mtime=myDate.getHours()+":"+m+":"+s;
  33. document.getElementById("mtime").innerText=mtime;
  34. }
  35. function sendMsg () {
  36. const msg = document.getElementById('messageText');
  37. tiows.send(msg.value);
  38. msg.value="";
  39. }
  40. function clearMsg () {
  41. document.getElementById('contentId').innerHTML = ''
  42. }
  43. initWs ()
  44. setInterval(showTime,1000);
  • imHandler.js
  1. const IMHandler = function () {
  2. this.onopen = function (event, ws) {
  3. // ws.send('hello 连上了')
  4. //document.getElementById('contentId').innerHTML += 'hello 连上了<br>';
  5. }
  6. /**
  7. * 收到服务器发来的消息
  8. * @param {*} event
  9. * @param {*} ws
  10. */
  11. this.onmessage = function (event, ws) {
  12. const data = event.data;
  13. const msgBody = eval('(' + data + ')');
  14. let ico, imgcls, spancls;
  15. const content = document.getElementsByTagName('ul')[0];
  16. let nickcls;
  17. if (myname === msgBody.name) {
  18. // 头像
  19. ico = "image/t2.jpg";
  20. imgcls = "imgright";
  21. spancls = "spanright";
  22. nickcls = "nickright";
  23. } else {
  24. // 头像
  25. ico = "image/t1.jpg";
  26. imgcls = "imgleft";
  27. spancls = "spanleft";
  28. nickcls = "nickleft"
  29. }
  30. if (msgBody.name === "admin") {
  31. content.innerHTML += '<li><div class="sysinfo">' + msgBody.message + '</div></li>';
  32. } else {
  33. content.innerHTML += '<li><img src="' + ico + '" class="' + imgcls + '"><span class="' + nickcls + '">' + msgBody.name + '</span><span class="' + spancls + '">' + msgBody.message + '</span></li>';
  34. }
  35. console.log("-----------")
  36. content.scrollTop = content.scrollHeight;
  37. }
  38. this.onclose = function (e, ws) {
  39. // error(e, ws)
  40. }
  41. this.onerror = function (e, ws) {
  42. // error(e, ws)
  43. }
  44. /**
  45. * 发送心跳,框架会自动定时调用该方法,请在该方法中发送心跳
  46. * @param {*} ws
  47. */
  48. this.ping = function (ws) {
  49. // log("发心跳了")
  50. ws.send('心跳内容')
  51. }
  52. };
  • name.js
  1. function getRandomName(){
  2. const firstNames = ["赵", "钱", "孙", "李"];
  3. const secondNames = ["三", "四", "五", "六"];
  4. const firstLength = firstNames.length;
  5. const secondLength = secondNames.length;
  6. const i = parseInt(Math.random() * firstLength);
  7. const j = parseInt(Math.random() * secondLength);
  8. return firstNames[i] + secondNames[j];
  9. }
  • tiows.js
  1. if (typeof(tio) == "undefined") {
  2. tio = {};
  3. }
  4. tio.ws = {};
  5. /**
  6. * @param {*} ws_protocol wss or ws
  7. * @param {*} ip
  8. * @param {*} port
  9. * @param {*} paramStr 加在ws url后面的请求参数,形如:name=张三&id=12
  10. * @param {*} param 作为tio.ws对象的参数,由业务自己使用,框架不使用
  11. * @param {*} handler
  12. * @param {*} heartbeatTimeout 心跳时间 单位:毫秒
  13. * @param {*} reconnInterval 重连间隔时间 单位:毫秒
  14. * @param {*} binaryType 'blob' or 'arraybuffer';//arraybuffer是字节
  15. */
  16. tio.ws = function (ws_protocol, ip, port, paramStr, param, handler, heartbeatTimeout, reconnInterval, binaryType) {
  17. this.url = ws_protocol + '://' + ip + ':' + port
  18. this.binaryType = binaryType || 'arraybuffer'
  19. if (paramStr) {
  20. this.url += '?' + paramStr
  21. this.reconnUrl = this.url + "&"
  22. } else {
  23. this.reconnUrl = this.url + "?"
  24. }
  25. this.reconnUrl += "tiows_reconnect=true";
  26. this.handler = handler
  27. this.heartbeatTimeout = heartbeatTimeout
  28. this.reconnInterval = reconnInterval
  29. this.lastInteractionTime = function () {
  30. if (arguments.length === 1) {
  31. this.lastInteractionTimeValue = arguments[0]
  32. }
  33. return this.lastInteractionTimeValue
  34. }
  35. this.heartbeatSendInterval = heartbeatTimeout / 2
  36. this.connect = function (isReconnect) {
  37. let _url = this.url;
  38. if (isReconnect) {
  39. _url = this.reconnUrl;
  40. }
  41. const ws = new WebSocket(_url);
  42. this.ws = ws
  43. ws.binaryType = this.binaryType; // 'arraybuffer'; // 'blob' or 'arraybuffer';//arraybuffer是字节
  44. const self = this;
  45. ws.onopen = function (event) {
  46. self.handler.onopen.call(self.handler, event, ws)
  47. self.lastInteractionTime(new Date().getTime())
  48. self.pingIntervalId = setInterval(function () {
  49. self.ping(self)
  50. }, self.heartbeatSendInterval)
  51. }
  52. ws.onmessage = function (event) {
  53. self.handler.onmessage.call(self.handler, event, ws)
  54. self.lastInteractionTime(new Date().getTime())
  55. }
  56. ws.onclose = function (event) {
  57. clearInterval(self.pingIntervalId) // clear send heartbeat task
  58. try {
  59. self.handler.onclose.call(self.handler, event, ws)
  60. } catch (error) {}
  61. self.reconn(event)
  62. }
  63. ws.onerror = function (event) {
  64. self.handler.onerror.call(self.handler, event, ws)
  65. }
  66. return ws
  67. }
  68. this.reconn = function (event) {
  69. const self = this;
  70. setTimeout(function () {
  71. self.ws = self.connect(true)
  72. }, self.reconnInterval)
  73. }
  74. this.ping = function () {
  75. const iv = new Date().getTime() - this.lastInteractionTime(); // 已经多久没发消息了
  76. // 单位:秒
  77. if ((this.heartbeatSendInterval + iv) >= this.heartbeatTimeout) {
  78. this.handler.ping(this.ws)
  79. }
  80. };
  81. this.send = function(data){
  82. this.ws.send(data);
  83. };
  84. }