websocket封装

使用原生websocket,在websocket中加入了心跳机制,断网重连等功能

ps:最开始使用的是uni的uni.connectSocket方法,然而在实际使用过程中,发现一旦断网或者服务器断开一会儿,做不到每隔几秒钟重连一次,他是第一次重连发送一个请求,第一百次就会发送100个请求,后来通过清除定时器的方式实现了,但这就导致第100次重连需要清除99个定时器,越来越多,不太放心,后来没办法,改用原生的websocket重新实现,没有出现之前的问题。有知道为啥的小伙伴欢迎指正。下面分别贴上原生的和uni.connectSocket两种方式的代码,以及服务端代码。

1.原生websocket:

class WebSocketClient {

    constructor(url, reconnectInterval = 5000, heartbeatInterval = 10000, maxReconnectAttempts = 10, {

        onOpen = () => {},

        onMessage = () => {},

        onClose = () => {},

        onError = () => {}

    } = {}) {

        this.url = url;

        this.reconnectInterval = reconnectInterval;

        this.heartbeatInterval = heartbeatInterval;

        this.reconnectTimer = null;

        this.heartbeatTimer = null;

        this.pongTimeout = null;

        this.onOpen = onOpen;

        this.onMessage = onMessage;

        this.onClose = onClose;

        this.onError = onError;

        this.maxReconnectAttempts = maxReconnectAttempts;

        this.reconnectAttempts = 0;

        this.connect();

    }

    connect() {

        this.socket = new WebSocket(this.url);

        this.socket.addEventListener('open', (event) => {

            console.log('已连接到服务器');

            this.reconnectAttempts = 0;

            if (this.reconnectTimer) {

                clearInterval(this.reconnectTimer);

                this.reconnectTimer = null;

            }

            this.startHeartbeat();

            this.onOpen(event);

        });

        this.socket.addEventListener('message', (event) => {

            if (event.data === 'PONG') {

                clearTimeout(this.pongTimeout);

            }

            this.onMessage(event);

        });

        this.socket.addEventListener('close', (event) => {

            this.stopHeartbeat();

            console.log('与服务器的连接已关闭,尝试重新连接...');

            console.log('this.reconnectAttempts:', this.reconnectAttempts);

            if (this.reconnectAttempts < this.maxReconnectAttempts) {

                if (!this.reconnectTimer) {

                    this.reconnectTimer = setInterval(() => {

                        this.reconnectAttempts++;

                        this.connect();

                    }, this.reconnectInterval);

                }

            } else {

                // 清除重连定时器

                if (this.reconnectTimer) {

                    clearInterval(this.reconnectTimer);

                    this.reconnectTimer = null;

                }

                console.log('达到最大重连次数,停止重连');

            }

            this.onClose(event);

        });

        this.socket.addEventListener('error', (event) => {

            console.error('WebSocket 连接出错:', event);

            this.stopHeartbeat();

            if (this.reconnectAttempts < this.maxReconnectAttempts) {

                if (!this.reconnectTimer) {

                    this.reconnectTimer = setInterval(() => {

                        this.reconnectAttempts++;

                        this.connect();

                    }, this.reconnectInterval);

                }

            } else {

                // 清除重连定时器

                if (this.reconnectTimer) {

                    clearInterval(this.reconnectTimer);

                    this.reconnectTimer = null;

                }

                console.log('达到最大重连次数,停止重连');

            }

            this.onError(event);

        });

    }

    startHeartbeat() {

        this.heartbeatTimer = setInterval(() => {

            if (this.socket.readyState === WebSocket.OPEN) {

                this.socket.send('PING');

                this.pongTimeout = setTimeout(() => {

                    console.log('未收到 pong 响应,断开连接');

                    this.socket.close();

                }, 5000);

            }

        }, this.heartbeatInterval);

    }

    stopHeartbeat() {

        if (this.heartbeatTimer) {

            clearInterval(this.heartbeatTimer);

            this.heartbeatTimer = null;

        }

        if (this.pongTimeout) {

            clearTimeout(this.pongTimeout);

            this.pongTimeout = null;

        }

    }

    sendMessage() {

        const input = document.getElementById('input');

        const message = input.value;

        if (message) {

            this.socket.send(message);

            input.value = '';

        }

    }

}

export default WebSocketClient;

2.uni.connectSocket

interface Options {

  url: string,

  header?: {},

  reconnectAttempts?: number,

  maxReconnectAttempts?: number,

  interval?:number,

  success: (res: any) => void,

  fail: (res: any) => void,

  onOpen: (res: any) => void,

  onMessage: (res: any) => void,

  onError: (res: any) => void,

  onClose: (res: any) => void,

}

export class WebSocketService {

  private ws: UniApp.SocketTask | null = null

  private isClosed = false

  private reconnectAttempts = 0

  private maxReconnectAttempts = 100

  private readonly reconnectInterval = 1000

  private heartbeatInterval: number | null = null

  private heartbeatTimeout: number | null = null

  private interval = 0

  private options?: Options

  private readonly heartbeatTimeoutDuration = 5000; // 心跳超时时间,单位毫秒

  private setTimer: number | null = null

  createConnect(options: Options) {

    if(this.ws) {

      console.log('ws 已经存在')

      this.ws.close({})

      this.ws = null

    }

    this.reconnectAttempts = options.reconnectAttempts || 0

    this.maxReconnectAttempts = options.maxReconnectAttempts || 100

    this.interval = options.interval || 10000

    this.options = {

      url: options.url,

      header: options.header || {},

      success: (res: any) => {

        options.success(res)

      },

      fail: (res: any) => {

        options.fail(res)

      },

      onOpen: (res: any) => {

        options.onOpen(res)

        this.reconnectAttempts = 0

        this.startHeartbeat()

      },

      onMessage: (res: any) => {

        this.handleHeartbeatResponse(res)

        options.onMessage(res)

      },

      onError: (res: any) => {

        options.onError(res)

      },

      onClose: (res: any) => {

        options.onClose(res)

        if(!this.isClosed) {

          this.reconnect()

        }

      }

    }

    const that = this

    if(this.options) {

     

      this.ws = uni.connectSocket({

        url: this.options.url,

        header: this.options.header,

        success: (res: any) => {

          this.options!.success(res)

        },

        fail: (res: any) => {

          this.options!.fail(res)

        },

      })

      this.ws!.onOpen((res: any) => {

        that.options!.onOpen(res)

      })

      this.ws!.onMessage((res: any) => {

        that.options!.onMessage(res)

      })

      this.ws!.onClose((res: any) => {

        that.options!.onClose(res)

      })

      this.ws!.onError((res: any) => {

        that.options!.onError(res)

      })

    }

    return this.ws

  }

  private startHeartbeat() {

    // this.heartbeatInterval = setInterval(() => {

    //   if(this.ws?.readyState === WebSocket.OPEN) {

    //     this.ws!.send({

    //       data: 'PING'

    //     });

    //     this.startHeartbeatTimeout();

    //   } else {

    //     clearInterval(this.heartbeatInterval!);

    //   }

    // }, this.interval)

  }

  private startHeartbeatTimeout() {

    this.heartbeatTimeout = setTimeout(() => {

      console.log('心跳超时,尝试重新连接');

      this.ws?.close({});

      this.reconnect();

    }, this.heartbeatTimeoutDuration);

  }

  private handleHeartbeatResponse(res: any) {

    if (res.data === 'PONG') {

      if (this.heartbeatTimeout) {

        clearTimeout(this.heartbeatTimeout);

      }

    }

  }

  private reconnect() {

    if (this.isClosed) {

      return

    }

    // if(this.setTimer) {

    //   console.log('清除定时器')

    //   clearTimeout(this.setTimer)

    // }

    if(this.reconnectAttempts < this.maxReconnectAttempts) {

      console.log('重连次数',this.reconnectAttempts)

      if(!this.setTimer){

        this.setTimer = setTimeout(() => {

          this.createConnect({...this.options,reconnectAttempts:this.reconnectAttempts} as Options)

          this.reconnectAttempts ++

          clearTimeout(this.setTimer)

        }, this.reconnectInterval)

      }

    } else {

      this.isClosed = true

    }

  }

  close() {

    this.isClosed = true

    this.heartbeatInterval && clearInterval(this.heartbeatInterval)

    if (this.heartbeatTimeout) {

      clearTimeout(this.heartbeatTimeout);

    }

    this.ws?.close({

      success: () => {

        console.log('WebSocket disconnected successfully')

      },

      fail: () => {

        console.log('WebSocket disconnected fail')

      }

    })

    this.ws = null

  }

}

export default new WebSocketService()

3.自己调试用的服务端代码

const WebSocket = require('ws');

// 创建 WebSocket 服务器,监听 8080 端口

const wss = new WebSocket.Server({ port: 8080 });

// 存储所有连接的客户端

const clients = new Set();

// 当有新的客户端连接时触发

wss.on('connection', (ws) => {

    // 将新连接的客户端添加到集合中

    clients.add(ws);

    ws.send('连接成功');

    // 当接收到客户端发送的消息时触发

    ws.on('message', (message) => {

        // 将 Buffer 对象转换为字符串

        const messageStr = message.toString();

        console.log('Received message:', message instanceof Buffer);

        if (messageStr === 'PING') {

            // 收到客户端的 ping 消息,回复 pong

            ws.send('PONG');

            return;

        }

        // 遍历所有客户端

        clients.forEach((client) => {

            // 确保消息不会发送回发送者

            if (client!== ws && client.readyState === WebSocket.OPEN) {

                // 向客户端发送消息

                client.send(messageStr);

            }

        });

    });

    // 当客户端断开连接时触发

    ws.on('close', () => {

        // 从集合中移除断开连接的客户端

        clients.delete(ws);

    });

    // 错误处理

    ws.on('error', (error) => {

        console.error('WebSocket error:', error);

        clients.delete(ws);

    });

});

console.log('WebSocket server is running on port 8080');

### 实现 WebSocket 封装 为了简化 WebSocket 的开发流程并提高代码的可维护性和重用性,通常会创建一个 WebSocket 封装库。这种封装不仅限于简单的连接管理,还可能涉及消息处理、错误恢复机制等功能。 #### 创建自定义 WebSocket 封装库 以下是 Python 中基于 `websockets` 库的一个简单 WebSocket 客户端封装实例: ```python import asyncio import websockets class SimpleWebSocketClient: def __init__(self, uri): self.uri = uri self.connection = None async def connect(self): try: self.connection = await websockets.connect(self.uri) print(f"Connected to server at {self.uri}") except Exception as e: print(f"Failed to connect: {e}") async def send_message(self, message): if not self.connection or not self.connection.open: raise ConnectionError("Not connected.") await self.connection.send(message) async def receive_message(self): while True: try: response = await self.connection.recv() return response except websockets.ConnectionClosed: break async def close_connection(self): if self.connection and self.connection.open: await self.connection.close() async def main(): client = SimpleWebSocketClient('ws://localhost:8765') await client.connect() await client.send_message("Hello Server!") reply = await client.receive_message() print(f"Received from server: {reply}") await client.close_connection() if __name__ == "__main__": asyncio.run(main()) ``` 此代码展示了如何构建一个基本的 WebSocket 客户端类[^1],它能够执行连接服务器、发送和接收数据的操作,并妥善关闭连接。 对于更复杂的场景,比如需要支持多线程操作或是与其他框架集成,则建议考虑现有的成熟解决方案,如 Flask-SocketIO 提供了对 WebSockets 和 Socket.IO 协议的支持,在 Flask 应用中非常容易上手[^3]。 #### 使用现有 WebSocket 封装库 除了自行编写外,还有许多成熟的 WebSocket 封装库可供选择,这些库往往已经解决了大量实际应用中的难题,提供了丰富的特性集。例如: - **Flask-SocketIO**: 结合 Flask 框架使用的 WebSocket 扩展包,适用于构建实时网络应用程序。 - **AutobahnPython**: 支持 WAMP (Web Application Messaging Protocol) 的高级 WebSocket/WSGI/Twisted 库,适合复杂的企业级项目需求。 - **Tornado**: Tornado 是另一个流行的异步网络库,内置了对 WebSocket 的良好支持,非常适合高并发环境下的服务端编程。 综上所述,无论是自己动手还是借助第三方工具,都可以有效地完成 WebSocket 功能的封装工作,从而让开发者专注于业务逻辑本身而不是底层协议细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值