一、项目的核心说明

1、Fleck这个是实现websocket一个比较简单第三方组件,它不需要安装额外的容器。本身也就几个接口可供调用。

2、项目是基于.net framework 4.7.2 ,在vs2019上开发的,没试过在低版本上运行。但是代码上没怎么用到新特性,所以估计是可以被低版本使用的。

3、这个项目并不是真实项目,也就是玩一下,但是对熟悉Fleck或者了解及时聊天,应该有一丁点的启发作用。

二、Fleck 的简要说明(https://github.com/statianzo/Fleck)

1、最简单、最常用的调用方法:(ws://172.10.3.4:8111改成您的服务器本地IP和端口)

//控制台代码
var server = new WebSocketServer("ws://172.10.3.4:8111");
server.Start(socket =>
{
socket.OnOpen = () => Console.WriteLine("产生连接处理");
socket.OnClose = () => Console.WriteLine("连接断开处理");
socket.OnMessage = (message) => {
//1、此方法用于接收客户端发送来的消息
//2、可以做一些自己的操作,例如存入数据库
//3、为了响应客户端,一般会使用下面的send函数,返回响应结果。
socket.Send(message);
  }
});

2、Fleck本身只负责帮你单线联系。也就是客户端A和服务器建立连接后,会产生一个IWebSocketConnection,也就是上面代码中socket变量的类型,它包含了接收方法、发送方法,但是都仅限于单一连接内。至于客户端A想发送消息给客户端B、C、D亦或者想群发,不好意思Fleck本身不Care。。。当然了那并不是这个功能就不能实现了,只是要开发者自己去把每一个IWebSocketConnection存储起来,并管理他们的生存周期,通过自己的代码去实现客户端A给B发信息或者群发。

3、Fleck不需要额外的容器或进程来运行,它随着IIS网站运行,也就是在w3wp.exe。至于它是怎么运行的,目前我还没有去看源码,后期有时间再瞧瞧。

三、聊天室源码位置

1、GitHub:https://github.com/DisSunRestart2020/DisSunChat

2、码云:https://gitee.com/dissun/DisSunChat

3、微信扫码演示(网络时好时坏)

四、核心代码说明。

1、IWebSocketHelper接口。因为一开始,我是想用多个插件来实现聊天室,所以想用一个接口来做行为封装。结果完成了Fleck之后,发现其他的操作模式都不太相同,很难封装就放弃了,但是保留这个接口,是为了体现扩展性。

    /// <summary>
/// 所有调用WebSocket的帮助类必须遵从的协议
/// </summary>
public interface IWebSocketHelper
{
/// <summary>
/// websocket连通后触发事件
/// </summary>
event SwitchEventHandler WsOpenEventHandler;
/// <summary>
/// websocket连接关闭后触发事件
/// </summary>
event SwitchEventHandler WsCloseEventHandler;
/// <summary>
/// websocket监听到消息后触发事件
/// </summary>
event ListenEventHandler WsListenEventHandler;
/// <summary>
/// websocket响应处理事件
/// </summary>
event ResponseTextEventHandler WsResponseTextEventHandler;
/// <summary>
/// 聊天室在线人数
/// </summary>
int PlayerCount
{
get;
set;
}
/// <summary>
/// websocket初始化
/// </summary>
void WebSocketInit(); /// <summary>
/// 向全员发送信息
/// </summary>
/// <param name="wsocketMsg"></param>
void SendMessageToAll(WebSocketMessage wsocketMsg);
/// <summary>
/// 向自己发送信息
/// </summary>
/// <param name="wsocketMsg"></param>
void SendMessageToMe(WebSocketMessage wsocketMsg);
}

接口中用到了4个委托事件,这里稍微简单的复习一下委托和事件。

①委托从使用形式来说,是指我们可以把一个函数作为参数进行传递,例如我们在解一道数学题,条件和要求都是一样的,但是求解的过程可以多种多样,通过委托就可以在不改变主体程序的同时,把不同的求解过程,封装到不同的函数中,然后把函数作为参数传入主体程序。

②委托从模式的角度来说,是在实现观察者模式。订阅者\观察者告诉发布者\主题,如果发生了某一特定事情该怎么处理,“怎么处理”的过程就是委托方法的内容

③事件其实就是一个委托,都说事件是一个特殊委托,特殊在哪里,特殊在它对委托增加了约束,让你不能毫无顾忌的使用委托,这是为了保证封装性。

④上面的4个事件,其实我可以直接换成4个委托属性,对程序运行不会有太大影响。但是为什么要使用事件,还是第三点的封装性。事件本来的用意,是达到特定条件后让发布者自己来触发委托方法的执行,但是如果使用委托属性,订阅者本身就可以进行调用,封装性就很差。

⑤上面的4个事件,分别是订阅者告诉Fleck中心,新长连接接通怎么办、长连接断开怎么办、客户端发来消息怎么办、要返回客户端的消息怎么转换。

2、Fleck类的实现。Fleck实现了IWebSocketHelper接口,这是这个项目的核心代码。

 public class FleckHelper:IWebSocketHelper
{
/// <summary>
/// websocket连通后触发事件
/// </summary>
public event SwitchEventHandler WsOpenEventHandler;
/// <summary>
/// websocket连接关闭后触发事件
/// </summary>
public event SwitchEventHandler WsCloseEventHandler;
/// <summary>
/// websocket监听到消息后触发事件
/// </summary>
public event ListenEventHandler WsListenEventHandler;
/// <summary>
/// websocket反馈客户端的文本处理事件
/// </summary>
public event ResponseTextEventHandler WsResponseTextEventHandler;
/// <summary>
/// 聊天室在线人数
/// </summary>
public int PlayerCount
{
get;
set;
}
/// <summary>
/// websocket已经连通的连接集合
/// </summary>
private Hashtable socketListHs = new Hashtable(); public void WebSocketInit()
{ string websocketPath = Utils.GetConfig("websocketPath");
WebSocketServer wsServer = new WebSocketServer(websocketPath); wsServer.Start(socket =>
{
//以下的设置,每当一个新连接进来,都会生效。
socket.OnOpen = () => {
//自定义处理 if (this.WsOpenEventHandler != null)
{
WebsocketEventArgs args = new WebsocketEventArgs();
this.WsOpenEventHandler(this, args);
}
}; socket.OnClose = () => {
//从连接集合中移除
for (int i= socketListHs.Count-; i>=;i--)
{
if (socketListHs[i] == null)
{
socketListHs.Remove(i);
}
}
PlayerCount = socketListHs.Count;
//自定义处理
if (this.WsCloseEventHandler != null)
{
WebsocketEventArgs args = new WebsocketEventArgs();
this.WsCloseEventHandler(this, args);
}
}; socket.OnMessage = (message) =>
{
ClientData cData = Utils.JsonToObject<ClientData>(message);
WebSocketMessage wsocketMsg = new WebSocketMessage(socket.ConnectionInfo.ClientIpAddress, socket.ConnectionInfo.ClientPort.ToString(), socket.ConnectionInfo.Id.ToString("N"), cData); if (Convert.ToBoolean(cData.IsConnSign))
{
//收到用户上线信息,更新socket列表
if (!socketListHs.ContainsKey(cData.IdentityMd5))
{
socketListHs.Add(cData.IdentityMd5, socket);
}
else
{
socketListHs[cData.IdentityMd5] = socket;
}
PlayerCount = socketListHs.Count;
} if (this.WsListenEventHandler != null)
{
WebsocketEventArgs args = new WebsocketEventArgs();
args.WebSocketMessage = wsocketMsg;
this.WsListenEventHandler(this, args);
}
}; });
} /// <summary>
/// 向全员发送信息
/// </summary>
/// <param name="wsocketMsg"></param>
public void SendMessageToAll(WebSocketMessage wsocketMsg)
{
string resultData = "";
if (this.WsResponseTextEventHandler != null)
{
WebsocketEventArgs args = new WebsocketEventArgs();
args.WebSocketMessage = wsocketMsg;
this.WsResponseTextEventHandler(this, args);
resultData = args.ResultDataMsg;
} if (!string.IsNullOrWhiteSpace(resultData))
{
foreach (DictionaryEntry dey in socketListHs)
{
IWebSocketConnection subConn = (IWebSocketConnection)dey.Value;
subConn.Send(resultData);
}
}
} }

①代码中IdentityMd5是我自己通过各种本地信息算出来的身份标识,因为在在微信上运行cookie不稳定,不能在本地存储任何信息,只能通过算法把环境信息合成一个唯一值。

②前面说过,Fleck只负责单线联系,每一个连接都是一个IWebSocketConnection,所以我需要把IdentityMd5和IWebSocketConnection存起来,方便索引调用。

③socket.OnMessage中,socketListHs是存储IWebSocketConnection的集合,每次有消息发来,如果判断是新的IdentityMd5就会存起来。

④socket.OnClose 中,如果有连接断开了,IWebSocketConnection就会为null,所以遍历集合,然后把值为null剔除就能保持socketListHs集合的有效性

⑤SendMessageToAll函数用于群发消息,遍历集合中所有的对象,调用每个IWebSocketConnection.Send(),就可以把消息发送出去。如果想从客户端A发到客户端B,也得利用IdentityMd5来做文章,本项目由于是聊天室并不需要这个功能,所以就省略了。

⑥socket.OnOpen、socket.OnClose、socket.OnMessage都是Fleck自行触发的事件,前面一开始我们介绍过了,例如OnOpen中,如果我们需要当建立新连接时记录日志,我们就可以对WsOpenEventHandler赋值(本项目在Global.asax的Application_Start中用lambda赋值),如下:

        protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RouteConfig.RegisterRoutes(RouteTable.Routes);
App_Start.FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);      ChatService chatService = new ChatService();
try
{ IWebSocketHelper helper = new FleckHelper();//用FleckHelper来实例化IWebSocketHelper 接口。
helper.WebSocketInit(); helper.WsOpenEventHandler +=(sender, args)=>{
Utils.SaveLog("WebSocket已经开启2");
}; helper.WsCloseEventHandler += (sender, args) => {
Utils.SaveLog("WebSocket已经关闭2");
}; helper.WsListenEventHandler += (sender, args) => {
Utils.SaveLog("WebSocket监听到了消息2");
if (!Convert.ToBoolean(args.WebSocketMessage.ClientData.IsConnSign))
{
chatService.CreateChatInfo(args.WebSocketMessage);
args.WebSocketMessage.ClientData.SMsg = Utils.ReplaceIllegalWord(args.WebSocketMessage.ClientData.SMsg);
}
else
{
string clientName = args.WebSocketMessage.CIp + ":" + args.WebSocketMessage.CPort;
string traceInfo = string.Format("{0} 加入聊天室(共{1}人在线)", clientName, helper.PlayerCount);
args.WebSocketMessage.ClientData.SMsg = traceInfo;
}
//给所有人转发消息
helper.SendMessageToAll(args.WebSocketMessage);
}; helper.WsResponseTextEventHandler += (sender, args) => {
string jsonStr = Utils.ObjectToJsonStr(args.WebSocketMessage);
args.ResultDataMsg = jsonStr;
}; }
catch(Exception ex)
{
Utils.SaveLog("发现了错误:" + ex.Message);
}
}

3、在html端连接Fleck,只须一个一个WebSocketJs.js文件。

var socket;
var websocketInit = function (wsPath) {
if (typeof (WebSocket) === "undefined") {
socket = null;
console.log("浏览器不支持websocket");
} else {
// 实例化socket
socket = new WebSocket(wsPath);
// 监听socket连接
socket.onopen = wsOnOpen;
//监听socket关闭
socket.onclose = wsOnClose;
// 监听socket错误信息
socket.onerror = wsOnError;
// 监听socket消息
socket.onmessage = wsOnMessage;
}
} var wsOnOpen = function () {
console.log("已经成功连接");
var sMsg = ""; var sendMsg = "{\"IdentityMd5\":\"" + identityMd5 + "\",\"SMsg\":\"\",\"ImgIndex\":\"" + imgIndex + "\",\"IsConnSign\":\"true\"}";
socket.send(sendMsg);
} var wsOnClose = function () {
console.log("已经关闭连接");
} var wsOnError = function (evt) {
console.log("异常:" + evt);
} var wsSend = function (sMsg) {
if (socket == null || sMsg == "") return false;
console.log("消息成功发出");
socket.send(sMsg);
}

①WebSocket是浏览器内置的一个类型,现在一般的浏览器都支持,但是还是typeof (WebSocket)来做一下判断。

②然后在启动时加载即可(ws://14.215.177.1:8111改成您的服务器公网IP和端口)。

$(function () {

    websocketInit("ws://14.215.177.1:8111");    

});

********** End *****************

上面这些也只是部分代码,需要可运行代码,请去github下载源码。

疫情当前,公司发展不顺,旧项目停摆,新项目需求不明。像当前这样的企业破产潮,我是比较紧张的,毕竟不是小年轻,技术又不是特别过硬,不上不下的焦虑的很。但是也明白,行动起来,每天让自己靠近目标一点点,是解决焦虑的不二法门。

能写一些博客,就写一些博客吧,我也不确定写这个对我自己有什么帮助,对读者都多大的帮助,就当备忘录吧。

当一个会折腾的“前浪”...

欢迎建设性的批评和指导意见。

Asp.Net Mvc基于Fleck开发的多人网页版即时聊天室的更多相关文章

  1. 基于ASP.NET MVC的快速开发平台,给你的开发一个加速度!

    基于ASP.NET MVC的快速开发平台,给你的开发一个加速度! bingo炸了 2017/4/6 11:07:21 阅读(37) 评论(0) 现在的人做事情都讲究效率,最好能达到事半功倍那种效果,软 ...

  2. ASP.NET MVC 基于角色的权限控制系统的示例教程

    上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于 ...

  3. ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上

    原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...

  4. ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则

    原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则 对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象, ...

  5. 在ASP.NET MVC应用中开发插件框架(中英对照)

    [原文] Developing a plugin framework in ASP.NET MVC with medium trust [译文] 在ASP.NET MVC应用中开发一个插件框架 I’v ...

  6. C# 动态生成word文档 [C#学习笔记3]关于Main(string[ ] args)中args命令行参数 实现DataTables搜索框查询结果高亮显示 二维码神器QRCoder Asp.net MVC 中 CodeFirst 开发模式实例

    C# 动态生成word文档 本文以一个简单的小例子,简述利用C#语言开发word表格相关的知识,仅供学习分享使用,如有不足之处,还请指正. 在工程中引用word的动态库 在项目中,点击项目名称右键-- ...

  7. asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发4- 后台模板html页面创建

    上一篇教程<asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发3-登录模块开发>完成了本项目的登录模块,登录后就需要进入后台管理首页了,需要准备一个后台模 ...

  8. asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发2-Model层建立

    上篇(asp.net mvc+jquery easyui开发实战教程之网站后台管理系统开发1-准备工作)文章讲解了开发过程中的准备工作,主要创建了项目数据库及项目,本文主要讲解项目M层的实现,M层这里 ...

  9. 转载 ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据   最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前 ...

随机推荐

  1. airtest+poco多脚本、多设备批处理运行测试用例自动生成测试报告

    一:主要内容 框架功能及测试报告效果 airtest安装.环境搭建 框架搭建.框架运行说明 airtest自动化脚本编写注意事项 二:框架功能及测试报告效果 1. 框架功能: 该框架笔者用来作为公司的 ...

  2. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之十一(四十七)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  3. C语言设计实验报告(二)

    C程序设计实验报告姓 名:赖瑾 实验地点:家 实验时间:2020年3月9日 实验项目:2.3.3 字符与ASCLL码 2.3.4 运算符与表达式的运用 2.3.5 顺序结构应用程序 3.3.1 数学函 ...

  4. 支付宝小程序云开发(Serverless)

    支付宝小程序云开发(Serverless) 博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢! 一.在支付宝账号里面开通小程序云服务 ...

  5. Django 配置JWT认证方式

    1. 安装 rest_framework + djangorestframework_simplejwt 安装djangorestframework_simplejwt :pip install dj ...

  6. 201771010113 李婷华 《面向对象程序设计(Java)》第十六周总结

    一.理论知识部分 1.程序是一段静态的代码,它应用程序执行蓝 是一段静态的代码,它应用程序执行蓝 是一段静态的代码,它应用程序执行蓝本. 2.进程是程序的一次动态执行,它对应了从代码加载.执行至执行完 ...

  7. Linux内核驱动学习(四)Platform设备驱动模型

    Linux platform设备驱动模型 文章目录 Linux platform设备驱动模型 前言 框架 设备与驱动的分离 设备(device) 驱动(driver) 匹配(match) 参考 前言 ...

  8. CF-612D The Union of k-Segments 差分

    D. The Union of k-Segments 题意 给出n个线段,以及一个数字k,让求出有哪些线段:线段上所有的点至少被覆盖了k次. 思路 假如忽略掉线段的左右端点范围,肯定是使用差分来维护每 ...

  9. 设计模式之GOF23迭代器模式

    迭代器模式Iterator /** * 自定义迭代器接口 * @author 小帆敲代码 * */public interface MyIterator {  void first();//游标置于第 ...

  10. python--常用模块calendar

    常用模块: calendar.time.datetime.timeit.os.shutil.zip.math.string 上述所有的模块使用理论上都应该先导入,string是特例 -calendar ...