在开发环境中,对于实时数据流的需求非常常见,最常用的技术包括 Server-Sent Events (SSE) 和 WebSocket。

什么是 Server-Sent Events (SSE)?

SSE (服务器发送事件)是一种基于 HTTP/1.1 协议的传达模型,允许服务器向浏览器不断发送数据更新。它直接使用 HTTP GET 请求,服务器送选用的字符串及内容。

举例: 让我们将一个服务器的实时状态传达给前端浏览器:

1. 添加服务器端 API

在 ASP.NET Core 中实现 SSE,示例是一个简单的项目实时监控。

项目结构如下:

Starup.cs文件新增如下代码:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace WebApplication
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers();
// 允许跨域请求
services.AddCors(options =>
{
options.AddPolicy("AllowLocalhost",
builder => builder.WithOrigins("https://localhost:5001") // 允许来自 https://localhost:5001 的请求
.AllowAnyHeader() // 允许任何头部
.AllowAnyMethod()); // 允许任何方法
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication", Version = "v1" });
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication v1"));
}
// 启用 CORS 中间件
app.UseCors("AllowLocalhost"); app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization();
// 启用静态文件中间件
app.UseStaticFiles(); // 默认提供 wwwroot 下的静态文件 app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

控制器代码:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Runtime.InteropServices; namespace WebApplication.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ServerStatusController : ControllerBase
{
// 定义性能计数器来获取 CPU 使用率
private readonly PerformanceCounter _cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); [HttpGet("status")]
public async Task GetServerStatus()
{
// 设置响应头,声明是 SSE 流
Response.ContentType = "text/event-stream";
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Connection", "keep-alive"); // 获取当前进程的基本信息
var process = Process.GetCurrentProcess(); await using var writer = new StreamWriter(Response.Body, Encoding.UTF8, leaveOpen: true); while (!HttpContext.RequestAborted.IsCancellationRequested)
{
// 获取 CPU 使用率
var cpuUsage = _cpuCounter.NextValue(); // CPU 使用率百分比
var memoryUsage = process.WorkingSet64 / (1024 * 1024); // 内存使用(MB)
var uptime = (DateTime.Now - process.StartTime).ToString(@"hh\:mm\:ss"); // 服务器运行时间 // 获取系统的磁盘使用情况
var diskUsage = GetDiskUsage(); // 获取系统的网络使用情况(假设 Windows 上可用)
var networkUsage = new NetworkUsage().GetNetworkUsage(); // 构建状态信息
var status = new
{
CPU = $"{cpuUsage:F2}%",
Memory = $"{memoryUsage} MB",
Uptime = uptime,
DiskUsage = diskUsage,
NetworkUsage = networkUsage,
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
}; // 将状态信息转化为 JSON 格式并发送
await writer.WriteLineAsync($"data: {System.Text.Json.JsonSerializer.Serialize(status)}\n");
await writer.FlushAsync(); // 确保立即推送数据
await Task.Delay(1000*2); // 每秒更新一次
}
} // 获取磁盘使用情况(Windows)
private string GetDiskUsage()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var drive = DriveInfo.GetDrives().FirstOrDefault(d => d.IsReady);
if (drive != null)
{
return $"{drive.TotalFreeSpace / (1024 * 1024 * 1024)} GB free of {drive.TotalSize / (1024 * 1024 * 1024)} GB";
}
}
return "N/A";
} }
}

网路获取类:

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Text; public class NetworkUsage
{
public string GetNetworkUsage()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return GetWindowsNetworkUsage();
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return GetLinuxNetworkUsage();
}
else
{
return "Unsupported operating system.";
}
} private string GetWindowsNetworkUsage()
{
try
{
// 获取 PerformanceCounter 支持的所有网络接口实例
var category = new PerformanceCounterCategory("Network Interface");
var validInstances = category.GetInstanceNames(); // 返回支持的实例名称 // 获取系统中活动的网络接口
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
.Where(ni => ni.OperationalStatus == OperationalStatus.Up
&& validInstances.Contains(ni.Description)) // 匹配实例名称
.ToList(); if (!interfaces.Any())
{
return "No valid network interfaces found.";
} var result = new StringBuilder(); foreach (var iface in interfaces)
{
try
{
var networkIn = new PerformanceCounter("Network Interface", "Bytes Received/sec", iface.Description);
var networkOut = new PerformanceCounter("Network Interface", "Bytes Sent/sec", iface.Description); var receivedBytes = networkIn.NextValue() / (1024 * 1024); // 转换为 MB
var sentBytes = networkOut.NextValue() / (1024 * 1024); // 转换为 MB result.AppendLine($"{iface.Name} ({iface.Description}): {receivedBytes:F2} MB received, {sentBytes:F2} MB sent per second");
}
catch (Exception ex)
{
result.AppendLine($"Error retrieving data for {iface.Name} ({iface.Description}): {ex.Message}");
}
} return result.ToString();
}
catch (Exception ex)
{
return $"Error retrieving network usage on Windows: {ex.Message}";
}
} private string GetLinuxNetworkUsage()
{
try
{
if (!File.Exists("/proc/net/dev"))
return "Unable to access network statistics (Linux only)"; string[] lines = File.ReadAllLines("/proc/net/dev"); var networkInterfaces = lines
.Skip(2) // 跳过前两行标题
.Select(line => line.Trim())
.Where(line => line.Contains(":"))
.Select(ParseNetworkLine)
.ToList(); return string.Join("\n", networkInterfaces.Select(ni =>
$"{ni.Interface}: {ni.ReceivedMB:F2} MB received, {ni.TransmittedMB:F2} MB sent"));
}
catch (Exception ex)
{
return $"Error retrieving network usage on Linux: {ex.Message}";
}
} private (string Interface, double ReceivedMB, double TransmittedMB) ParseNetworkLine(string line)
{
var parts = line.Split(new[] { ' ', ':' }, StringSplitOptions.RemoveEmptyEntries);
string interfaceName = parts[0]; long receivedBytes = long.Parse(parts[1]); // 接收字节
long transmittedBytes = long.Parse(parts[9]); // 发送字节 return (
Interface: interfaceName,
ReceivedMB: receivedBytes / (1024.0 * 1024.0), // 转换为 MB
TransmittedMB: transmittedBytes / (1024.0 * 1024.0) // 转换为 MB
);
}
}

2. 前端展示 SSE

在浏览器中使用 JavaScript 接收服务器数据:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Status</title>
</head>
<body>
<h1>Server Status</h1>
<div id="status">
<p>Loading...</p>
</div> <script>
const eventSource = new EventSource('/api/serverstatus/status'); eventSource.onmessage = function (event) {
const status = JSON.parse(event.data); document.getElementById('status').innerHTML = `
<p><strong>CPU Usage:</strong> ${status.CPU}</p>
<p><strong>Memory Usage:</strong> ${status.Memory}</p>
<p><strong>Uptime:</strong> ${status.Uptime}</p>
<p><strong>Disk Usage:</strong> ${status.DiskUsage}</p>
<p><strong>Network Usage:</strong> ${status.NetworkUsage}</p>
<p><strong>Timestamp:</strong> ${status.Timestamp}</p>
`;
}; eventSource.onerror = function (error) {
console.error("Error occurred: ", error);
};
</script>
</body>
</html>

运行网站后效果如下,2s刷新一次:

比较 SSE 和 WebSocket

特性 SSE WebSocket
通讯方式 服务器 -> 客户端 双向通信
使用协议 HTTP/1.1 TCP/HTTP/1.1 or HTTP/2
解析方式 浏览器内置,无需额外应用 需要设计应用协议
应用场景 更新速度不高,如实时通知 高频发送,如游戏体验和客制游戏
应用端支持 原生支持,不需额外学习 需要客户端实现
考虑问题 支持 HTTP 跨域,比 WebSocket 更简单 需要第三方应用支持,解决处理诡机

总结

    • SSE 适合于不高频、安全性优先的场景,如通知信息。它具有以下优点:

    1. 单向通信的效率:服务器可以在需要时直接推送更新,无需客户端不断轮询,减少资源消耗。

    2. 基于 HTTP/1.1 的简单性:由于 SSE 使用标准 HTTP 请求和响应机制,无需额外的协议支持。

    3. 与现有 HTTP 基础设施的兼容性:例如,代理服务器、负载均衡器等无需特殊配置即可支持 SSE。

    • WebSocket 是一种全双工通信协议,基于 TCP 连接。它允许客户端和服务器之间实时双向通信,特别适用于高频、低延迟的应用场景,如在线游戏、实时协作编辑、股票交易和聊天应用。

    WebSocket 的特点包括:

    1. 支持子协议:例如用于消息格式的 STOMP 和用于加密传输的 WAMP。

    2. 自定义消息格式的能力:可以选择 JSON、Protobuf 或二进制数据来优化通信效率。

    3. 实时交互场景的处理:WebSocket 的低延迟特性使其能够快速响应用户的实时交互需求。

    4. 高效的资源利用:相比于轮询或长轮询,WebSocket 使用单一持久连接,减少了频繁的 HTTP 开销。

    此外,WebSocket 在需要多客户端实时同步状态的场景中表现优异,如协作工具(文档编辑、白板)和物联网设备管理。

ASP.NET Core EventStream (SSE) 使用以及 WebSocket 比较的更多相关文章

  1. ASP.NET Core Building chat room using WebSocket

    Creating “Login form” We use here simple form where user can insert his or her preferred nick name f ...

  2. WebSocket in ASP.NET Core

    一.WebSocket WebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有1.1和1.0 ...

  3. 网络游戏开发-服务器(01)Asp.Net Core中的websocket,并封装一个简单的中间件

    先拉开MSDN的文档,大致读一遍 (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/websockets) WebSocket 是一 ...

  4. 快速搭建CentOS+ASP.NET Core环境支持WebSocket

    环境:CentOS 7.x,.net core 2 以下.net core 2安装操作为官方方法.如果你使用Docker,那么更简单了,只需要docker pull microsoft/dotnet就 ...

  5. Real-time chart using ASP.NET Core and WebSocket

    Solution in glance The following diagram illustrates our solution where IoT device reports readings ...

  6. WebSocket In ASP.NET Core(一)

    .NET-Core Series Server in ASP.NET-Core DI in ASP.NET-Core Routing in ASP.NET-Core Error Handling in ...

  7. 在Asp.net Core中使用中间件来管理websocket

    介绍 ASP.NET Core SignalR是一个有用的库,可以简化Web应用程序中实时通信的管理.但是,我宁愿使用WebSockets,因为我想要更灵活,并且与任何WebSocket客户端兼容. ...

  8. ASP.NET Core 集成 WebSocket

    1. 环境 AspNetCore Web 2.0 (MVC) Windows 10 IIS 10 Express/IIS VS 2017 2.如何配置 在已有的或者新创建的 AspNet Core M ...

  9. ASP.NET Core 中的 WebSocket 支持(转自MSDN)

    本文介绍 ASP.NET Core 中 WebSocket 的入门方法. WebSocket (RFC 6455) 是一个协议,支持通过 TCP 连接建立持久的双向信道. 它用于从快速实时通信中获益的 ...

  10. asp.net core系列 70 即时通迅-WebSocket+Redis发布订阅

    一.概述 在asp.net core 中可以用WebSocket 或asp.net core SignalR来开发即时通迅.在项目中由于开发前后端分离,对于SignalR前端技术人员不想依赖juqer ...

随机推荐

  1. debian 12 编译 vlc/libvlc 支持 rtsp

    debian 官方从11开始,不再提供支持 rtsp 的 VLC deb 包,通过 libvlc 播放 rtsp 也无法实现,因此需要自己编译. # 安装编译环境,编译依赖库以及 contrib 第三 ...

  2. 【USB3.0协议学习】Topic2·USB3.0的LTSSM分析

    一.什么是LTSSM,处于USB层次中的哪个位置? LTSSM是链路训练状态机的简称,位于USB3.0协议的link layer,共有12种状态,在链路的两端,也就是Downstream port和U ...

  3. Excel读写之xlrd模块

    1.1.xlrd模块介绍 xlrd:用于读取Excle数据文件将返回的数据对象放到内存中,然后查询数据文件对象的相关信息. xlwt:用于在内存中生成新的数据文件对象,处理完成后写入到Excel数据文 ...

  4. mysql替换内容

    UPDATE storage SET guige = REPLACE(guige, '×', 'x')

  5. IntelliJ IDEA 2024激活码(亲测有效,仅供学习和交流)

    资源是从官网购买,仅供学习和交流 激活码链接地址

  6. 《机器学习实战》(Machine Learning in Action)

    地址: https://www.manning.com/books/machine-learning-in-action 代码地址: https://www.manning.com/downloads ...

  7. Linux 日志管理基础

    目录 基本介绍 日志的存放 存放目录与存放内容 举例说明 日志管理服务: rsyslogd 功能与配置 检查自启动 配置文件 /etc/rsyslog.conf 修改配置文件 基本介绍 日志文件是重要 ...

  8. 题解:CF1301B Motarack's Birthday

    CF1301D Time to Run 题解 思维题. 分析 把一个格子视作一个点,每个点的度数都是偶数,所以这是一张欧拉图.而需要走遍整个方格图,可以证明只要 \(k\) 不超过 \(4nm-2n- ...

  9. python基础之__init__.py

    如何使用 在 Python 中,当一个目录被作为包来使用时,它会在包中寻找一个名为 __init__.py 的文件.如果该文件存在,Python 会将它加载到内存中,并在其中执行所有的代码. __in ...

  10. 2023NOIP A层联测23 T2 涂鸦

    2023NOIP A层联测23 T2 涂鸦 模拟赛一道博弈,剩下仨全期望,我: 思路 其实我也不是很会 考虑设 \(f_{mst}\),为 \(n*m\) 个格被压成一个二进制 \(mst\),转移到 ...