Net Core Web 使用 WebSocket 實現鏈接人數
Net Core Web 使用 WebSocket 實現監控人數
在 Startup.cs 文件中的方法 Configure() 中加入如下中間件(盡量往前面放,盡量在 app.UseCors()的后面(如果有的話)):
app.UseWebSockets();
app.Use(async (context, next) =>
{
//是否為WebSocket請求
if (context.WebSockets.IsWebSocketRequest)
{
using (IServiceScope scope = app.ApplicationServices.CreateScope())
{
//執行...
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
await new WebSocketFilter().Echo(context, webSocket);
}
}
else
{
//下一個中間件...
await next();
}
});
創建一個文件 WebSocketFilter.cs 拷貝如下代碼:
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace YGNT.DtSchoolAdmin.Filter
{
public class WebSocketFilter
{
//用戶連接池
private static Dictionary<string, WebSocket> UserPool = new Dictionary<string, WebSocket>();
//監控連接池(全部)
private static Dictionary<string, WebSocket> MonitoringPool1 = new Dictionary<string, WebSocket>();
/// <summary>
/// webSocket的處理
/// </summary>
public async Task Echo(HttpContext context, WebSocket webSocket)
{
try
{
//鏈接的唯一標識符
var ConnectionId = context.Connection.Id;
//用戶={user_id}
string user = context.Request.Query["user"];
//監控={1全部,2機構,3企業}
string monitoring = context.Request.Query["monitoring"];
if (!string.IsNullOrWhiteSpace(user))
{
//第一次open時,添加到連接池中
if (!UserPool.ContainsKey(user))
{
UserPool.Add(user, webSocket);
foreach (var item in MonitoringPool1)
SendTextAsync(item.Value, $"人數為{UserPool.Count}");
}
else
{
await UserPool[user].CloseAsync(WebSocketCloseStatus.NormalClosure, "此用戶被占領,強制關閉", CancellationToken.None);
UserPool[user] = webSocket;
}
}
else if (!string.IsNullOrWhiteSpace(monitoring))
{
if (monitoring == "1")
{
if (!MonitoringPool1.ContainsKey(ConnectionId))
MonitoringPool1.Add(ConnectionId, webSocket);
await SendTextAsync(webSocket, $"人數為{UserPool.Count}");
}
}
//全部消息容器
List<byte> bs = new List<byte>();
//緩沖區
var buffer = new byte[1024 * 4];
//監聽Socket信息
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
//是否關閉
while (!result.CloseStatus.HasValue)
{
//文本消息
if (result.MessageType == WebSocketMessageType.Text)
{
bs.AddRange(buffer.Take(result.Count));
//消息是否已接收完全
if (result.EndOfMessage)
{
//發送過來的消息
string userMsg = Encoding.UTF8.GetString(bs.ToArray(), 0, bs.Count);
var reply = $"服務器收到你的信息為({userMsg})【{DateTime.Now}】";
await SendTextAsync(webSocket, reply);
//清空消息容器
bs = new List<byte>();
}
}
//繼續監聽Socket信息
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
//關閉WebSocket(客戶端發起)
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
if (!string.IsNullOrWhiteSpace(user))
if (UserPool.ContainsKey(user))
UserPool.Remove(user);
if (!string.IsNullOrWhiteSpace(ConnectionId))
if (MonitoringPool1.ContainsKey(ConnectionId))
MonitoringPool1.Remove(ConnectionId);
foreach (var item in MonitoringPool1)
SendTextAsync(item.Value, $"人數為{UserPool.Count}");
}
catch (Exception ex)
{
await SendTextAsync(webSocket, $"服務器發生錯誤,正在關閉WebSocket。錯誤堆載為【{ex}】");
//關閉WebSocket(服務端發起)
await webSocket.CloseAsync(WebSocketCloseStatus.InternalServerError, ex.Message, CancellationToken.None);
}
}
/// <summary>
/// 發送文本消息
/// </summary>
/// <param name="webSocket"></param>
/// <param name="mess"></param>
/// <returns></returns>
private static async Task SendTextAsync(WebSocket webSocket, string mess)
{
var replyMess = Encoding.UTF8.GetBytes(mess);
if (webSocket != null && webSocket.State == WebSocketState.Open)
//發送消息
await webSocket.SendAsync(new ArraySegment<byte>(replyMess), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
在 Startup.cs 文件中的方法 Configure() 中加入如下中間件(可以放在 app.UseWebSockets() 的前面):
app.UseStaticFiles();
在web項目中創建文件夾 wwwroot (如果沒有的話)(注:vs會自動改變樣式),並在下面創建一個文件名為WebSocket.html 的文件,如圖:

並將如下代碼拷貝在 WebSocket.html 文件中:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocket示例應用程序</title>
<style>
body {
background-color: #DDDDDD;
}
table {
border: 0
}
.connection-status {
color: orangered;
}
.connection-status2 {
color: blueviolet;
}
.commslog-data {
font-family: Consolas, Courier New, Courier, monospace;
}
.commslog-server {
background-color: red;
color: white
}
.commslog-client {
background-color: green;
color: white
}
</style>
</head>
<body>
<h1>WebSocket示例應用程序</h1>
<p>
當前狀態:
<label id="stateLabel" class="connection-status">准備連接</label>
</p>
<div>
<label>可用參數:</label>
<label class="connection-status2">?user=1&monitoring=1</label>
</div>
<div>
<label for="connectionUrl">WebSocket服務器URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">連接</button>
</div>
<br />
<div>
<label for="sendMessage">發送消息:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>發送</button>
<button id="closeButton" disabled>關閉WebSocket</button>
<button id="emptyButton">清空消息</button>
</div>
<h2>通信記錄</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">發送者</td>
<td style="width: 100px">接收者</td>
<td>消息</td>
</tr>
</thead>
<tbody id="commsLog">
</tbody>
</table>
<script>
var connectionUrl = document.getElementById("connectionUrl");
var connectButton = document.getElementById("connectButton");
var stateLabel = document.getElementById("stateLabel");
var sendMessage = document.getElementById("sendMessage");
var sendButton = document.getElementById("sendButton");
var commsLog = document.getElementById("commsLog");
var closeButton = document.getElementById("closeButton");
var emptyButton = document.getElementById("emptyButton");
var socket;
var scheme = document.location.protocol === "https:" ? "wss" : "ws";
var port = document.location.port ? (":" + document.location.port) : "";
//connectionUrl.value = scheme + "://" + document.location.hostname + port + "/ws";
connectionUrl.value = scheme + "://" + document.location.hostname + port;
function updateState() {
function disable() {
sendMessage.disabled = true;
sendButton.disabled = true;
closeButton.disabled = true;
}
function enable() {
sendMessage.disabled = false;
sendButton.disabled = false;
closeButton.disabled = false;
}
connectionUrl.disabled = true;
connectButton.disabled = true;
if (!socket) {
disable();
} else {
switch (socket.readyState) {
case WebSocket.CLOSED:
stateLabel.innerHTML = "關閉";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "關閉中...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "連接中...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "打開";
enable();
break;
default:
stateLabel.innerHTML = "未知的WebSocket狀態: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("沒有連接Socket");
}
socket.close(1000, "從客戶端關閉");
};
emptyButton.onclick = function () {
commsLog.innerHTML = ""
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("沒有連接Socket");
}
var data = sendMessage.value;
socket.send(data);
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">客戶端</td>' +
'<td class="commslog-server">服務端</td>' +
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
};
connectButton.onclick = function () {
stateLabel.innerHTML = "連接中";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">連接已打開</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">連接關閉了。錯誤碼: ' + htmlEscape(event.code) + '。原因:' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">服務端</td>' +
'<td class="commslog-client">客戶端</td>' +
'<td class="commslog-data">' + htmlEscape(event.data) + '</td></tr>';
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
</script>
</body>
</html>
完成,運行項目,跳轉到我們的測試頁面(如我的為:https://localhost:5001/WebSocket.html):

我們鏈接一個觀察者的身份:

然后登陸一個用戶A:(可以看到監控者收到了一條消息“人數為1”)

然后登陸一個用戶B:(可以看到監控者收到了一條消息“人數為2”)

然后關閉用戶A的瀏覽器:(可以看到監控者收到了一條消息“人數為1”)

【額外】並且還實現了用戶給服務器發送消息,服務器對用戶回復原來的消息和回復時間。(可以稍微改一下就可以實現聊天對話和群聊功能)

【額外】並且還實現了用戶被同一個用戶擠下線的情況

完畢
