开源 C# .net mvc 开发(九)websocket--服务器与客户端的实时通信
文章的目的为了记录.net mvc学习的经历。本职为嵌入式软件开发,公司安排开发文件系统,临时进行学习开发,系统上线3年未出没有大问题。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C# .net mvc 开发(一)WEB搭建_c#部署web程序-CSDN博客
开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-CSDN博客
开源 C# .net mvc 开发(三)WEB内外网访问-CSDN博客
开源 C# .net mvc 开发(四)工程结构、页面提交以及显示-CSDN博客
开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-CSDN博客
开源 C# .net mvc 开发(六)发送邮件、定时以及CMD编程-CSDN博客
开源 C# .net mvc 开发(七)动态图片、动态表格和json数据生成-CSDN博客
开源 C# .net mvc 开发(八)IIS Express轻量化Web服务器的配置和使用-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
开源 java android app 开发(十三)绘图定义控件、摇杆控件的制作-CSDN博客
主要内容:一个基于ASP.NET MVC和WebSocket的实时通信应用。
1. 应用场景
2. 核心源码分析
3. 所有源码
4. 效果演示
一、应用场景
1. 实时通信系统。
在线聊天应用:支持即时消息收发(如微信、WhatsApp)、消息状态提示(如“对方正在输入”)、已读回执等交互功能。
多人在线游戏:实时同步玩家操作(如角色移动、攻击动作),确保游戏体验的流畅性和公平性。
2. 实时数据推送。
金融交易平台:毫秒级推送股票价格、外汇汇率等市场数据,帮助投资者快速决策。
3. 体育赛事直播:动态更新比分、球员状态及比赛事件,用户无需刷新页面即可获取最新进展。
协同办公与远程交互。
文档协作工具:如 Google Docs,允许多用户同时编辑并实时同步内容。
视频会议系统:保持低延迟的语音和视频流传输,提升远程会议质量。
4. 物联网与监控系统。
智能设备控制:实时双向交互指令,如智能家居设备的远程操控。
工业监控:传输传感器数据(如温度、压力)并即时响应异常警报。
二、核心源码分析
1. WebSocket服务器 (MvcApplication类)
Global.asax文件中
public static WebSocketServer WsServer;
public static readonly ConcurrentDictionary<string, WebSocketSession> UserSessions;
关键特性:
使用SuperWebSocket库实现WebSocket服务器
监听所有网络接口(0.0.0.0:2019)
使用线程安全的ConcurrentDictionary管理用户会话
支持用户认证和消息广播
2. 会话管理机制
Global.asax文件中
// 新连接处理
UserSessions[userName] = session;// 消息发送
public static bool SendToUser(string userName, string message)// 连接清理
UserSessions.TryRemove(userEntry.Key, out _);
3. 前端通信逻辑
WebSocket连接:
index.cshtml文件中
ws = new WebSocket('ws://192.168.3.126:2019?user=' + encodeURIComponent(userName));
三. 所有源码
1. index.cshtml
@using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "uploadForm" }))
{@Html.AntiForgeryToken()<div class="form-group"><label for="file">登录名:</label><input type="text" name="name" id="name" class="form-control" /></div><div class="form-group"><label for="file">密码:</label><input type="text" name="pwd" id="pwd" class="form-control" /></div>}
<div class="form-group"><input type="text" id="ajaxmsg" placeholder="ajax消息" /><button onclick="sendAjax()">ajax发送</button>
</div><br />
<br />
<br />
<br />
<div class="form-group"><button onclick="myCon()">连接</button>
</div>
<div class="form-group"><input type="text" id="websocketmsg" placeholder="websocket消息" /><button onclick="sendWebSocket()">websocket发送</button>
</div><div id="output"></div>
<script type="text/javascript">var ws;appendToOutput("123");// 表单提交事件处理function myCon() {var userName = document.getElementById('name').value;var userPwd = document.getElementById('pwd').value;if (!userName || !userPwd) {alert('请填写所有必填字段');return false;}// 建立WebSocket连接(携带用户名参数)connectWebSocket(userName);return true;}function connectWebSocket(userName) {// 关闭现有连接if (ws) {ws.close();}// 连接到 WebSocket 服务器,携带用户名参数//ws = new WebSocket('ws://127.0.0.1:2019?user=' + encodeURIComponent(userName));ws = new WebSocket('ws://192.168.3.126:2019?user=' + encodeURIComponent(userName));ws.onopen = function () {console.log('WebSocket connection opened for user:', userName);};ws.onmessage = function (evt) {try {var data = JSON.parse(evt.data);console.log('收到消息:', data);// 处理服务器发送的消息appendToOutput('服务器: ' + data.message);switch (data.type) {case 'processing_start':case 'processing':break;case 'tts_complete':appendToOutput(text);break;case 'welcome':// 忽略欢迎消息,或者可以显示break;}} catch (e) {appendToOutput('收到: ' + evt.data);}};ws.onclose = function (event) {console.log('Connection closed.', event.code, event.reason);appendToOutput('WebSocket 连接已关闭');};ws.onerror = function (error) {console.error('WebSocket Error: ', error);appendToOutput('WebSocket 连接错误');};var msg = document.getElementById("websocketmsg").value;}// 新增:发送简单字符串(非JSON格式)function sendWebSocket(message) {message = document.getElementById("websocketmsg").value;if (!ws || ws.readyState !== WebSocket.OPEN) {console.error('WebSocket连接未就绪');return false;}try {// 直接发送字符串(如果服务器支持简单字符串协议)ws.send(message);appendToOutput('发送: ' + message);return true;} catch (error) {console.error('发送字符串失败:', error);return false;}}function appendToOutput(text) {var output = document.getElementById('output');output.innerHTML += '<p>' + text + '</p>';}function sendAjax(strCustom) {//alert(1);var userName = document.getElementById('name').value;var userPwd = document.getElementById('pwd').value;strCustom = document.getElementById("ajaxmsg").value;if (!userName || !userPwd || !strCustom) {alert('请填写所有必填字段');return false;}$.ajax({url: "/Home/sendAjax?name=" + userName + "&pwd=" + userPwd + "&str=" + strCustom,type: "get",data: {},success: function (res) {//alert(res);}, dataType: "text"});}</script>
2. Global.asax文件
using Newtonsoft.Json;
using SuperWebSocket;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;/*IP 绑定问题:Setup("127.0.0.1", port) 只监听本地回环地址。这意味着只有服务器本机上的浏览器或客户端才能建立 WebSocket 连接。
其他设备无法连接。通常需要改为 Setup("0.0.0.0", port) 来监听所有网络接口。防火墙和端口开放:端口 2019 需要在服务器防火墙中开放,外部请求才能访问。扩展性限制:单服务器:所有连接都保存在内存字典中,这意味着它无法扩展到多台服务器(无状态扩展)。如果使用负载均衡,连接会分散到不同服务器,
一台服务器上的客户端无法收到另一台服务器上客户端的广播消息。解决方案:对于需要水平扩展的场景,通常需要引入 Redis Pub/Sub 或 Azure SignalR 等背板服务来在服务器实例之间转发消息。*/namespace WebApplication1
{public class MvcApplication : System.Web.HttpApplication{public static WebSocketServer WsServer;// 按用户名存储WebSocket会话public static readonly ConcurrentDictionary<string, WebSocketSession> UserSessions= new ConcurrentDictionary<string, WebSocketSession>();protected void Application_Start(){AreaRegistration.RegisterAllAreas();FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);RouteConfig.RegisterRoutes(RouteTable.Routes);BundleConfig.RegisterBundles(BundleTable.Bundles);StartWebSocketServer();}private void StartWebSocketServer(){WsServer = new WebSocketServer();int port = 2019;WsServer.NewSessionConnected += WsServer_NewSessionConnected;WsServer.NewMessageReceived += WsServer_NewMessageReceived;WsServer.SessionClosed += WsServer_SessionClosed;//if (!WsServer.Setup("127.0.0.1", port))//if (!WsServer.Setup("0.0.0.0", port))if (!WsServer.Setup("0.0.0.0", port)){System.Diagnostics.Debug.WriteLine("WebSocket服务器设置失败");return;}if (!WsServer.Start()){System.Diagnostics.Debug.WriteLine("WebSocket服务器启动失败");return;}System.Diagnostics.Debug.WriteLine($"WebSocket服务器已启动,端口: {port}");}// 新会话连接事件处理private static void WsServer_NewSessionConnected(WebSocketSession session){try{/*// 从查询参数获取用户名var queryParams = HttpUtility.ParseQueryString(session.Path);string userName = queryParams["user"] ?? "anonymous";*/string userName = "anonymous";// 直接使用 Uri 类来解析var uri = new Uri("ws://0.0.0.0" + session.Path); // 添加虚拟协议和域名var queryParams = HttpUtility.ParseQueryString(uri.Query);userName = queryParams["user"] ?? "anonymous";// 移除该用户之前的会话(避免重复连接)if (UserSessions.TryRemove(userName, out WebSocketSession oldSession)){try{oldSession.Close();}catch { }}// 添加新会话UserSessions[userName] = session;System.Diagnostics.Debug.WriteLine($"用户 {userName} 已连接,会话ID: {session.SessionID}");// 发送欢迎消息(只发给当前用户)session.Send(JsonConvert.SerializeObject(new{type = "welcome",message = $"欢迎 {userName},连接已建立",user = userName,timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}));}catch (Exception ex){System.Diagnostics.Debug.WriteLine($"新会话连接错误: {ex.Message}");}}// 接收到新消息事件处理private static void WsServer_NewMessageReceived(WebSocketSession session, string msg){try{// 这里可以处理客户端发来的消息,如果需要的话System.Diagnostics.Debug.WriteLine($"收到消息: {msg}");}catch (Exception ex){System.Diagnostics.Debug.WriteLine($"处理消息错误: {ex.Message}");}}// 会话关闭事件处理private static void WsServer_SessionClosed(WebSocketSession session, SuperSocket.SocketBase.CloseReason reason){try{// 找到并移除该会话var userEntry = UserSessions.FirstOrDefault(x => x.Value.SessionID == session.SessionID);if (!string.IsNullOrEmpty(userEntry.Key)){UserSessions.TryRemove(userEntry.Key, out _);System.Diagnostics.Debug.WriteLine($"用户 {userEntry.Key} 已断开连接");}}catch (Exception ex){System.Diagnostics.Debug.WriteLine($"会话关闭错误: {ex.Message}");}}// 新增:向特定用户发送消息public static bool SendToUser(string userName, string message){try{if (UserSessions.TryGetValue(userName, out WebSocketSession session)){if (session.Connected){session.Send(message);System.Diagnostics.Debug.WriteLine($"消息已发送给用户: {userName}");return true;}else{// 会话已断开,清理UserSessions.TryRemove(userName, out _);return false;}}System.Diagnostics.Debug.WriteLine($"用户 {userName} 未找到或未连接");return false;}catch (Exception ex){System.Diagnostics.Debug.WriteLine($"发送给用户 {userName} 的消息失败: {ex.Message}");return false;}}protected void Application_End(){WsServer?.Stop();}}
}
3. HomeControl.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;using System.IO;
using Newtonsoft.Json; //https://www.nuget.org/packages/Newtonsoft.Json
using RestSharp;
using System.Security.Cryptography;
using WebApplication1.Models;namespace WebApplication1.Controllers
{public class HomeController : Controller{private Model1 db = new Model1();string name = "";public ActionResult Index(){/*string strAsr = myASR();string strLim = myLIM(strAsr);myTTS(strLim); */return View();}// 精确发送消息给特定用户private bool SendWebSocketMessage(string userName, object message){try{var jsonMessage = JsonConvert.SerializeObject(message);return MvcApplication.SendToUser(userName, jsonMessage);}catch (Exception ex){System.Diagnostics.Debug.WriteLine($"发送WebSocket消息给 {userName} 失败: {ex.Message}");return false;}}public ActionResult SendMessageToAllClients(string message){// 检查 WebSocket 服务器是否已启动且有客户端连接if (MvcApplication.WsServer != null && MvcApplication.WsServer.SessionCount > 0){// 向所有客户端发送消息foreach (var session in MvcApplication.WsServer.GetAllSessions()){session.Send($"Server says: {message}");}return Content($"Message '{message}' sent to all connected clients.");}return Content("No WebSocket clients connected or server not running.");}public string sendAjax(string name,string pwd,string str){// 用户认证int n = db.myuser.Where(R => R.name == name && R.pwd == pwd).Count();if (n == 0){return ""; // 认证失败直接返回,不发送WebSocket消息}if (!string.IsNullOrEmpty(str)){SendWebSocketMessage(name, str);}return str;}}
}
4. myuser的sql文本,用于创建sqlserver数据库
CREATE TABLE [dbo].[myuser] ([Id] INT IDENTITY (1, 1) NOT NULL,[name] NVARCHAR (MAX) NULL,[pwd] NVARCHAR (MAX) NULL,[type] NVARCHAR (MAX) NULL,[company] NVARCHAR (MAX) NULL,[right] NVARCHAR (MAX) NULL,PRIMARY KEY CLUSTERED ([Id] ASC)
);
四. 效果演示
1.写入用户名和密码,连接则建立websocket连接。输入websocket信息,页面显示发送的消息。
2.输入ajax信息,点击发送,IIS Express服务器上显示,收到的字符串。