1. 程式人生 > 實用技巧 >結合實際需求,在webapi內利用WebSocket建立單向的訊息推送平臺,讓A頁面和服務端建立WebSocket連線,讓其他頁面可以及時給A頁面推送訊息

結合實際需求,在webapi內利用WebSocket建立單向的訊息推送平臺,讓A頁面和服務端建立WebSocket連線,讓其他頁面可以及時給A頁面推送訊息

1.需求示意圖

2.需求描述

原本是為了給做unity3d客戶端開發的同事提供不定時的訊息推送,比如商城購買道具後服務端將道具資訊推送給客戶端。

本篇文章簡化理解,用“相關部門開展活動,向全市人民徵集社會服務改善意見”為例子。但核心想法一致:單向推送。所以這個功能並不是聊天室,不需要客戶端和客戶端之間互相通訊。核心介面只和服務端建立WebSocket連線,推送訊息全部來自其他地方。

只有核心頁面和服務端建立WebSocket連線,其他市民們都是通過web開發者耳熟能詳的http協議在傳送訊息,不要以為是市民們和部門公告欄玩WebSocket互動

3.程式碼如下,複製即可使用

①WebSocket幫助類,負責建立連線和推送訊息

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets; namespace WSTest
{
public class WSHelper
{
/// <summary>
/// 儲存客戶端的WebSocket物件
/// </summary>
private static readonly Dictionary<string, WebSocket> dicSockets = new Dictionary<string, WebSocket>(); #region 構建執行緒安全的單例模式
private static WSHelper _instance;
private WSHelper()
{ } public static WSHelper GetInstance()
{
if (_instance == null)
{
lock (dicSockets)
{
if (_instance == null)
{
_instance = new WSHelper();
}
}
}
return _instance;
}
#endregion /// <summary>
/// 和客戶端建立WebSocket連線
/// </summary>
/// <param name="arg">客戶端傳送的WebSocket相關資訊</param>
/// <returns></returns>
public async Task ProcessWSChat(AspNetWebSocketContext arg)
{
// 1.獲取請求的客戶端WebSocket物件
WebSocket socket = arg.WebSocket;
// 2.獲取自定義的引數
string adminUserKey = arg.QueryString["adminUserKey"];
if (string.IsNullOrEmpty(adminUserKey)) return;
// 3.將使用者編號作為標識客戶端唯一性的Key,儲存客戶端的WebSocket物件
dicSockets[adminUserKey] = socket; while (true)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[ * ]);
WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None); try
{
if (socket.State != WebSocketState.Open)
{
dicSockets.Remove(adminUserKey);
break;
}
}
catch
{
break;
}
}
} /// <summary>
/// 服務端向客戶端推送訊息
/// </summary>
public bool SendMsg(string message, string adminUserKey)
{
WebSocket socket = null;
if (dicSockets.ContainsKey(adminUserKey))
{
socket = dicSockets[adminUserKey];
}
else
{
return false;
} //【重要】執行下面socket.State程式碼可能會拋異常"無法訪問已經釋放的物件",
// 因為客戶端已經處於斷電、斷網、強制關閉、重新整理等狀態,當前的WebSocket物件已經失去價值,直接刪除即可
try
{
if (socket.State == WebSocketState.Open)
{
ArraySegment<byte> buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(message));
socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
return true;
}
}
catch
{
dicSockets.Remove(adminUserKey);
return false;
}
return false;
}
}
}

WSHelper

②webapi的控制器,負責建立WebSocket連線

using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http; namespace WSTest.Controllers
{
[RoutePrefix("WebSocketConn")]
public class WebSocketConnController : ApiController
{
/// <summary>
/// 建立websocket連線
/// </summary>
[HttpGet]
[Route("GetConnect")]
public HttpResponseMessage GetConnect()
{
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(WSHelper.GetInstance().ProcessWSChat);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
}
}
}

WebSocketConnController

③webapi的業務控制器,徵集意見

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http; namespace WSTest.Controllers
{
/// <summary>
/// 市民服務
/// </summary>
[RoutePrefix("CitizenService")]
public class CitizenServiceController : ApiController
{
/// <summary>
/// 市民意見徵集
/// </summary>
[HttpGet]
[Route("GiveOpinion")]
public string GiveOpinion(string userName, string msg, string sendTo)
{
//1.傳送訊息給客戶端
string sendMsg = string.Format("熱心市民{0}有話要說:{1}", userName, msg);
bool result = WSHelper.GetInstance().SendMsg(sendMsg, sendTo); //2.接收結果,若傳送失敗,可能客戶端還未成功連線WebSocket
return result ? "已提交,您可以去相關部門的官網檢視剛傳送的資訊了。" : "相關部門的平臺還沒開放,請耐心等待"; }
}
}

CitizenServiceController

④測試用部門公告欄頁面【核心頁面】

<!DOCTYPE html>
<html>
<head>
<title>教育局的市民意見徵集佈告欄</title>
</head>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<body>
<div id="titleMsg"></div>
<div id="msgMenu">
來自市民的話:<br>
</div>
<script type="text/javascript">
var webSocket;
var msgCount = 1;
//HTTP處理程式的地址
var handlerUrl = "ws://localhost:2465/WebSocketConn/GetConnect?adminUserKey=adminA"; $(function(){
InitWebSocket();
});
function CloseWebSocket() {
webSocket.close();
webSocket = undefined;
} function InitWebSocket() {
//如果WebSocket物件未初始化,則初始化
if (webSocket == undefined) {
webSocket = new WebSocket(handlerUrl); //開啟連線處理程式
webSocket.onopen = function () {
//WebSocket連線成功
$("#titleMsg").text("平臺已開放,歡迎大家留言");
}; //訊息資料處理程式
webSocket.onmessage = function (e) {
updMsgMenu(e.data);
}; //關閉事件處理程式
webSocket.onclose = function () {
//WebSocket斷開連線
}; //錯誤事件處理程式
webSocket.onerror = function (e) {
updMsgMenu(e.message);
};
}
else {
//webSocket.open();沒有open方法
}
} function updMsgMenu(str){
var tempStr = $("#msgMenu").html();
tempStr = tempStr + msgCount + "." + str + "</br>";
msgCount++;
$("#msgMenu").html(tempStr);
} function Clear(){
msgCount = 1;
$("#msgMenu").html("訊息列表:<br>");
} </script>
</body>
</html>

部門公告欄頁面

⑤測試用市民意見徵集頁面

<!DOCTYPE html>
<html>
<head>
<title>市民意見徵集平臺</title>
</head>
<body>
您的姓名:<input type="text" id="userName" /><br>
您的意見:<textarea type="text" id="msg"></textarea><br>
您想給哪個部門留言:<select id="sendTo">
<option value="adminA">教育局</option>
<option value="adminB">社保局</option>
<option value="adminC">勞動局</option>
</select>
<input type="button" value="提交" onclick="doSend()" /> <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script>
var msgCount = 1;
function doSend(){
$.ajax({
url: "http://localhost:2465/CitizenService/GiveOpinion",
type: "GET",
data:{
userName: $("#userName").val(),
msg: $("#msg").val(),
sendTo: $("#sendTo").val()
},
cache: false,
dataType: "json",
success: function (res) {
console.log(res);
alert("收到訊息:"+ res);
},
error: function (error) {
alert("服務端繁忙");
}
});
}
</script>
</body>
</html>

市民意見徵集頁面

4.執行如下

①教育部門開放了自己的平臺,準備接收市民意見

②有市民向教育部門反饋問題

③公告欄收到及時推送的訊息

5.總結

①本案例中標識建立WebSocket連線的客戶端唯一性的是自定義引數,但WebSocket內部標識唯一性的是SecWebSocketKey,可參考https://www.jianshu.com/p/8759dda1dbfc 瞭解原理。

因此只要核心頁面斷開了WebSocket連線(斷點、斷網、重啟、重新整理頁面等),這次的WebSocket物件都不再有效,需要重新建立連線。

②本案例的需求是市民們向部門反應意見,不是聊天室。並且市民們反應意見是傳統的http請求,服務端向核心頁面推送訊息才用到WebSocket。

③WSHelper.cs類中建立了執行緒安全的單例模式,目的是讓所有使用者訪問到的儲存WebSocket物件的字典集合物件唯一,如果不這樣做,那麼只有在釋出公告欄那臺電腦上才有用。

④案例缺點:服務端沒有監聽核心頁面的WebSocket狀態,因此只有在try-catch捕獲異常時才可以處理掉客戶端的WebSocket物件,對效能上有些不友好。