JianghuJS- SocketIO

12002

1、背景

  • socket.io说明

Socket.io是一个WebSocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。

它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。

  • 为什么要使用 socket.io

    使用 socket.io 的主要原因在于它提供了一种简便而灵活的方式来实现实时双向通信,尤其在构建需要实时性的应用中,socket.io 提供了许多优势:

    1. 实时性: socket.io 基于 WebSocket 协议,能够在客户端和服务器之间建立持久性的双向连接,实现实时性非常高,适用于需要即时通信的场景,如聊天应用、实时协作工具等。

    2. 跨平台兼容性: socket.io 能够在不同的浏览器和移动设备上运行,提供了跨平台的兼容性,使开发者不需要过多关注不同平台的差异性。

    3. 事件驱动模型: socket.io 使用事件驱动的编程模型,通过触发和监听事件的方式进行通信。这种模型使得代码更加清晰、易于理解,并能够方便地处理不同场景下的异步操作。

    4. 灵活性和扩展性: socket.io 具有良好的灵活性和扩展性,可以轻松地应对各种实时通信需求。同时,它支持多个房间(Rooms)和命名空间(Namespaces),使得开发者可以更精细地控制通信的范围。

    5. 简化实时应用开发: socket.io 的存在简化了实时应用的开发流程。开发者可以更专注于业务逻辑,而不用过多关心底层通信细节。这有助于提高开发效率。

    6. 自适应传输方式: socket.io 能够根据浏览器和网络条件自动选择最佳的传输方式,包括 WebSocket、AJAX 长轮询、Iframe 流等。这使得在各种网络环境下都能够提供可靠的实时通信服务。

2、特点

  • 实现的功能
  1. 实时分析: 通过实时推送数据,可以实现实时的分析,用于实时计数器、图表显示、日志等应用场景。

  2. 实时通信和聊天: socket.io 提供了简便的 API,使得实时通信和聊天应用的开发变得容易,仅需几行代码就能实现一个实时的聊天应用。

  3. 二进制流传输: 从1.0版本开始,socket.io 支持任何形式的二进制文件传输,如图片、视频、音频等,使得在实时通信中可以传输各种类型的文件。

  4. 文档合并: socket.io 可用于实现多用户协同编辑一个文档,每个用户的修改都能够实时同步给其他用户,从而实现文档的协同编辑

3、使用

  • 服务端

  • 配置jiangHuConfig, 启用socket:

  1. // config.default.js
  2. jiangHuConfig: {
  3. enableSocket: true
  4. }
  • 配置socketIO配置信息:
  1. // config.default.js jianghuConfig同级
  2. socketIO: {
  3. path: `/${appId}/socket.io`,
  4. serveClient: true,
  5. connectTimeout: 45000,
  6. // 多work模式下需要 启用 redis adapter
  7. // redis: {
  8. // host: "127.0.0.1",
  9. // port: "6379",
  10. // // password: '',
  11. // db: 0,
  12. // },
  13. },
  • 发送socket消息:
  1. const { socketIO } = this.app;
  2. socketIO.to(toSocketId).emit(resourcePath, socketBody);
  • 客户端

  • 初始化socket 客户端:

  1. function init() {
  2. const requestBody = {
  3. packageId,
  4. packageType: "socketRequest",
  5. deviceId: socket.deviceId,
  6. status: null,
  7. timestamp: new Date().toISOString(),
  8. appData: {
  9. appId: window.appInfo.appId,
  10. pageId: "socket",
  11. actionId: "connect",
  12. authToken: socket.authToken,
  13. actionData: {
  14. socketId,
  15. },
  16. },
  17. };
  18. try {
  19. socket.client = io("/", {
  20. path: `/${window.appInfo.appId}/socket.io`,
  21. auth: requestBody,
  22. closeOnBeforeunload: true,
  23. transports: ["websocket"],
  24. forceNew: true,
  25. timeout: 5000,
  26. secure: false, // 是否支持SSL/TLS
  27. });
  28. } catch (e) {
  29. console.error(e);
  30. }
  31. }
  • 注册客户端监听事件:
  1. socket.client.on("resource", async (message) => {
  2. console.log("==== [socket channel.resource]", JSON.stringify(message));
  3. const { appId, pageId, actionId } = message.appData;
  4. socket.packageId = message.packageId;
  5. // 认证成功
  6. if (pageId === "socket" && actionId === "connect") {
  7. // 每次掉线都会重新走一遍认证过程,code=100的逻辑要注意别搞混
  8. // 设置在线标识
  9. socket.online = true;
  10. socket.loadFinish = true;
  11. console.log("socket.hasDisconnectOrError :: " + socket.hasDisconnectOrError)
  12. socket.onReConnect(socket.hasDisconnectOrError)
  13. } else {
  14. socket.receiveSocketMsg(message.appData, message.packageId);
  15. if (socket.packageListeners[message.packageId]) {
  16. const { callback } = socket.packageListeners[message.packageId];
  17. callback(message);
  18. }
  19. }
  20. });
  21. socket.client.on("connect", (message) => {
  22. console.log("==== [socket channel.onConnect]", message);
  23. });
  24. socket.client.on("connect_timeout", (message) => {
  25. console.log("==== [socket channel.connect_timeout]", message);
  26. });
  27. socket.client.on("connect_error", (message) => {
  28. console.log("==== [socket channel.onError]", message);
  29. socket.hasDisconnectOrError = true;
  30. socket.online = false;
  31. });
  32. socket.client.on("disconnect", (message) => {
  33. console.log("==== [socket channel.disconnect]", message);
  34. socket.online = false;
  35. socket.hasDisconnectOrError = true;
  36. });
  37. socket.client.on("reconnect", (message) => {
  38. socket.hasDisconnectOrError = true;
  39. console.log("==== [socket channel.onReconnect]", message);
  40. });
  41. socket.client.on("reconnect_attempt", (message) => {
  42. console.log("==== [socket channel.onReconnectAttempt]", message);
  43. });
  44. socket.client.on("reconnect_failed", (message) => {
  45. console.log("==== [socket channel.onReconnectFailed]", message);
  46. });
  47. socket.client.on("reconnect_error", (message) => {
  48. console.log("==== [socket channel.onReconnectError]", message);
  49. });
  50. socket.client.on("ping", (message) => {
  51. // 心跳请求
  52. // callback_onIMPing
  53. console.log("==== [socket channel.onPing]", message);
  54. });
  55. socket.client.on("pong", (message) => {
  56. // 心跳响应
  57. // callback_onIMPong
  58. console.log("==== [socket channel.onPong]", message);
  59. });
  • 添加socket客户端断开重连逻辑
  1. // 每5s检查一下连接状态,若连接已断开,则尝试重新连接
  2. setInterval(() => {
  3. if (!socket.connected) {
  4. this.init()
  5. }
  6. }, 5000)
  • 发送socket消息:
  1. const packageId = `${Date.now()}_${_.random(1000000, 9999999)}`;
  2. const requestBody = {
  3. packageId,
  4. packageType: "socketForward",
  5. deviceId: socket.deviceId,
  6. status: null,
  7. timestamp: new Date().toISOString(),
  8. appData: {
  9. appId: window.appInfo.appId,
  10. pageId: "socket",
  11. actionId,
  12. authToken: socket.authToken,
  13. actionData,
  14. },
  15. };
  16. socket.client.emit("resource", requestBody);