实时通信微软官方推荐使用SignalR,不到在哪个博客看到说SignalR.js依赖Jquery(其实我没验证过),我玩了下demo就没用过了。
听说WebSocket是比较原始的实时通信类,.Net Core也支持,不过官方demo就太过简单了,了解过的朋友都知道,只能自己发消息给自己,不像SignalR直接可以广播。
经过多番研究,下面用官方demo改造。
WebSocketHelper.cs 辅助类(需要自己创建),主要做容器和容器模型,根据自己的结构定义。
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
namespace EchoApp
{
public enum SendType
{
SystemMsg,
Unicast,
Broadcast
}
public enum DataType
{
String,
Json,
}
public class WebSocketModel
{
public WebSocket Sk { get; set; }
public Guid Id { get; set; }
}
public class ChatRoom
{
public List<WebSocketModel> UserList { get; set; }
public Guid RoomId { get; set; }
}
public class MessageModel
{
public DataType DataType { get; set; }
public SendType SendType { get; set; }
public object Data { get; set; }
public string SenderName { get; set; }
public Guid SenderId { get; set; }
public Guid TargetId { get; set; }
}
public static class WebSocketHelper
{
/// <summary>
/// websocket容器,可以当作缓存使用
/// </summary>
public static ChatRoom Root = new ChatRoom {
UserList = new List<WebSocketModel>(),
RoomId = Guid.NewGuid(),
};
}
}
Startup.cs websocket是个服务,需要一点异步基础才好看懂代码。
#define UseOptions // or NoOptions or UseOptionsAO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Debug;
using Newtonsoft.Json;
namespace EchoApp
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder =>
{
builder.AddConsole()
.AddDebug()
.AddFilter<ConsoleLoggerProvider>(category: null, level: LogLevel.Debug)
.AddFilter<DebugLoggerProvider>(category: null, level: LogLevel.Debug);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
#region WebSockets相关
var webSocketOptions = new WebSocketOptions()
{
KeepAliveInterval = TimeSpan.FromSeconds(120),
ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);
app.Use(async (context, next) =>
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
#region 创建socket连接
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
var msg = JsonConvert.SerializeObject(new MessageModel
{
DataType = DataType.Json,
SendType = SendType.SystemMsg,
SenderName = "Server",
Data = new { Id = Guid.NewGuid(), UserName = "NolenJ" }
});
byte[] byteArray = System.Text.Encoding.Default.GetBytes(msg);
//连接成功,发送反馈,这里用了自定义的数据模板,前端根据消息类型做处理。
//WebSocket创建连接在浏览器network可以看到请求,但是不像ajax请求,给不了返回值。
await webSocket.SendAsync(new ArraySegment<byte>(byteArray), WebSocketMessageType.Text, true, CancellationToken.None);
//保存到socket容器
WebSocketHelper.Root.UserList.Add(new WebSocketModel
{
Id = Guid.NewGuid(),
Sk = webSocket
});
#endregion
//连接成功后,丢给自定义收发数据的管理工具
await Echo(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
#endregion
app.UseFileServer();
}
#region WebSockets相关
private async Task Echo(WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
while (!webSocket.CloseStatus.HasValue)
{
//拆包
var msg = JsonConvert.DeserializeObject<MessageModel>(Encoding.Default.GetString(buffer));
switch (msg.SendType)
{
case SendType.Broadcast:
List<Task> tasks = new List<Task>();
WebSocketHelper.Root.UserList.ForEach(c =>
{
tasks.Add(c.Sk.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None));
});
Task.WaitAll(tasks.ToArray());
break;
case SendType.Unicast:
Task.WaitAll(new List<Task>() {
WebSocketHelper.Root.UserList.Where(c => c.Id == msg.TargetId).FirstOrDefault().Sk.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None),
//给自己发消息表示我的消息已送达
webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None)
}.ToArray());
break;
}
//重置消息容器
buffer = new byte[1024 * 4];
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
}
WebSocketHelper.Root.UserList.Remove(WebSocketHelper.Root.UserList.Where(c => c.Sk == webSocket).FirstOrDefault());
await webSocket.CloseAsync(webSocket.CloseStatus.Value, webSocket.CloseStatusDescription, CancellationToken.None);
}
#endregion
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style>
table { border: 0 }
.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 Sample Application</h1>
<p id="stateLabel">Ready to connect...</p>
<div>
<label for="connectionUrl">WebSocket Server URL:</label>
<input id="connectionUrl" />
<button id="connectButton" type="submit">Connect</button>
</div>
<p></p>
<div>
<label for="sendMessage">Message to send:</label>
<input id="sendMessage" disabled />
<button id="sendButton" type="submit" disabled>Send</button>
<button id="closeButton" disabled>Close Socket</button>
</div>
<h2>Communication Log</h2>
<table style="width: 800px">
<thead>
<tr>
<td style="width: 100px">From</td>
<td style="width: 100px">To</td>
<td>Data</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 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" ;
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 = "Closed";
disable();
connectionUrl.disabled = false;
connectButton.disabled = false;
break;
case WebSocket.CLOSING:
stateLabel.innerHTML = "Closing...";
disable();
break;
case WebSocket.CONNECTING:
stateLabel.innerHTML = "Connecting...";
disable();
break;
case WebSocket.OPEN:
stateLabel.innerHTML = "Open";
enable();
break;
default:
stateLabel.innerHTML = "Unknown WebSocket State: " + htmlEscape(socket.readyState);
disable();
break;
}
}
}
closeButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
socket.close(1000, "Closing from client");
};
sendButton.onclick = function () {
if (!socket || socket.readyState !== WebSocket.OPEN) {
alert("socket not connected");
}
var data = sendMessage.value;
socket.send(JSON.stringify({
DataType: 0,
SendType: 2,
Data: data,
SenderName:'未知用户'
}));
commsLog.innerHTML += '<tr>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-data">' + htmlEscape(data) + '</td></tr>';
};
connectButton.onclick = function() {
stateLabel.innerHTML = "Connecting";
socket = new WebSocket(connectionUrl.value);
socket.onopen = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection opened</td>' +
'</tr>';
};
socket.onclose = function (event) {
updateState();
commsLog.innerHTML += '<tr>' +
'<td colspan="3" class="commslog-data">Connection closed. Code: ' + htmlEscape(event.code) + '. Reason: ' + htmlEscape(event.reason) + '</td>' +
'</tr>';
};
socket.onerror = updateState;
socket.onmessage = function (event) {
let tempdata = JSON.parse(event.data);
console.log("收到消息", tempdata);
switch (tempdata.SendType) {
case 0:
break;
case 1:
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(tempdata.Data) + '</td></tr>';
break;
case 2:
commsLog.innerHTML += '<tr>' +
'<td class="commslog-server">Server</td>' +
'<td class="commslog-client">Client</td>' +
'<td class="commslog-data">' + htmlEscape(tempdata.Data) + '</td></tr>';
break;
}
};
};
function htmlEscape(str) {
return str.toString()
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
</script>
</body>
</html>
以上的代码都基于官方demo改动,建议下载官方demo后对比,要是你用.net core 3.0,记得修改目标框架。
代码如果有出错或哪里不懂,在评论区告诉我。
如果对你有用请留个赞,谢谢。