zoukankan      html  css  js  c++  java
  • 记录一次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收到消息:
    ')
                    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出现错误. 
    " + "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版本必须与客户端版本一致。

  • 相关阅读:
    百万英雄答题值不值得您继续参加
    一个程序员&自媒体人的2017年终总结
    送书福利又来了,总共10本程序员技术书
    CCF 201912-2 回收站选址(100分)Java
    CCF 201912-1 报数 (100分)Java
    CCF 201909-2 小明种苹果(续)(100分)Java
    CCF 201909-1 小明种苹果(100分)Java
    初识JWT
    Redis学习——事务、消息订阅和发布
    Redis学习——数据类型及操作
  • 原文地址:https://www.cnblogs.com/zhao-yi/p/9204441.html
Copyright © 2011-2022 走看看