記錄一次SignalR服務端實現過程


前言:最近手上一個項目需要后端實時推送數據到前端,第一個想到的就是微軟的SignalR,由於之前都是平時沒事寫的Demo,沒有用到實際項目中,這次恰好用到了,因此記錄下來整個實現過程(網上也有很多類似的教程,寫的不好,請指正出來)

本文源碼下載:https://download.csdn.net/download/baidu_24578765/10490052

一、項目准備

  1、新建一個MVC空項目(過程不在贅述)

  2、添加SignalR引用,通過Nuget安裝SignalR包。右鍵引用-》選擇管理Nuget程序包-》在出現的窗口中輸入SignalR來找到SignalR包進行安裝

  3、安裝SignalR成功后,SignalR庫的腳本將被添加進Scripts文件夾下。具體如下圖所示:

 

  4、安裝Microsoft.Owin以及Microsoft.Owin.Cors,安裝方式同前

二、項目構建

  首先在Models文件夾下面新建一個SignalR集線器(v2)並命名為ChatsHub

然后繼續新建接口IChatClient以及IChatService,在ChatsHub.cs中添加以下代碼:

[HubName("ChatsHub")]
    public class ChatsHub : Hub<IChatClient>, IChatService
    {
        #region 基礎信息
        /// <summary>
        /// 前端自定義參數集合
        /// </summary>
        public INameValueCollection ClientQueryString
        {
            get { return Context.QueryString; }
        }

        /// <summary>
        /// Cookie
        /// </summary>
        public IDictionary<string, Cookie> ClientCookies
        {
            get { return Context.RequestCookies; }
        }

        /// <summary>
        /// 用戶信息
        /// </summary>
        public IPrincipal ClientContextUser
        {
            get { return Context.User; }
        }

        /// <summary>
        /// SignalR上下文
        /// </summary>
        public HttpContext HttpContext
        {
            get { return HttpContext.Current; }
        }

        #endregion

        #region 測試代碼

        /// <summary>
        /// 向所有客戶端發送消息
        /// </summary>
        /// <param name="message"></param>
        public async Task Send(string message)
        {
            try
            {
                //當前發送消息的用戶ID,前端自定義
                //string userId = ClientQueryString["userId"];
                //當前連接ID
                string connId = Context.ConnectionId;

                //調用所有客戶端的SendMessage方法
                ChatMessageDTO msg = new ChatMessageDTO
                {
                    SendId = connId,
                    SendUserName = "",
                    Content = message,
                    CreateDate = DateTime.Now
                };
                await Clients.All.SendMessage(msg);
            }
            catch (Exception e)
            {
                throw new HubException("發送消息發生異常.", new { userName = ClientContextUser.Identity.Name, message = e.Message });
            }
        }

        #endregion

        #region 默認事件

        /// <summary>
        /// 客戶端連接的時候調用
        /// </summary>
        /// <returns></returns>
        public override Task OnConnected()
        {
            //string userId = ClientQueryString["userId"];

            Trace.WriteLine("客戶端連接成功,連接ID是: " + Context.ConnectionId);
            return base.OnConnected();
        }

        /// <summary>
        /// 客戶端斷開連接的時候調用
        /// </summary>
        /// <param name="stopCalled"></param>
        /// <returns></returns>
        public override Task OnDisconnected(bool stopCalled)
        {
            Trace.WriteLine($"客戶端[{Context.ConnectionId}]斷開連接");
            return base.OnDisconnected(true);
        }

        /// <summary>
        /// 客戶端重新連接的時候調用
        /// </summary>
        /// <returns></returns>
        public override Task OnReconnected()
        {
            Trace.WriteLine($"客戶端[{Context.ConnectionId}]正在重新連接");

            return base.OnReconnected();
        }
        #endregion
    }

IChatClient.cs代碼:

/// <summary>
    /// 客戶端方法
    /// </summary>
    public interface IChatClient
    {
        #region 用於測試
        /// <summary>
        /// 測試方法
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        Task SendMessage(ChatMessageDTO message); 
        #endregion

    }

IChatService.cs代碼

/// <summary>
    /// 服務端方法
    /// </summary>
    public interface IChatService
    {
        #region 基礎信息
        /// <summary>
        /// 前端自定義參數集合
        /// </summary>
        INameValueCollection ClientQueryString { get; }
        /// <summary>
        /// Cookie
        /// </summary>
        IDictionary<string, Cookie> ClientCookies { get; }
        /// <summary>
        /// 用戶信息
        /// </summary>
        IPrincipal ClientContextUser { get; }
        /// <summary>
        /// SignalR上下文
        /// </summary>
        HttpContext HttpContext { get; }
        #endregion

        #region 用於測試
        /// <summary>
        /// 發送消息,測試方法
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        Task Send(string message); 
        #endregion
    }

至此,一個基礎的Hub就建立好了,然后繼續在App_Start中新建Startup.cs,如下圖:

在Startup中添加以下代碼:

[assembly: OwinStartup(typeof(SignalRService.Startup))]
namespace SignalRService
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //異常處理
            GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule());

            //自定義管道
            GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());

            //允許跨域推送
            app.UseCors(CorsOptions.AllowAll);

            app.MapSignalR();
        }
    }
}

添加自定義異常處理機制,新建一個ErrorHandlingPipelineModule類,這里只做了簡單的實現,具體的可以根據你自己的需求進行處理:

/// <summary>
    /// 自定義異常處理
    /// </summary>
    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
        {
            Trace.WriteLine("=> Exception " + exceptionContext.Error.Message);
            if (exceptionContext.Error.InnerException != null)
            {
                Trace.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message);
            }
            base.OnIncomingError(exceptionContext, invokerContext);
        }
    }

新增日志處理管道LoggingPipelineModule類:

public class LoggingPipelineModule : HubPipelineModule
    {
        protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
        {
            Trace.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name);
            return base.OnBeforeIncoming(context);
        }
        protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context)
        {
            Trace.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub);
            return base.OnBeforeOutgoing(context);
        }
    }

上述類建立好了以后,結構如下:

 

實體類ChatMessageDTO.cs代碼:

public class ChatMessageDTO
    {
        /// <summary>
        /// 發送人ID
        /// </summary>
        public string SendId { get; set; }
        /// <summary>
        /// 發送方姓名
        /// </summary>
        public string SendUserName { get; set; }
        /// <summary>
        /// 內容
        /// </summary>
        public string Content { get; set; }
        /// <summary>
        /// 創建時間
        /// </summary>
        public DateTime CreateDate { get; set; }
    }

 至此,一個簡單的SignalR服務端就搭建好了。接下來就是前端調用服務測試,同樣,新建一個空的MVC項目,把剛剛安裝SignalR時自動添加的jquery.signalR-2.2.3.min.js拷貝到現在項目中,前端實現代碼:

添加一個輸入框:

<div class="container">
    <input type="text" id="message" />
    <input type="button" id="sendmessage" value="發送消息" />
    <ul id="discussion"></ul>
</div>

JS代碼:

<!--引用SignalR庫. -->
    <script src="~/Scripts/jquery.signalR-2.2.3.min.js"></script>
    <!--引用自動生成的SignalR 集線器(Hub)腳本.在運行的時候在瀏覽器的Source下可看到 -->
    <script src="http://你的地址/signalr/hubs"></script>
    <script>
        $(function () {
            // 引用自動生成的集線器代理
            var chat = $.connection.ChatsHub;
            $.connection.hub.url = 'http://你的地址/signalr/hubs';
            //自定義用戶ID
            $.connection.hub.qs = { "userId": "110" };

            //啟用瀏覽器端輸出日志
            //$.connection.hub.logging = true;

            // 定義服務器端調用的客戶端SendMessage來顯示新消息
            chat.client.SendMessage = function (req) {
                // 向頁面添加消息
                $('#discussion').append('<li><strong>' + htmlEncode(req.SendId) + '</strong>: ' + htmlEncode(req.Content) + '</li>');
            };
            // 設置焦點到輸入框
            $('#message').focus();

            // Start the connection.
            $.connection.hub.starting(function () {
                console.log('SignalR啟動中...')
            });
            $.connection.hub.received(function (e) {
                //console.log(e)
                console.log('SignalR收到消息:\n')
                var msgBody = e.A;
                //console.log(msgBody)
                if (msgBody)
                    console.log("用戶ID:" + msgBody[0].SendId + ",消息內容:" + msgBody[0].Content)
            });
            $.connection.hub.connectionSlow(function () {
                console.log('connectionSlow.')
            });
            $.connection.hub.reconnecting(function () {
                console.log('SignalR正在重新連接中...')
            });
            $.connection.hub.reconnected(function () {
                console.log('SignalR重新連接成功...')
            });
            $.connection.hub.stateChanged(function (o, n) {
                //console.log(o)
                console.log('SignalR狀態改變,newState:' + o.newState + ",oldState:" + o.oldState + "," + n)
            });
            $.connection.hub.disconnected(function () {
                console.log('SignalR斷開連接...');
                setTimeout(function () {
                    $.connection.hub.start();
                }, 1000); // 1秒后重新連接
            });
            //錯誤
            $.connection.hub.error(function (err) {
                console.log("SignalR出現錯誤. \n" + "Error: " + err.message);
            });

            // 開始連接服務器
            $.connection.hub.start().done(function () {
                $('#sendmessage').click(function () {
                    var msg = $('#message').val();
                    // 調用服務器端集線器的Send方法
                    chat.server.send(msg).fail(function (e) {
                        if (e.source === 'HubException') {
                            console.log("異常信息:" + e.message + ",用戶名:" + e.data.userName + ",錯誤描述:" + e.data.message);
                        }
                    });
                    // 清空輸入框信息並獲取焦點
                    $('#message').val('').focus();
                });
            });
        });

        // 為顯示的消息進行Html編碼
        function htmlEncode(value) {
            var encodedValue = $('<div />').text(value).html();
            return encodedValue;
        }
    </script>

在運行前,先啟動服務端,然后修改js代碼中服務器地址,再運行客戶端,效果圖如下:

三、后序

  參考文章:https://docs.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server

       https://www.cnblogs.com/aaaaq/p/5929104.html

  特別提醒:服務端signalR版本必須與客戶端版本一致。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM