基于超时重传协议的websocket优化方案

8 阅读2分钟

概述

在实时通信系统中,确保消息的可靠传输是非常重要的。超时重传协议是一种常见的机制,用于在网络不稳定或消息丢失的情况下保证消息的最终送达。本文将详细介绍超时重传协议的工作原理,并提供一种基于SignalR的实现方案。此外,还将讨论如何优化该协议以降低服务器负载。

工作原理

超时重传协议的基本思想是在发送消息后设置一个定时器。如果在指定的时间内(例如10秒)没有收到确认(ACK),则认为消息未成功送达,并重新发送该消息。这种机制可以有效地应对网络延迟和丢包问题。

[Hub]

服务端使用SignalR库来处理客户端的连接和消息传递。以下是关键部分的代码实现:

using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

public class WebsocketLib : Hub
{
    // 存储待处理消息的计时器
    private readonly Dictionary<string, Timer> _pendingMessages = new Dictionary<string, Timer>();

    // 设置最大重试次数
    private readonly int maxRetries = 3;

    // 发送单条消息的方法
    public async Task EchoSingleMessage(string sender, string receiver, string message)
    {
        // 生成唯一的消息ID
        var messageId = Guid.NewGuid().ToString();
        int retryCount = 0;

        // 启动计时器以检查ACK
        var timer = new Timer(async state =>
        {
            // 如果计时器不在待处理消息字典中,则直接返回
            if (!_pendingMessages.ContainsKey(messageId))
            {
                return;
            }

            // 如果在10秒内没有收到ACK,重新发送消息
            if (retryCount < maxRetries)
            {
                //发送信息
                await WebsocketLib.SendMessage(sender, receiver, message,messageId);
                
                retryCount++;
            }
            else
            {   // 重传失败需要记录到数据库
                WebsocketLib.logFailMessgae(sender, receiver, messagemessageId);
                // 取消计时器
                _pendingMessages[messageId].Change(-1, 0);
                // 从待处理消息字典中移除计时器
                _pendingMessages.Remove(messageId);
            }
        }, null, TimeSpan.FromSeconds(10), TimeSpan.Zero);

        // 将计时器添加到待处理消息字典中
        _pendingMessages[messageId] = timer;
    }

    // 接收ACK消息的方法
    public async Task AcknowledgeMessage(string ack)
    {
        // 从ACK消息中提取消息ID
        var parts = ackMessage.Split(':');
        var messageId = parts[0];

        // 如果消息ID存在于待处理消息字典中
        if (_pendingMessages.ContainsKey(messageId))
        {
            // 取消计时器
            _pendingMessages[messageId].Change(-1, 0);
            // 从待处理消息字典中移除计时器
            _pendingMessages.Remove(messageId);
            Console.WriteLine($"Received ACK for message: {ackMessage}");
        }
    }
}


Clients

客户端使用SignalR库连接到服务端,并处理接收到的消息。以下是关键部分的代码实现:

    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chat")
            .build();


        connection.on("receiveMessage", (message) => {
            console.log(`Received Message: ${message}`);
            
            if(message.messageId){
            //回传messageId
            setTimeout(() => {
                connection.invoke("AcknowledgeMessage", `${messageId}:ACK`).catch(err => console.error(err));
            }, 5000); 
            }
        });

        async function start() {
            try {
                await connection.start();
                console.log("SignalR Connected.");
            } catch (err) {
                console.error(err.toString());
            }
        }

        connection.onclose(async () => {
            await start();
        });

        start();
    </script>

希望对大家有所帮助。