管理SignalR通知以同步客户端和服务器(C#)

7
在我的网页应用程序中,我希望在上电时从服务器端将所有数据加载到客户端。之后,我希望所有通信都通过Signalr进行管理——这意味着每次更新时,服务器将向所有客户端发送通知并要求它们请求更新的数据。
然而,当SingalR连接中断并重新连接时,我不知道该怎么做。我不想再重新加载所有数据。我想做的是在服务器端为每个断开连接的客户端实现一些通知管理,并在SignalR连接恢复时,将错过的所有通知推送给具体的客户端。
我们在客户端使用单例监听器代替短暂的控制器作为SignalR侦听器,以便防止在每个视图更改时进行GET请求,并使应用程序更快速、更用户友好。因此,即使对于最终用户当前不相关的当前视图,后台中的新通知也会得到处理和处理,如下所示:
// This service is initialized once only
public class Service1 {
        static inject = ['$rootScope']
    array : Item[];

    // This is a singleton!
    public constructor ($rootScope){

        // Get all items from the server
        GetAllItemsFromServer();

        // Listener for signalR updates
        var listener = $rootScope.$on("ItemsNotificationFromServer", UpdateItems);

        $rootScope.$on('destroy', {
            // Stop the listener
            listener();
        })
    }   

    // Getting all the items from the server on each controller creation
    GetAllItemsFromServer(){
        // Getting the items
    }

    // Handle the notification from the server
    public UpdateItems(event, result) : void 
        //..
    }
}

目前的情况是,例如当终端用户刷新浏览器(F5)时,我无法知道此客户端在连接问题期间错过了哪些SignalR通知,因此我必须重新从服务器加载所有数据(很糟糕)。

为了防止这种情况发生,我考虑实现类似以下的内容 -

namespace MapUsersSample
{
    public class UserContext : DbContext
    {
        // All those are cleaned when server is powered up
        public DbSet<Connection> Connections { get; set; }
        public DbSet<Notification> Notifications {get; set;}
    }

    public class Connection
    {
        [Key]
        [DatabaseGenerationOptions.None]
        public string ConnectionID { get; set; }
        public bool Connected { get; set; }

        // I fill this when disconnected
        public List<Notification> MissedNotifications {get; set;}

        public Connection(string id)
        {
            this.ConnectionID = id;
            this.Connected = true;
            this.MissedNotifications = new List<Notification>();
        }
    }

    public abstract class Notification()
    {
        public int Id {get; set;}
        public DateTime CreationTime {get; set;}
    }

    .. // Many notifications implement this
}

public class MyHub : Hub
{
    private readonly DbContext _db;
    public class MyHub(DbContext db)
    {
        this._db = db;
    }

    // Adding a new connection or updating status to true
    public override Task OnConnected()
    {
        var connection = GetConnection(Context.ConnectionId);

        if (connection == null)
            _db.Connections.Add(new Connection(Context.ConnectionId));
        else 
            connection.Connected = true;

        return base.OnConnected()
    }

    // Changing connection status to false
    public override Task OnDisconnected()
    {
        var connection = GetConnection(Context.ConnectionId);

        if (connection == null)
        {
            Log("Disconnect error: failed to find a connection with id : " + Context.ConnectionId);
            return;
        }
        else {
            connection.Connected = false;
        }
        return base.OnDisconnected();
    }

    public override Task OnReconnected()
    {
       var connection = GetConnection(Context.ConnectionId);

        if (connection == null)
        {
            Log("Reconnect error - failed to find a connection with id : " + Context.ConnectionId);
            return;
        }
        else {
            connection.Connected = true;
        }

        // On reconnect, trying to send to the client all the notifications that he has missed
        foreach (var notification in connection.MissedNotifications){
            Clients.Client(connection.ConnectionID).handleNotification(notification);
        }

        return base.OnReconnected();
    }

    // This method is called from clients that receive a notification
    public clientNotified(int connectionId, int notificationId)
    {
        // Getting the connection
        var connection = GetConnection(connectionId);

        if (connection == null){
            Log("clientNotified error - failed to find a connection with id : " + Context.ConnectionId);
            return;
        }

        // Getting the notification that the client was notified about
        var notificationToRemove = _dbConnection.Notifications.FirstOrDefault(n => n.Id == notificationId);

        if (notificationToRemove == null)
        {
            Log("clientNotified error - failed to find notification with id : " + notificationId);
            return;
        }

        // Removing from the missed notifications
        connection.MissedNotifications.Remove(notificationToRemove);
    }

    private Connection GetConnection(int connectionId) 
    {
        return _db.Connections.find(connectionId);
    }


}

// Notifications outside of the hub
public class Broadcaster
{
    DbContext _db;
    public Broadcaster(DbContext db)
    {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
        _dbConnection = db;
    }

    public void NotifyClients(Notification notification)
    {
        var openConnections = _db.Connections.Where(x => x.Connected);
        var closedConnections = _db.Connections.Where(x => !x.Connected);

        // Adding all notifications to be sent when those connections are back
        foreach (var connection in closedConnections){
            connection.MissedNotifications.add(notification);
        }

        // Notifying all open connections
        foreach (var connection in openConnections){
            _hubContext.Clients.Client(connection.ConnectionID).handleNotification(notification);
        }
    }
}


client side java script:

handleNotification(notification){
    hubProxy.Server.clientNotified(hub.connection.id, notification.Id)

    // Keep handling the notification here..
}

我还没有测试过这种方法,但在向我的团队提出这个想法之前,这种方法是否很流行呢?我没有看到有人采取这种方法,我想知道为什么?这里是否存在任何风险?


在我看来,如果最终用户刷新页面,由JS构建的整个堡垒,包括调用客户端集合、SignalR连接对象都会丢失。在这种情况下,如果你想要数据,就需要重新获取所有数据,否则你只能得到增量数据。 - Sayan Pal
你考虑过像RabbitMQ这样的队列吗? - Vignesh.N
一旦套接字连接丢失,除非您维护某些状态,否则无法知道是否重新连接了相同的客户端,您还必须小心并将正确的数据推送给正确的用户。我建议将任何新请求视为新请求。 - Vignesh.N
2个回答

1
目前的情况是,例如当终端用户刷新浏览器(F5)时,我无法知道此客户端在连接问题期间错过了哪些SignalR通知,因此我必须从服务器重新加载所有数据(很糟糕)。
按下F5键刷新浏览器相当于硬重置,所有现有的SignalR连接都将丢失。新连接将被建立以获取数据。连接问题发生在SignalR注意到http连接存在问题,例如由于临时网络问题。浏览器刷新不是连接问题,而是用户有意重新创建一个新连接的行为。
因此,您处理错过通知的代码只适用于SignalR连接问题。我认为它不适用于浏览器刷新,但这是一个新连接,因此您没有错过任何内容。

1
你应该检查数据是否实际存在。它可以是哈希值或最后更改的日期时间。
当客户端重新连接时,你应该向客户端发送实际数据哈希值或最后更改的日期时间。
例如:
{
clients: '2016-05-05T09:05:05',
orders: '2016-09-20T10:11:11'
} 

客户端应用程序将决定需要更新哪些数据。
在客户端,您可以将数据保存到本地存储或会话存储中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接