zoukankan      html  css  js  c++  java
  • Asp.net MVC企业级开发(04)---SignalR消息推送

    Asp.net SignalR是微软为实现实时通信而开发的一个类库。可以适用于以下场景:

       

    • 聊天室,如在线客服系统,IM系统等
    • 股票价格实时更新
    • 消息的推送服务
    • 游戏中人物位置的实时推送

         

    SignalR可以进行远程分布式实时通信,都是使用远程代理来实现,其中有两大内部对象,第一个是Persisten Connection,用于客户端和服务器端的持久连接,第二个是Hub(集线器)对象,主要用于信息交互,将服务器端的数据推送(push)至客户端,大致原理如下:

    1. 客户端建立与服务器端的连接
    2. 客户端调用服务器端的方法
    3. 服务器端通过客户端发送的请求,响应数据,调用客户端的方法将数据推送至客户端

    4.1 SignalR的基本使用

    接下来我们通过消息实时推送的案例来学习 SignalR 的使用步骤。具体操作步骤如下:

       

    1. 创建一个应用程序,我这里创建的是MVC应用程序
    2. 在MVC项目的Models文件夹中添加新项 SignalR集线器类。如图所示。

       

    创建完成之后,在应用程序的Scripts文件夹里面会自动生成两个js文件,如图所示:

       

    在创建的Hub类中添加如下代码:

    //hub别名,方便前台调用

    [HubName("getMsg")]

    public class MyHub : Hub

    {

    public void Send(string title,string msg)

    {

    //调用客户端的sendMessage()方法

    Clients.All.sendMessage(title,msg);

    }

    }

    其中Clients.All是dynamic类型,sendMessaage()方法是视图中js定义的function。HubName特性用来给集线器类定义别名。

       

    1. 在项目中添加 OWIN StartUp 类。如图所示。

         

    代码如下:

    public class Startup

    {

    public void Configuration(IAppBuilder app)

    {

    //注册管道,使用默认的虚拟地址,根目录下的"/signalr",

    //当然你也可以自己定义

    app.MapSignalR();

    }

    }

       

    1. 创建控制器和视图

      创建MessageController,并创建两个用于显示视图的action。

    控制器代码:

    public class MessageController : Controller

    {

    //发送消息

    public ActionResult SendMessage()

    {

    return View();

    }

    //接收消息

    public ActionResult ReceiveMessage()

    {

    return View();

    }

    }

       

    发送消息视图代码:

    <h2>发送消息</h2>

    <div>

    标题:<input type="text" id="title" />

    </div><br />

    <div>

    内容:<textarea id="message" rows="4" cols="30"></textarea>

    </div>

    <br />

    <div>

    <input type="button" id="sendmessage" value="发送" />

    </div>

    <script src="~/Scripts/jquery.signalR-2.2.2.js"></script>

    <!--引用自动生成的SignalR 集线器(Hub)脚本.在运行的时候在浏览器的Source下可看到 -->

    <script src="~/signalr/hubs"></script>

    <script type="text/javascript">

    $(function () {

    // 引用自动生成的集线器代理(此处使用别名getMsg)

    var chat = $.connection.getMsg;

    // 集成器连接开始

    $.connection.hub.start().done(function () {

    // 服务连接完成,给发送按钮注册单击事件

    $('#sendmessage').click(function () {

    // 调用服务器端集线器的Send方法

    chat.server.send($("#title").val(), $('#message').val());

    });

    });

    });

    </script>

      

    接收消息视图代码:

    <h2>接收消息</h2>

    <div>

    <br />

    <div id="msgcontent"></div>

    </div>

    <script src="~/Scripts/jquery.signalR-2.2.2.js"></script>

    <script src="~/signalr/hubs"></script>

    <script type="text/javascript">

    $(function () {

    // 引用自动生成的集线器代理

    var chat = $.connection.getMsg;

    // 定义服务器端调用的客户端sendMessage来显示新消息

    chat.client.sendMessage = function (title, message) {

    // 向页面发送接收的消息

    var html = "<div>标题:" + title + "消息内容:" + message + "</div>";

    $("#msgcontent").after(html);

    };

    // 集成器连接开始

    $.connection.hub.start();

    });

    </script>

      

       

    运行程序的时候,页面就与SignalR的服务建立了连接,具体的建立连接的代码就是:$.connection.hub.start()。这句代码的作用就是与SignalR服务建立连接,后面的done函数表明建立连接成功后为发送按钮注册了一个click事件,当客户端输入内容点击发送按钮后,该Click事件将会触发,触发执行的操作为: chat.server.send($("#title").val(), $('#message').val());。这句代码表示调用服务端的send函数,而服务端的Send方法又调用所有客户端的sendMessage函数,而客户端中sendMessage函数就是将信息添加到对应的消息列表中。这样就实现了广播消息的功能了。

       

    在服务端声明的所有Hub信息,都会生成JavaScript输出到客户端,为了验证这一点,可以在Chrome中F12来查看源码就明白了,具体如下图所示:

       

    看到上图,也就明白了为什么页面需要引入"signalr/hubs"脚本库了。

    <!--引用SignalR库. -->

    <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>

    <!--引用自动生成的SignalR 集线器(Hub)脚本 -->

    <script src="~/signalr/hubs"></script>

       

    4.2 使用SignalR实现点对点聊天

       

    介绍了群发消息的实现,下面来学习如何实现像QQ一样的点对点聊天。

    Clients.All.sendMessage(title, message)表示调用所有客户端的SendMessage方法。除了All属性外,还具有其他属性,可以在VS中按F12来查看Clients对象的所有属性或方法,具体的定义如下:

    public interface IHubConnectionContext<T>

    {

    T All { get; } // 代表所有客户端

    T AllExcept(params string[] excludeConnectionIds); //除了参数中的所有客户端

    T Client(string connectionId); // 特定的客户端,实现端对端聊天的关键

    T Clients(IList<string> connectionIds); // 参数中的客户端

    T Group(string groupName, params string[] excludeConnectionIds); // 指定客户端组,可以实现群聊

    T Groups(IList<string> groupNames, params string[] excludeConnectionIds);

    T User(string userId); // 特定的用户

    T Users(IList<string> userIds); // 参数中的用户

    }

       

      SignalR会每一个客户端分配一个ConnnectionId,这样我们就可以通过ConnnectionId来找到特定的客户端了。我们在向某个客户端发送消息的时候,除了要将消息传入,也需要将发送给对方的ConnectionId输入,这样服务端就能根据传入的ConnectionId来转发对应的消息给对应的客户端了。这样也就完成了端对端聊天的功能。另外,如果用户如果不在线的话,服务端可以把消息保存到数据库中,等对应的客户端上线的时候,再从数据库中查看该客户端是否有消息需要推送,有的话,从数据库取出数据,将该数据推送给该客户端。

       

    下面我们来梳理下端对端聊天功能的实现思路:

    1. 客户端登入的时候记录下客户端的ConnnectionId,并将用户加入到一个静态数组中,该数据为了记录所有在线用户。
    2. 用户可以点击在线用户中的用户聊天,在发送消息的时候,需要将ConnectionId一并传入到服务端。
    3. 服务端根据传入的消息内容和ConnectionId调用Clients.Client(connnection).sendMessage方法来进行转发到对应的客户端。

       

    根据上面的思路,先来实现集线器中的代码:

    [HubName("Chat")]

    public class ChatHub : Hub

    {

    // 静态属性,在线用户列表

    public static List<UserInfo> OnlineUsers = new List<UserInfo>();

    //登录

    public void Login(string userId,string userName)

    {

    //获取客户端的ConnectionId

    var connnectId = Context.ConnectionId;

    OnlineUsers.Add(new UserInfo

    {

    ConnectionId = connnectId,

    UserId = userId,

    UserName = userName

    });

    // 所有客户端同步在线用户

    Clients.All.loadUser(OnlineUsers);

    }

       

    /// <summary>

    /// 发送私聊

    /// </summary>

    /// <param name="toUserId">接收方用户连接ID</param>

    /// <param name="message">内容</param>

    public void SendPrivateMessage(string toUserId, string message)

    {

    var fromUserId = Context.ConnectionId;

       

    var toUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == toUserId);

    var fromUser = OnlineUsers.FirstOrDefault(x => x.ConnectionId == fromUserId);

       

    if (toUser != null && fromUser != null)

    {

    // 调用指定用户的客户端方法

    Clients.Client(toUserId).receivePrivateMessage(fromUser.UserName, message);

    }

    else

    {

    //表示对方不在线

    Clients.Caller.absentSubscriber();

    }

    }

       

    /// <summary>

    /// 断线时调用

    /// </summary>

    /// <param name="stopCalled"></param>

    /// <returns></returns>

    public override Task OnDisconnected(bool stopCalled)

    {

    var user = OnlineUsers.FirstOrDefault(u => u.ConnectionId == Context.ConnectionId);

       

    // 判断用户是否存在,存在则删除

    if (user == null) return base.OnDisconnected(stopCalled);

       

    Clients.All.onUserDisconnected(user.ConnectionId, user.UserName); //调用客户端用户离线通知

    // 删除用户

    OnlineUsers.Remove(user);

    return base.OnDisconnected(stopCalled);

    }

    }

       

    客户端代码:

    <h2>聊天系统</h2>

    <div class="container">

    @using (Html.BeginForm("login", "Chat", FormMethod.Post, new { @class = "form-inline" }))

    {

    <label>用户Id:</label>

    @Html.TextBox("userId", "", new { @class = "form-control" })

    <label>用户名:</label>

    @Html.TextBox("userName", "", new { @class = "form-control" })

    <input type="button" id="btnLogin" value="登录" class="btn btn-default" />

    }

    </div>

    <hr />

    <div class="container">

    <div class="col-md-3">

    <div class="panel panel-default">

    <div class="panel-heading">

    在线用户

    </div>

    <div class="panel-body">

    <ul id="userList">

    </ul>

    </div>

    </div>

    </div>

    <div class="col-md-8">

    <div class="panel panel-default">

    <div class="panel-heading">

    聊天内容

    </div>

    <div class="panel-body">

    <ul id="msgList" >

    </ul>

    </div>

    <div class="panel-footer">

    <label>消息To:</label> <label id="toUser"></label>

    @Html.TextBox("msg", "", new { @class = "form-control form-inline" })

    <input type="button" id="btnSend" value="发送" class="btn btn-default" />

    </div>

    </div>

    </div>

    </div>

       

    @section scripts{

    <script src="~/Scripts/jquery.signalR-2.2.2.min.js"></script>

    <script src="~/signalr/hubs"></script>

    <script>

    //用户点击(选择用户)

    function selectUser(li) {

    var connectionId = $(li).attr("cid");

    $('#toUser').text(connectionId);

    }

    $(function () {

    var chat = $.connection.Chat;

       

    //刷新在线列表

    chat.client.loadUser = function (allUsers) {

    $('#userList').html("");

    for (var i = 0; i < allUsers.length; i++) {

    var li = $('<li cid="' + allUsers[i].ConnectionId+'" onclick="selectUser(this)">' + allUsers[i].UserId + ':' + allUsers[i].UserName + '</li> ');

    $('#userList').append(li);

    }

    }

    //接收消息

    chat.client.receivePrivateMessage = function (from, msg) {

    var li = $("<li>来自:" + from + "<br/>" + msg + "</li>");

    $("#msgList").append(li);

    }

       

    $.connection.hub.start().done(function () {

    console.log("连接完成");

    //登录

    $('#btnLogin').click(function () {

    chat.server.login($("#userId").val(), $('#userName').val())

    })

       

    $('#btnSend').click(function () {

    chat.server.sendPrivateMessage($('#toUser').text(), $('#msg').val())

    })

    });

    })

    </script>

       

       

       

       

  • 相关阅读:
    Java内存模型(JMM)是什么?JMM 通过控制主内存与每个线程的本地内存之间的交互,来提供内存可见性保证
    【普及组_在线赛】班级聚会(reuntion)
    面试官:你对Redis缓存了解吗?面对这11道面试题是否有很多问号?
    【华为云技术分享】浅谈产品模型(Profile)在程序设计中的作用
    【华为云技术分享】LiteAI四大绝招,解锁物联网智能设备AI开发难关
    【华为云技术分享】漫谈Huawei LiteOS五大内核模块
    科技感满满,华为云DevCloud推出网页暗黑模式
    赶在520之前,程序员如何用Python送上最特别的“我爱你”表白
    【华为云技术分享】从部署和运维说说DLI(1)
    【2017.11.25普及组模拟】The Farthest House题解
  • 原文地址:https://www.cnblogs.com/mrfang/p/11049385.html
Copyright © 2011-2022 走看看