在开发环境中,对于实时数据流的需求非常常见,最常用的技术包括 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. RabbitMQ协议基础及C++和Java混合开发

    目前面对大多数的需要在异构系统间进行消息传递技术路线,大多会选择socket或webservice.这两种技术的共同特点是耦合紧,调试依赖双方同步,但是效率高.除此以外,使用消息队列(MQ)的应用场景 ...

  2. 什么是WebRTC

    背景:webrtc web real-time communication 实时通信标准,提供了音视频通话系统的能力. 应用场景: 点对点视频聊天,如 微信视频,等实时视频通话应用. 多人视频会议,企 ...

  3. mysql进阶-SQL优化篇

    SQL优化 -插入数据 批量插入:(一次尽量不超过1000条) Insert into tb test values(1,'Tom'),(2,'cat'),(3, Jerny'); 手动事务提交: s ...

  4. .NET使用Graphql的演示

    Graphql是什么?先来一段AI给的回答: GraphQL是一种为API设计的查询语言,与REST相比,它提供了更高效.强大和灵活的方法来与数据交互.GraphQL由Facebook于2012年开发 ...

  5. 探索 Kubernetes 持久化存储之 Rook Ceph 初窥门径

    在 Kubernetes 生态系统中,持久化存储是支撑业务应用稳定运行的基石,对于维护整个系统的健壮性至关重要.对于选择自主搭建 Kubernetes 集群的运维架构师来说,挑选合适的后端持久化存储解 ...

  6. 一文读懂 Prometheus 长期存储主流方案

    嘉宾 | 霍秉杰 整理 | 西京刀客 出品 | CSDN 云原生 Prometheus 作为云原生时代崛起的标志性项目,已经成为可观测领域的事实标准.Prometheus 是单实例不可扩展的,那么如果 ...

  7. Linux利用ftp命令上传下载文件

    Linux中如何使用ftp命令,包括如何连接ftp服务器,上传or下载文件以及创建文件夹.虽然现在有很多ftp桌面应用(例如:FlashFXP),但是在服务器.SSH.远程会话中掌握命令行ftp的使用 ...

  8. 在vue中引入外部的css文件

    在vue中引入外部的css文件在项目的src文件下,新建一个style文件夹,存放css文件. 1. 全局引入将外部的css文件放到style文件下,引入外部文件只需在main.js文件中 impor ...

  9. docker网络管理--项目三

    一.Docker网络概念 1.网络驱动 Docker 网络子系统使用可插拔的驱动,默认情况下有多个驱动程序,并提供核心联网功能. bridge:桥接网络,这是默认的网络驱动程序(不指定驱动程序创建的容 ...

  10. Python3开启简易服务器

    nohup python3 -m http.server 3000 2>&1 &