MVC SignalR Hub实时聊天实时Web解决方案

635 篇文章 16 订阅
82 篇文章 3 订阅

目录

介绍

背景

1. WebSocket

2.服务器发送事件(SSE)

3.永远的Frame

4.轮询

5.长轮询

场景描述

先决条件

使用代码

第1步:创建项目

步骤2:打开PM以安装依赖项文件

第3步:删除旧依赖关系的指令

第4步:安装必要的依赖项文件的说明

第5步:启动类

第6步:根据我们的场景组织数据库

第7步:创建Hub类

步骤8:创建文件夹并将其命名为Hubs然后创建简单类并将其命名为“MyHub.cs”

规则和约定

提示


介绍

如今,由于信息量的增加和实时实现数据的必要性,我们需要技术来满足我们在这个问题上的要求。假设股票市场价格在每个时刻都在变化,您认为用户应该每时每刻刷新页面以告知最后价格吗?显然,对于这样的问题,这不是一个合理的解决方案。或者随着产品和服务的增加,我们需要客户服务来帮助用户和买家,最好和更便宜的沟通方式是通过聊天程序进行对话。出于同样的原因,我们不能强迫用户按下按钮来接收我们的上一条消息。

SignalR是一种实时技术,它使用异步库集在客户端和服务器之间建立持久连接。用户无需使用传统方式即可从服务器接收上次更新的数据,如刷新页面或按下按钮。

背景

您需要了解MVC 4.0技术和EntityFramework> 4.0才能更好地完成本文。

另一方面,SignalR使用以下方法建立实时网络:

1. WebSocket

Websocket是一种全双工协议,在内部使用http握手,允许消息流在TCP之上流动。它支持:Google Chrome> 16Fire Fox> 11IE> 10Win IIS> 8.0)。由于加密消息和全双工,websocket是最好的解决方案,并且首先signalR检查Web服务器和客户端服务器是否支持websocket

单工通讯

它只是以一种方式传播,当一个点只是广播而另一个点只能听而不发送信息,如电视和广播。

半双工

一个点发送消息,并且在这一瞬间的另一点无法发送消息,并且应该等待,直到第一点完成其传输,然后发送它的消息,它仅仅是一个每次一个通信线路,诸如旧无线设备对讲机和HTTP协议。

全双工

这两个点可以同时发送和接收消息,不需要等到其他点完成其传输,如电话和websocket协议。

全双工

2.服务器发送事件(SSE

signalr的下一个选择是服务器发送事件,因为服务器和客户端之间的持久性通信。在这种方法中,通信不会断开连接,服务器的最后数据将自动更新并通过HTTP连接传输到客户端。EventSourceHTML5技术的一部分。

var evsrc = new EventSource("url");
       // Load and Register Event Handler for Messages in this section

       evsrc.addEventListener("message", function (event) {
           //processing data in this section
       });

3.永远的Frame

当客户端向服务器发送请求时,服务器将一个隐藏的iframe作为chunked块发送给客户端,因此这个iframe负责永久保持客户端和服务器之间的连接。每当服务器更改数据,然后将数据作为脚本标记发送到客户端(隐藏的iframe),这些脚本将按顺序接收。

4.轮询

客户端立即向服务器发送请求和服务器响应发送请求,但在那之后,服务器再次断开连接以便在服务器和客户端之间建立通信,我们应该等待来自客户端的下一个请求。要解决此问题,我们必须手动设置超时,并且每10秒客户端向服务器发送请求以检查服务器端的新修改并获取上次更新数据。轮询使用资源,并且它不是经济的解决方案。

5.长轮询

客户端向服务器发送请求,服务器立即响应,此连接一直保持到特定时间,在此期间客户端不必向服务器发送显式请求,而在轮询中客户端必须在超时期间向服务器发送显式请求。Comet编程涵盖了这一概念。

简单的说,SignalR库在客户端和服务器之间选择一种传输数据的类型,其优先级是websocket,服务器发送事件,长轮询和永久iframe。该库中有两个类,如下所示:

1.持续连接

它是低级别的,因此它很复杂,需要更多的配置,但作为回报,它提供了更多的个人处理类的工具。

2. 集线器(Hub)

这是高水平,更受欢迎。

如何借助signalrhub类实现简单的聊天场景?

我的目的只是发出涉及signalr的随机场景。您可以将它用于您的个人场景,我只需按照以下步骤对服务器(集线器类)和客户端进行挑战,并说明客户端发送请求和服务器如何响应?他们如何互相交流?

场景描述

我想为客户服务部门建立一个应用程序。有些主管部门负责帮助客户,另一方面有客户提出问题并需要帮助。

假设两个管理员在线并连接到聊天服务,第一个客户端来问一个问题,所以系统连接第一个客户端到第一个免费管理员,第二个客户端这个过程会重复,但是第三个客户端从系统发出警报,没有管理员帮忙。每当第一个客户端断开连接时,第一个管理员就会自由。

我对这个场景的约定是使用标志来提醒连接的用户是用户还是管理员,以及哪个用户空闲或忙碌。在我的数据库中,如果admincode等于零,那么它是用户,否则它是管理员,我定义标志tpflag(在应用程序中)对于用户等于零,对于管理员等于1。每当他们连接到聊天标志时,freeflag变为零,这表示忙碌的用户,并且一旦客户端离开会话,就变成显示空闲状态的会话。

如果 freeflag == 0 ==>
如果freeflag == 1 ==>空闲
如果tpflag == 0 ==>用户
如果tpflag == 1 ==> 管理员

先决条件

  1. Visual Studio 2012
  2. SQL Server 2008
  3. 从包管理器控制台安装必要的依赖项

使用代码

1步:创建项目

文件 - >新项目 - > ASP.Net MVC 4 Web应用程序{提供名称和目录} - > {Template = BasicView Engine = Razor}

步骤2:打开PM以安装依赖项文件

菜单(工具) - >库包管理器 - >包管理器控制台

3步:删除旧依赖关系的指令

首先,删除所有旧版本,以便安装新版本的SignalR 2.xx In Line

PM>Uninstall-Package Microsoft.AspNet.SignalR –RemoveDependencies

4步:安装必要的依赖项文件的说明

对于新版本,请使用:

PM> Install-Package Microsoft.AspNet.SignalR

我已经使用signalr版本2.0.1进行此练习:

PM> Install-Package Microsoft.AspNet.SignalR -Version 2.0.1

PM>Install-Package Microsoft.Owin

通过编写该指令,nuget可以完成运行signalr所需的所有依赖注入。如果你看一下解决方案中的参考部分,你可以使用Microsoft.ASPNet.SignalR.xMicrosoft.Owin.xx等等,或者如果你看一下解决方案jquery-1.xjquery.signalR 2.xx等中的Scripts部分,那么对所有依赖感到安慰。

解决方案 - >打开引用 - >

解决方案 - >脚本 - >

另一方面,在成功安装signalR依赖项之后,您将在程序包控制台上方找到readme.txt完整帮助。它包含了开始使用signalr的所有必要指令。我将在接下来的步骤中解释这些说明。

提示(1):NuGet

如果您遇到此错误无法解析远程名称:'www.nuget.org'”那么您应该更改位于Package Source前面的Package Manager Settings

您应该将源从https更改为http协议以解决此问题。

提示(2):Owin

检查您的引用部分以确保有Owin,否则按照以下指示:右键单击引用 - >管理NuGet程序包 - >在左侧选择在线 - >搜索Owin - >选择OwinOwin IAppBuilder启动界面) - >安装。

然后你应该在你的引用部分看到Owin

5步:启动类

要在项目中启用signalr ,您应该将类​​创建为启动类(startup)。(如果在以前版本的signalr中,我的意思是第一个版本,你曾经在global.asax中的Application Start中编写RouteTable.Routes.MapHubs();,现在忘记它并只使用启动类(startup)。右键单击:在项目名称上{SignalR} - >添加类 - - >名称:Startup.cs

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(MvcSignal.Startup))]
namespace MvcSignal
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }
}

6步:根据我们的场景组织数据库

步骤6.1:创建“tbl_User”

tbl_user将收集用户和管理员,如果AdminCode由前一个表中的数字填充,那么它是属于department其他人的管理员,如果它被(零)填充说明给普通用户。{“UserID” int + identity=yes and “AdminCode” default value = 0 }

步骤6.2:创建“tbl_Conversation”

tbl_Conversation将从用户和管理员之间的对话中收集数据。完成对话后将填写此表格。{“ConID”int + identity = yes}

7步:创建Hub

步骤7.1Model (文件夹) - >创建类“UserInfo.cs”

public class UserInfo
    {
        public string ConnectionId { get; set; }
        public string UserName { get; set; }
        public string UserGroup { get; set; }

        //if freeflag==0 ==> Busy
        //if freeflag==1 ==> Free
        public string freeflag { get; set; }

        //if tpflag==2 ==> User Admin
        //if tpflag==0 ==> User Member
        //if tpflag==1 ==> Admin

        public string tpflag { get; set; }

        public int UserID { get; set; }
        public int AdminID { get; set; } 
    }

步骤7.2Model (文件夹) - >创建类“MessageInfo.cs”

public class MessageInfo
  {
      public string UserName { get; set; }

      public string Message { get; set; }

      public string UserGroup { get; set; }

      public string StartTime { get; set; }

      public string EndTime { get; set; }

      public string MsgDate { get; set; }
  }

步骤7.3Model (文件夹)?创建“HomeController.cs”

public ActionResult Chat()
   {
       ViewBag.Message = "Your contact page.";

       return View();
   }

右键单击Chat() - >选择添加视图 - >

步骤7.4:创建Chat.cshtml {Client Side}

@{
    ViewBag.Title = "Chat";
}

<div id="divLogin" class="mylogin">

    User Name:<input id="txtUserName" type="text" /><br />
       Password :   <input id="txtPassword" type="password" /><br />
    <input id="btnLogin" type="button" value="Login" />
    <div id="divalarm"></div>
</div>

<div id="divChat" class="mylogin">

<div id="welcome"></div><br />
<input id="txtMessage" type="text" />
<input id="btnSendMessage" type="button" value="Send" />
<div id="divMessage"></div>

</div>

    <input id="hUserId" type="hidden" />
    <input id="hId" type="hidden" />
    <input id="hUserName" type="hidden" />
    <input id="hGroup" type="hidden" />

@section scripts {
   
    <script src="~/Scripts/jquery-1.8.2.min.js"></script>
    <script src="~/Scripts/jquery.signalR-2.0.1.min.js" type="text/javascript"></script>
    <script src="~/signalr/hubs" type="text/javascript"></script>
    @*<script type="text/javascript" src="@Url.Content("~/signalr/hubs")"></script>*@
    @* <script type="text/javascript" src='<%= ResolveClientUrl("~/signalr/hubs") %>'></script>*@
   
   <script>
       $(function () { //This section will run whenever we call Chat.cshtml page

           $("#divChat").hide();
           $("#divLogin").show();

           var objHub = $.connection.myHub;

           loadClientMethods(objHub);

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

               loadEvents(objHub);

           });
       });

       function loadEvents(objHub) {

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

               var name = $("#txtUserName").val();
               var pass = $("#txtPassword").val();

               if (name.length > 0 && pass.length > 0) {
                   // <<<<<-- ***** Return to Server [  Connect  ] *****
                   objHub.server.connect(name, pass);

               }
               else {
                   alert("Please Insert UserName and Password");
               }

           });

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

               var msg = $("#txtMessage").val();

               if (msg.length > 0) {

                   var userName = $('#hUserName').val();
                   // <<<<<-- ***** Return to Server [  SendMessageToGroup  ] *****
                   objHub.server.sendMessageToGroup(userName, msg);

               }
           });

           $("#txtPassword").keypress(function (e) {
               if (e.which == 13) {
                   $("#btnLogin").click();
               }
           });

           $("#txtMessage").keypress(function (e) {
               if (e.which == 13) {
                   $('#btnSendMessage').click();
               }
           });
       }

       function loadClientMethods(objHub) {

           objHub.client.NoExistAdmin = function () {
               var divNoExist = $('<div><p>There is no Admin to response you try again later</P></div>');
               $("#divChat").hide();
               $("#divLogin").show();

               $(divNoExist).hide();
               $('#divalarm').prepend(divNoExist);
               $(divNoExist).fadeIn(900).delay(9000).fadeOut(900);
           }

           objHub.client.getMessages = function (userName, message) {

               $("#txtMessage").val('');
               $('#divMessage').append('<div><p>' + userName + ': ' + message + '</p></div>');

               var height = $('#divMessage')[0].scrollHeight;
               $('#divMessage').scrollTop(height);
           }

           objHub.client.onConnected = function (id, userName, UserID, userGroup) {

               var strWelcome = 'Welcome' + +userName;
               $('#welcome').append('<div><p>Welcome:' + userName + '</p></div>');

               $('#hId').val(id);
               $('#hUserId').val(UserID);
               $('#hUserName').val(userName);
               $('#hGroup').val(userGroup);

               $("#divChat").show();
               $("#divLogin").hide();
           }
       }
    </script>
}

步骤7.5:创建Model1.edmx

为了有一个简单的方法从数据库中获取和插入数据,我按如下方式创建模型:右键单击项目名称 - >添加新项 - >选择“ADO.NET实体数据模型” - >选择生成从数据库“ - >连接到您的数据库 - >选择您的表格。

步骤8:创建文件夹并将其命名为Hubs然后创建简单类并将其命名为“MyHub.cs”

{如果您拥有Visual Studio的最新更新版本,则可以添加新项目并选择“SignalR Hub Class”}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using MvcSignal.Models;
using Microsoft.AspNet.SignalR.Hubs;

namespace MvcSignal
{
    public class MyHub : Hub
    {
        static List UsersList = new List();
        static List<messageinfo> MessageList = new List<messageinfo>();

        //-->>>>> ***** Receive Request From Client [  Connect  ] *****
        public void Connect(string userName, string password)
        {
            var id = Context.ConnectionId;
            string userGroup="";
            //Manage Hub Class
            //if freeflag==0 ==> Busy
            //if freeflag==1 ==> Free

            //if tpflag==0 ==> User
            //if tpflag==1 ==> Admin


            var ctx = new TestEntities();

            var userInfo =
                 (from m in ctx.tbl_User
                  where m.UserName == userName && m.Password == password
                  select new { m.UserID, m.UserName, m.AdminCode }).FirstOrDefault();

            try
            {
                //You can check if user or admin did not login before by below line which is an if condition
                //if (UsersList.Count(x => x.ConnectionId == id) == 0)

                //Here you check if there is no userGroup which is same DepID --> this is User otherwise this is Admin
                //userGroup = DepID
               
               
                if ((int)userInfo.AdminCode == 0)
                {
                    //now we encounter ordinary user which needs userGroup and at this step, 
                    //system assigns the first of free Admin among UsersList
                    var strg = (from s in UsersList where (s.tpflag == "1") 
                    && (s.freeflag == "1") select s).First();
                    userGroup = strg.UserGroup;

                    //Admin becomes busy so we assign zero to freeflag which is shown admin is busy
                    strg.freeflag = "0";

                    //now add USER to UsersList
                    UsersList.Add(new UserInfo { ConnectionId = id, 
                                                 UserID = userInfo.UserID, 
                                                 UserName = userName, 
                                                 UserGroup = userGroup, 
                                                 freeflag = "0", 
                                                 tpflag = "0", });
                    //whether it is Admin or User now both of them has userGroup and I Join this user or admin to specific group 
                    Groups.Add(Context.ConnectionId, userGroup);
                    Clients.Caller.onConnected(id, userName, userInfo.UserID, userGroup);
                }
                else
                {
                    //If user has admin code so admin code is same userGroup
                    //now add ADMIN to UsersList
                    UsersList.Add(new UserInfo { ConnectionId = id, 
                                                 AdminID = userInfo.UserID, 
                                                 UserName = userName, 
                                                 UserGroup = userInfo.AdminCode.ToString(), 
                                                 freeflag = "1", 
                                                 tpflag = "1" });
                    //whether it is Admin or User now both of them has userGroup and I Join this user or admin to specific group 
                    Groups.Add(Context.ConnectionId, userInfo.AdminCode.ToString());
                    Clients.Caller.onConnected(id, userName, userInfo.UserID, userInfo.AdminCode.ToString());
                }                       
            }

            catch
            {
                string msg = "All Administrators are busy, please be patient and try again";
                //***** Return to Client *****
                Clients.Caller.NoExistAdmin();
            }
        }
        // <<<<<-- ***** Return to Client [  NoExist  ] *****

        //--group ***** Receive Request From Client [  SendMessageToGroup  ] *****
        public void SendMessageToGroup(string userName, string message)
        {
            if (UsersList.Count != 0)
            {
                var strg = (from s in UsersList where (s.UserName == userName) select s).First();
                MessageList.Add(new MessageInfo 
                { UserName = userName, Message = message, UserGroup = strg.UserGroup });
                string strgroup = strg.UserGroup;
                // If you want to Broadcast message to all UsersList use below line
                // Clients.All.getMessages(userName, message);

                //If you want to establish peer to peer connection use below line 
                //so message will be send just for user and admin who are in same group
                //***** Return to Client *****
                Clients.Group(strgroup).getMessages(userName, message);
            }
        }
        // <<<<<-- ***** Return to Client [  getMessages  ] *****

        //--group ***** Receive Request From Client ***** 
        //{ Whenever User close session then OnDisconneced will be occurs }
        public override System.Threading.Tasks.Task OnDisconnected()
        {

            var item = UsersList.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
            if (item != null)
            {
                UsersList.Remove(item);

                var id = Context.ConnectionId;

                if (item.tpflag == "0")
                {
                    //user logged off == user
                    try
                    {
                        var stradmin = (from s in UsersList where 
                        (s.UserGroup == item.UserGroup) && (s.tpflag == "1") select s).First();
                        //become free
                        stradmin.freeflag = "1";
                    }
                    catch
                    {
                        //***** Return to Client *****
                        Clients.Caller.NoExistAdmin();
                    }                    
                }

                //save conversation to dat abase
            }

            return base.OnDisconnected();
        }
    }
}</messageinfo>

规则和约定

客户端想要在服务器端调用服务器方法时的前缀:

(客户端 - >服务器)//客户端向服务器发送请求

1. objHub.server.methodname(){服务器端的方法名}

并且服务器端(myHub.cs类)中的方法名完全相同。

服务器想要在客户端调用客户端方法时的前缀:

(服务器 - >客户端)//服务器调用客户端方法&{客户端方法名称}

  1. Clients.caller.methodname() // caller表示只发送请求的用户
  2. Clients.all.methodname() // all表示所有已连接的用户
  3. Clients.Group(groupName).methodname() //Group仅表示同一组中的用户

当在MyHub.cs类中有*****返回客户端*****时,这意味着您必须在客户端编写具有相同名称的jquery函数。

确实,他们的互动如下:

提示(3):调用服务器类

每当你想要调用你的服务器类时,都有一些小技巧总是在客户端你应该使用驼峰类型的特定命名约定,例如,如果你的集线器类名是MyHub,你应该从myHub实例化你的对象,或者如果你有SendMessageToGroup,你应该从sendMessageToGroup调用它,所以它应该是这样的:

测试用例

要获得相同的结果,您应该拥有与我在第七步中解释的数据库相同的数据库。

用例1

测试计划:如果客户端尝试登录且没有管理员,则系统会显示警报。

测试步骤

  1. 运行项目
  2. 用户名:mahsa
  3. 密码:123
  4. 预期输出:系统显示警报

用例2

测试计划:至少有空闲管理员,然后一个客户端登录,然后第一个管理员将分配给第一个需要帮助的空闲客户。

测试步骤

1. 运行项目

2. 用户名:admin1

3. 密码:123

4. 登录{admin1 as first admin}

5. 将URL复制到另一个Web浏览器

6. 用户名:mahsa

7. 密码:123

8. 登录{mahsa as first client}

9. 如果“mahsa”发送消息,那么“admin1”将看到它,因为它们在同一组中。当第一个客户端真正登录然后添加到第一个空闲管理员。

10. 将URL复制到另一个Web浏览器

11. 用户名:kashi

12. 密码:123

13. 登录{kashi as second client}

14. 系统显示警报并说没有管理员然后系统显示警报

15. 将URL复制到另一个Web浏览器

16. 用户名:admin2

17. 密码:123

18. “kashi”“admin2”无法看到“admin1”“mahsa”之间的对话 

提示

提示1.管理员和用户的不同UI以及管理员的显示等待用户

如果您需要为User提供不同的用户界面,则应为用户和管理员创建不同的div,使用不同的css,并通过class属性为其分配特定的CSS

当您要从集线器类向Admin发送管理消息时,请发送到不同的客户端方法,例如objHub.client.getMessagesAdmin和用户objHub.client.getMessagesUser

Chat.cshtml中,通过不同的div“divMessageAdmin”“divMessageUser”使用不同的UI实现这些方法,您应该通过适当的消息填充这些div

所以请遵循:
 

 1.创建不同的div

<div class="Admin" id="divMessageAdmin"></div>
<div Class="User" id="divMessageUser"></div>

2.Hub - > SendMessageToGroup - > 

检查用户是否为Admin - > 

Clients.Group(strgroup).getMessagesAdmin(userName, message);

 检查用户是否是普通用户 - >

Clients.Group(strgroup).getMessagesUser(userName, message);

  1.Chat.cshtml中:

  如果用户是管理员:     

objHub.client.getMessagesAdmin = function (userName, message) {
 
       $("#txtMessage").val('');
       $('#divMessageAdmin').append('<div><p>' + userName + ': ' + message + '</p></div>');
 
       var height = $('#divMessageAdmin')[0].scrollHeight;
       $('#divMessageAdmin').scrollTop(height);
   }

如果User是普通用户:  

objHub.client.getMessagesUser = function(userName,message){ 
       $('#getMessagesUser').append('<div><p>' + userName + ': ' + message + '</p></div>');

       var height = $('#getMessagesUser')[0].scrollHeight;
       $('#getMessagesUser').scrollTop(height);
   }

要在AdminUI中查看等待用户:
 

1.Hub类中 - >连接 - >你有这行代码

catch
          {
              string msg = "All Administrators are busy, please be patient and try again";
              // Return to Client 
              Clients.Caller.NoExistAdmin();
          }

请为必须等待空闲管理员的用户发送用户名: 

Clients.Caller.NoExistAdmin(username);

2.Chat.cshtml

objHub.client.NoExistAdmin = function (username) {
    var divNoExist = $('There is no Admin to response you try again later');             
    $("#divChat").hide(); $("#divLogin").show(); $("#divWaitingUser").append('' + userName + ''); 
    $(divNoExist).hide(); $('#divalarm').prepend(divNoExist);     
    $(divNoExist).fadeIn(900).delay(9000).fadeOut(900); 
}

 

原文地址:https://www.codeproject.com/Articles/732190/Real-Time-Web-Solution-for-Chat-by-MVC-SignalR-H

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值