目錄
介紹
背景
1. WebSocket
2.伺服器發送事件(SSE)
3.永遠的Frame
4.輪詢
5.長輪詢
場景描述
先決條件
使用代碼
第1步:建立項目
步驟2:打開PM以安裝依賴項檔案
第3步:删除舊依賴關系的指令
第4步:安裝必要的依賴項檔案的說明
第5步:啟動類
第6步:根據我們的場景組織資料庫
第7步:建立Hub類
步驟8:建立檔案夾并将其命名為Hubs然後建立簡單類并将其命名為“MyHub.cs”
規則和約定
提示
- 下載下傳項目 - 4.35 MB
介紹
如今,由于資訊量的增加和實時實作資料的必要性,我們需要技術來滿足我們在這個問題上的要求。假設股票市場價格在每個時刻都在變化,您認為使用者應該每時每刻重新整理頁面以告知最後價格嗎?顯然,對于這樣的問題,這不是一個合理的解決方案。或者随着産品和服務的增加,我們需要客戶服務來幫助使用者和買家,最好和更便宜的溝通方式是通過聊天程式進行對話。出于同樣的原因,我們不能強迫使用者按下按鈕來接收我們的上一條消息。
SignalR是一種實時技術,它使用異步庫集在用戶端和伺服器之間建立持久連接配接。使用者無需使用傳統方式即可從伺服器接收上次更新的資料,如重新整理頁面或按下按鈕。
背景
您需要了解MVC 4.0技術和EntityFramework> 4.0才能更好地完成本文。
另一方面,SignalR使用以下方法建立實時網絡:
1. WebSocket
Websocket是一種全雙工協定,在内部使用http握手,允許消息流在TCP之上流動。它支援:Google Chrome(> 16)Fire Fox(> 11)IE(> 10)Win IIS(> 8.0)。由于加密消息和全雙工,websocket是最好的解決方案,并且首先signalR檢查Web伺服器和用戶端伺服器是否支援websocket。
單工通訊
它隻是以一種方式傳播,當一個點隻是廣播而另一個點隻能聽而不發送資訊,如電視和廣播。
半雙工
一個點發送消息,并且在這一瞬間的另一點無法發送消息,并且應該等待,直到第一點完成其傳輸,然後發送它的消息,它僅僅是一個每次一個通信線路,諸如舊無線裝置對講機和HTTP協定。
全雙工
這兩個點可以同時發送和接收消息,不需要等到其他點完成其傳輸,如電話和websocket協定。
全雙工
2.伺服器發送事件(SSE)
signalr的下一個選擇是伺服器發送事件,因為伺服器和用戶端之間的持久性通信。在這種方法中,通信不會斷開連接配接,伺服器的最後資料将自動更新并通過HTTP連接配接傳輸到用戶端。EventSource是HTML5技術的一部分。
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)
這是高水準,更受歡迎。
如何借助signalr和hub類實作簡單的聊天場景?
我的目的隻是發出涉及signalr的随機場景。您可以将它用于您的個人場景,我隻需按照以下步驟對伺服器(集線器類)和用戶端進行挑戰,并說明用戶端發送請求和伺服器如何響應?他們如何互相交流?
場景描述
我想為客戶服務部門建立一個應用程式。有些主管部門負責幫助客戶,另一方面有客戶提出問題并需要幫助。
假設兩個管理者線上并連接配接到聊天服務,第一個用戶端來問一個問題,是以系統連接配接第一個用戶端到第一個免費管理者,第二個用戶端這個過程會重複,但是第三個用戶端從系統發出警報,沒有管理者幫忙。每當第一個用戶端斷開連接配接時,第一個管理者就會自由。
我對這個場景的約定是使用标志來提醒連接配接的使用者是使用者還是管理者,以及哪個使用者空閑或忙碌。在我的資料庫中,如果admincode等于零,那麼它是使用者,否則它是管理者,我定義标志“tpflag”(在應用程式中)對于使用者等于零,對于管理者等于1。每當他們連接配接到聊天标志時,“freeflag”變為零,這表示忙碌的使用者,并且一旦用戶端離開會話,就變成顯示空閑狀态的會話。
如果 freeflag == 0 ==>忙
如果freeflag == 1 ==>空閑
如果tpflag == 0 ==>使用者
如果tpflag == 1 ==> 管理者
先決條件
- Visual Studio 2012
- SQL Server 2008
- 從包管理器控制台安裝必要的依賴項
使用代碼
第1步:建立項目
檔案 - >新項目 - > ASP.Net MVC 4 Web應用程式{提供名稱和目錄} - > {Template = Basic&View 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.x,Microsoft.Owin.xx等等,或者如果你看一下解決方案jquery-1.x,jquery.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 - >選擇Owin(Owin 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.1:Model (檔案夾) - >建立類“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.2:Model (檔案夾) - >建立類“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.3:Model (檔案夾)?建立“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類)中的方法名完全相同。
伺服器想要在用戶端調用用戶端方法時的字首:
(伺服器 - >用戶端)//伺服器調用用戶端方法&{用戶端方法名稱}
- Clients.caller.methodname() // caller表示隻發送請求的使用者
- Clients.all.methodname() // all表示所有已連接配接的使用者
- Clients.Group(groupName).methodname() //Group僅表示同一組中的使用者
當在“MyHub.cs”類中有*****傳回用戶端*****時,這意味着您必須在用戶端編寫具有相同名稱的jquery函數。
确實,他們的互動如下:
提示(3):調用伺服器類
每當你想要調用你的伺服器類時,都有一些小技巧; 總是在用戶端你應該使用駝峰類型的特定命名約定,例如,如果你的集線器類名是“MyHub”,你應該從“myHub”執行個體化你的對象,或者如果你有“SendMessageToGroup”,你應該從“sendMessageToGroup”調用它,是以它應該是這樣的:
測試用例
要獲得相同的結果,您應該擁有與我在第七步中解釋的資料庫相同的資料庫。
用例1
測試計劃:如果用戶端嘗試登入且沒有管理者,則系統會顯示警報。
測試步驟
- 運作項目
- 使用者名:mahsa
- 密碼:123
- 預期輸出:系統顯示警報
用例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);
}
要在Admin的UI中檢視等待使用者:
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