MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#实时通信方案(SignalR核心机制剖析)

2021-09-026.6k 阅读

C#实时通信方案(SignalR核心机制剖析)

一、SignalR简介

在当今的互联网应用开发中,实时通信变得越来越重要。无论是在线游戏、实时监控系统,还是协作工具,都需要高效的实时通信功能。C# 作为一种广泛使用的编程语言,在实时通信领域也有出色的解决方案,其中 SignalR 就是一个非常强大的框架。

SignalR 是一个开源的 ASP.NET 库,用于在服务器和客户端之间实现实时双向通信。它抽象了底层的通信细节,使得开发者可以轻松地创建实时应用程序,支持多种客户端技术,如 JavaScript、Silverlight、WPF 和 Windows Store 应用等。SignalR 自动处理连接管理、消息传递和故障恢复等复杂任务,让开发者能够专注于业务逻辑的实现。

二、SignalR核心机制

(一)连接管理
  1. 持久连接 SignalR 最初引入了持久连接的概念,它为服务器和客户端之间提供了一种长期存在的连接。持久连接是一种基于 HTTP 的长连接,通过这种连接,服务器可以主动向客户端发送数据,而不需要客户端不断地发起请求。

以下是一个简单的持久连接示例代码:

首先,创建一个继承自 PersistentConnection 的类:

using Microsoft.AspNet.SignalR;

public class MyPersistentConnection : PersistentConnection
{
    protected override Task OnConnected(IRequest request, string connectionId)
    {
        return Connection.Send(connectionId, "You are now connected!");
    }

    protected override Task OnReceived(IRequest request, string connectionId, string data)
    {
        return Connection.Broadcast(data);
    }

    protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
    {
        return Connection.Send(connectionId, "You have been disconnected.");
    }
}

然后,在 Global.asax.cs 文件中注册持久连接:

using System.Web.Routing;
using Microsoft.AspNet.SignalR;

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        RouteTable.Routes.MapHubs();
        RouteTable.Routes.MapConnection<MyPersistentConnection>("myConnection", "myConnection/{*operation}");
    }
}
  1. Hub 随着 SignalR 的发展,Hub 成为了更常用的连接管理方式。Hub 提供了一种更高级、更抽象的方式来处理实时通信。它允许开发者在服务器端定义方法,客户端可以直接调用这些方法,反之亦然。

创建一个简单的 Hub 类:

using Microsoft.AspNet.SignalR;

public class ChatHub : Hub
{
    public void Send(string name, string message)
    {
        Clients.All.broadcastMessage(name, message);
    }
}

在客户端(以 JavaScript 为例)调用 Hub 方法:

$(function () {
    var chat = $.connection.chatHub;

    chat.client.broadcastMessage = function (name, message) {
        $('#messages').append('<li><strong>' + name + '</strong>: ' + message + '</li>');
    };

    $.connection.hub.start().done(function () {
        $('#sendButton').click(function () {
            var name = $('#nameInput').val();
            var message = $('#messageInput').val();

            chat.server.send(name, message);

            $('#messageInput').val('');
        });
    });
});
(二)消息传递
  1. 广播消息 SignalR 提供了方便的广播机制,使得服务器可以将消息发送给所有连接的客户端。在 Hub 中,通过 Clients.All 可以实现这一功能。例如,在上述 ChatHub 类中,Clients.All.broadcastMessage(name, message); 这行代码将消息广播给所有连接到该 Hub 的客户端。

  2. 发送给特定客户端 除了广播消息,还可以将消息发送给特定的客户端。在 Hub 中,可以通过客户端的 connectionId 来指定目标客户端。例如:

public void SendPrivateMessage(string toConnectionId, string message)
{
    Clients.Client(toConnectionId).receivePrivateMessage(message);
}

在客户端,可以在连接建立时获取自己的 connectionId

$.connection.hub.start().done(function () {
    var connectionId = $.connection.hub.id;
});
  1. 分组发送 SignalR 支持将客户端分组,然后向特定的组发送消息。这在很多场景下非常有用,比如多人在线游戏中的不同队伍,或者实时监控系统中的不同设备组等。

在 Hub 中管理组:

public void JoinGroup(string groupName)
{
    Groups.Add(Context.ConnectionId, groupName);
}

public void SendGroupMessage(string groupName, string message)
{
    Clients.Group(groupName).receiveGroupMessage(message);
}

在客户端加入组:

chat.server.joinGroup('myGroup');
(三)传输协议
  1. WebSockets WebSockets 是一种在单个 TCP 连接上进行全双工通信的协议。SignalR 优先使用 WebSockets 作为传输协议,因为它具有低延迟、高效的特点,非常适合实时通信。当客户端和服务器都支持 WebSockets 时,SignalR 会自动使用它。

  2. Server-Sent Events (SSE) 如果客户端不支持 WebSockets,SignalR 可以退而使用 Server-Sent Events(SSE)。SSE 是一种单向的服务器推送技术,适用于只需要服务器向客户端发送数据的场景。它基于 HTTP 协议,通过保持连接打开来实现实时数据传输。

  3. 长轮询 长轮询是另一种备用的传输方式。在长轮询中,客户端向服务器发送请求,服务器保持连接打开,直到有数据可用或者连接超时。当有数据时,服务器将数据发送给客户端,客户端处理完数据后立即发起新的请求。这种方式虽然不如 WebSockets 和 SSE 高效,但兼容性更好,适用于不支持高级传输协议的环境。

SignalR 会自动根据客户端和服务器的能力以及网络环境选择最合适的传输协议,开发者无需手动处理协议切换的细节。

三、SignalR在实际项目中的应用

(一)实时监控系统

假设我们要开发一个实时监控系统,用于监控服务器的性能指标,如 CPU 使用率、内存使用率等。

  1. 服务器端实现 首先,创建一个 Hub 类来处理监控数据的推送:
using Microsoft.AspNet.SignalR;
using System.Timers;

public class MonitorHub : Hub
{
    private Timer _timer;

    public MonitorHub()
    {
        _timer = new Timer(5000);
        _timer.Elapsed += (sender, e) =>
        {
            var cpuUsage = GetCPUUsage();
            var memoryUsage = GetMemoryUsage();
            Clients.All.updateMonitorData(cpuUsage, memoryUsage);
        };
        _timer.Start();
    }

    private double GetCPUUsage()
    {
        // 实际获取 CPU 使用率的逻辑
        return new Random().NextDouble() * 100;
    }

    private double GetMemoryUsage()
    {
        // 实际获取内存使用率的逻辑
        return new Random().NextDouble() * 100;
    }
}
  1. 客户端实现 在客户端(以 HTML + JavaScript 为例),接收并展示监控数据:
<!DOCTYPE html>
<html>

<head>
    <title>Real - Time Monitor</title>
    <script src="Scripts/jquery-3.6.0.min.js"></script>
    <script src="Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
        $(function () {
            var monitor = $.connection.monitorHub;

            monitor.client.updateMonitorData = function (cpuUsage, memoryUsage) {
                $('#cpuUsage').text(cpuUsage.toFixed(2) + '%');
                $('#memoryUsage').text(memoryUsage.toFixed(2) + '%');
            };

            $.connection.hub.start().done(function () {
                console.log('Connected to monitor hub.');
            });
        });
    </script>
</head>

<body>
    <h1>Real - Time Server Monitor</h1>
    <p>CPU Usage: <span id="cpuUsage"></span></p>
    <p>Memory Usage: <span id="memoryUsage"></span></p>
</body>

</html>
(二)多人在线协作工具

以一个简单的多人在线文本编辑工具为例。

  1. 服务器端实现 创建一个 Hub 类来处理文本编辑的协作逻辑:
using Microsoft.AspNet.SignalR;
using System.Collections.Generic;

public class EditorHub : Hub
{
    private static List<string> _textLines = new List<string>();

    public void SendEdit(string line, int index)
    {
        if (index < 0 || index >= _textLines.Count)
        {
            if (index == _textLines.Count)
            {
                _textLines.Add(line);
            }
        }
        else
        {
            _textLines[index] = line;
        }
        Clients.Others.updateText(_textLines);
    }

    public List<string> GetInitialText()
    {
        return _textLines;
    }
}
  1. 客户端实现 在客户端(以 JavaScript 为例),实现文本编辑和同步功能:
<!DOCTYPE html>
<html>

<head>
    <title>Collaborative Text Editor</title>
    <script src="Scripts/jquery-3.6.0.min.js"></script>
    <script src="Scripts/jquery.signalR-2.4.1.min.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
        $(function () {
            var editor = $.connection.editorHub;

            editor.client.updateText = function (textLines) {
                $('#editor').empty();
                textLines.forEach(function (line, index) {
                    var input = $('<input type="text" value="' + line + '">');
                    input.on('change', function () {
                        editor.server.sendEdit($(this).val(), index);
                    });
                    $('#editor').append(input);
                });
            };

            $.connection.hub.start().done(function () {
                editor.server.getInitialText().done(function (textLines) {
                    editor.client.updateText(textLines);
                });
            });
        });
    </script>
</head>

<body>
    <h1>Collaborative Text Editor</h1>
    <div id="editor"></div>
</body>

</html>

四、SignalR的性能优化与注意事项

(一)性能优化
  1. 减少不必要的消息发送 在实时通信中,频繁地发送消息可能会导致网络拥塞和性能下降。因此,应该尽量减少不必要的消息发送。例如,在监控系统中,可以设置合理的监控数据更新频率,避免过于频繁地推送数据。

  2. 优化分组管理 如果使用了分组功能,要合理管理组的成员。避免将过多的客户端放入同一个组,以免在向组发送消息时产生性能问题。同时,要及时处理客户端加入和离开组的操作,确保组的状态始终保持正确。

  3. 使用压缩 SignalR 支持消息压缩,可以通过配置启用。压缩可以减少数据在网络上传输的大小,提高传输效率。在 Startup.cs 文件中,可以这样配置:

using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR;

[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            var hubConfiguration = new HubConfiguration
            {
                EnableDetailedErrors = true,
                EnableJSONP = true,
                EnableJavaScriptProxies = true,
                EnableCompression = true
            };
            app.MapSignalR(hubConfiguration);
        }
    }
}
(二)注意事项
  1. 连接生命周期管理 要注意处理连接的建立、断开等生命周期事件。在连接断开时,可能需要清理相关的资源,如定时器、缓存数据等。同时,对于重连机制也要有合理的设计,确保客户端在网络故障恢复后能够顺利重新连接到服务器。

  2. 安全性 实时通信系统往往涉及到敏感数据的传输,因此安全性至关重要。要确保对连接进行身份验证和授权,防止未经授权的访问。可以使用 ASP.NET Identity 等框架来实现身份验证和授权功能。同时,要对传输的数据进行加密,防止数据被窃取或篡改。

  3. 跨域问题 在实际应用中,客户端和服务器可能位于不同的域。这时,需要处理跨域问题。SignalR 支持跨域请求,可以通过配置来允许特定的域访问。在 Startup.cs 文件中,可以使用 Microsoft.Owin.Cors 中间件来配置跨域支持:

using Microsoft.Owin;
using Owin;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;

[assembly: OwinStartup(typeof(MyApp.Startup))]
namespace MyApp
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCors(CorsOptions.AllowAll);
            var hubConfiguration = new HubConfiguration();
            app.MapSignalR(hubConfiguration);
        }
    }
}

通过深入理解 SignalR 的核心机制,并在实际项目中合理应用和优化,开发者可以利用 C# 构建出高效、稳定的实时通信应用程序,满足各种复杂的业务需求。无论是小型的实时监控工具,还是大型的多人在线协作平台,SignalR 都能为开发者提供强大的支持。