记录websocket的使用,实现断网重连和心跳机制。

172 阅读3分钟

需求

校园安全告警的项目,其中一个需要实现的功能就是可以实时告警,简单来说,是后台通过接收校园里拾音器的关键词告警以后,推送到前端,前端弹窗提示告警,做出处理。所以采用了webSocket来实现。

基本使用

  1. 这里用正则判断url地址的前缀并替换,后面是后台接口地址,最后是用户ID(后台所需参数)
const url = `${baseURL.replace("https://", "ws://").replace("http://", "ws://")}/websocket/api/${userId}`;
  1. 然后在useEffect中写入连接,组件销毁的时候再去关闭连接。
// webSocket 连接状态
const [isConnect, setIsConnect] = useState<number>(-1);
const [socket, setSocket] = useState<any>(null);
useEffect(() => {
    const ws = new WebSocket(url);
         setSocket(ws);
              return () => {
                       ws.close();
                            };
                            }, []);

3.在新的useEffect中定义各个状态的回调。

useEffect(() => {
                               if (socket) {
                                      //onopen是webSocket连接成功后的回调,可以在这里打印一下,如果成功打印,则代表连接成功
                                             socket.onopen = () => {
                                                        setIsConnect(1)
                                                               };

                                                                  //WebSocket协议只允许字符串或二进制类型的数据传输
                                                                         //onmessage是接受到数据后的回调
                                                                                socket.onmessage = (data: string) =&gt; acceptMessage(data);
                                                                                
                                                                                       //onclose是连接关闭后的回调。后面均为自定义函数,可以在新的函数里写自己的业务逻辑
                                                                                              socket.onclose = () =&gt; webSocketOnClose();
                                                                                                     socket.onerror = () =&gt; webSocketOnError();
                                                                                                        }
                                                                                                        }, [socket]);</code></pre><p>4.接收消息,在这一步,后台推送消息过来后打印出来,就代表成功接收,可以写自己业务逻辑了。</p><pre><code>const acceptMessage = ({ data }: string) =&gt; {
                                                                                                            console.log(data)
                                                                                                            }</code></pre><p>5.前端断网重连功能,场景大概就是如果前端网络中断了以后,websocket就断开了,这时候网络恢复后,是不会自动重连的,所以加上这两个监听就可以实现了。</p><pre><code>const initWebSocket = () =&gt; {
                                                                                                                const ws = new WebSocket(url);
                                                                                                                    setSocket(ws);
                                                                                                                        console.log('socket初始化')
                                                                                                                        }   
                                                                                                                        // 断网重连
                                                                                                                        useEffect(() =&gt; {
                                                                                                                            const handleOnline = () =&gt; initWebSocket();
                                                                                                                                const handleOffline = () =&gt; {
                                                                                                                                        if (socket) {
                                                                                                                                                    socket.close();
                                                                                                                                                                setIsConnect(0);
                                                                                                                                                                        }
                                                                                                                                                                            };
                                                                                                                                                                                window.addEventListener("online", handleOnline);
                                                                                                                                                                                    window.addEventListener("offline", handleOffline);
                                                                                                                                                                                        return () =&gt; {
                                                                                                                                                                                                window.removeEventListener("online", handleOnline);
                                                                                                                                                                                                        window.removeEventListener("offline", handleOffline);
                                                                                                                                                                                                            };
                                                                                                                                                                                                            }, [socket]);</code></pre><ol start="6"><li>心跳机制功能实现。这里是和后端约定好的,我给他发ping,他接受到以后给我返pong,这种情况可以认定是链接成功状态的。因为当时考虑可以用onclose事件去重连。但是会出现一个问题就是,当组件卸载后,链接会关闭,这个时候onclose监听到了,他又会去做个新的链接,会导致后台无效链接越来越多。所以采用了手写心跳的方式。</li></ol><pre><code>useEffect(() =&gt; {
                                                                                                                                                                                                                if (socket) {
                                                                                                                                                                                                                        socket.onopen = () =&gt; {
                                                                                                                                                                                                                                   setIsConnect(1)
                                                                                                                                                                                                                                           };
                                                                                                                                                                                                                                                   socket.onmessage = (data: string) =&gt; acceptMessage(data);
                                                                                                                                                                                                                                                           socket.onclose = () =&gt; webSocketOnClose();
                                                                                                                                                                                                                                                                   socket.onerror = () =&gt; webSocketOnError();
                                                                                                                                                                                                                                                                   
                                                                                                                                                                                                                                                                           // 心跳机制 实现思路,每跟后台发一次ping,前端计数+1,后台返回pong,计数清零,如没有返回pong,则计数累加,当计数大于5后,重新链接。
                                                                                                                                                                                                                                                                                   const heartbeatInterval = setInterval(() =&gt; {
                                                                                                                                                                                                                                                                                               if (socket.readyState === WebSocket.OPEN) {
                                                                                                                                                                                                                                                                                                                socket.send(`${userId},PING`);
                                                                                                                                                                                                                                                                                                                                 setPingNum(() =&gt; pingNum + 1)
                                                                                                                                                                                                                                                                                                                                             }
                                                                                                                                                                                                                                                                                                                                                     }, 5000);
                                                                                                                                                                                                                                                                                                                                                             return () =&gt; clearInterval(heartbeatInterval);
                                                                                                                                                                                                                                                                                                                                                                 }
                                                                                                                                                                                                                                                                                                                                                                 }, [socket, pingNum]);
                                                                                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                                                                 useEffect(() =&gt; {
                                                                                                                                                                                                                                                                                                                                                                     if (pingNum &gt; 5) {
                                                                                                                                                                                                                                                                                                                                                                             initWebSocket();
                                                                                                                                                                                                                                                                                                                                                                                 }
                                                                                                                                                                                                                                                                                                                                                                                 }, [pingNum])
                                                                                                                                                                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                                                                                                                 const acceptMessage = ({ data }: any) =&gt; {
                                                                                                                                                                                                                                                                                                                                                                                     const acceptData = JSON.parse(data);
                                                                                                                                                                                                                                                                                                                                                                                         if (acceptData === "PONG") {
                                                                                                                                                                                                                                                                                                                                                                                                 setPingNum(0)
                                                                                                                                                                                                                                                                                                                                                                                                     }
                                                                                                                                                                                                                                                                                                                                                                                                     }</code></pre><h4>如有不对的地方,请指正。欢迎大家来一起探讨学习。因为写文章的时候距离实现功能已经有一段时间了,所有有些地方可能记得没那么清楚了。</h4>