应用场景:前端页面发起一个websocket请求与后端进行实时通讯。后端监听某端口获取数据,将监听到的数据加工处理,通过websocket发送到前端。

  这里只提供后台的处理方案仅供参考。
  1.后端监听某端口,获取数据并数据处理。可以在Global中单独开启一个后台线程用来监听数据。数据处理交给datawatcher的单例对象来处理。由于是监控端口的工作,一般采用独立线程在项目启动的时候就进行监听,因此可以将代码放在Application_start中。
  2.datawatcher对象,它需要有个委托队列,允许外部进行注册和删除。原理同事件类似,但是采用委托队列并将其暴露主要是为了容错,因为可能会用到删除委托队列中的委托。严谨的事务逻辑应该用事件(事件有点久了,忘了事件怎么写了)
  3.webapi建立websocket。

  以下为代码部分:

      protected void Application_Start()
{
//webapi相关配置
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//todo 初始化datawatcher 监听实时数据
//伪造实时数据
Thread bgthread = new Thread(Start);
bgthread.IsBackground = true;
bgthread.Start(); }
    //除红色部分均为模拟实时数据,正常数据应是通过监听获取
private void Start()
{
Random r = new Random();
var datawatcher = DataWatcher.GetInit();
while (true)
{
dynamic dyobj = new ExpandoObject();
dyobj.stationid = "01";
dyobj.value = new { at = DateTime.Now.ToTimeStampMS(), value = (r.Next(0, 1024)) }.ToJson();
datawatcher.Updata(dyobj, CallBackType.Record);
Thread.Sleep();
}
}
//使用单例是为了确保项目中使用的是同一个对象,防止注册的callback丢失
public class DataWatcher
{
private static DataWatcher _dataWatcher;
static readonly object locker = new object(); private DataWatcher()
{
CallBackAlarms = new List<DynamicDelegate>();
CallBackRecords = new List<DynamicDelegate>();
CallBackSpes = new List<DynamicDelegate>();
} public static DataWatcher GetInit()
{
if (_dataWatcher == null)
{
lock (locker)
{
if (_dataWatcher == null)
{
_dataWatcher = new DataWatcher();
}
}
}
return _dataWatcher;
} public WebSocket WebSocket { get; set; } public List<DynamicDelegate> CallBackSpes; public List<DynamicDelegate> CallBackRecords; public List<DynamicDelegate> CallBackAlarms; /// <summary>
/// 更新数据
/// </summary>
/// <param name="dyobj"></param>
/// <param name="type"></param>
public void Updata(dynamic dyobj, CallBackType type)
{
switch (type)
{
case CallBackType.Spe:
if (CallBackSpes.Count > )
{
foreach (var callback in CallBackSpes)
{
callback.Invoke(dyobj, WebSocket);
}
}
//CallBackSpes?.Invoke(dyobj, WebSocket);
break;
case CallBackType.Record:
if (CallBackRecords.Count > )
{
foreach (var callback in CallBackRecords)
{
callback.Invoke(dyobj, WebSocket);
}
}
//CallBackRecords?.Invoke(dyobj, WebSocket);
break;
case CallBackType.Alarm:
if (CallBackAlarms.Count > )
{
foreach (var callback in CallBackAlarms)
{
callback.Invoke(dyobj, WebSocket);
}
}
//CallBackAlarms?.Invoke(dyobj, WebSocket);
break;
}
} }
public enum CallBackType
{
Spe, Record, Alarm
} public delegate void DynamicDelegate(dynamic dynamic, WebSocket ws);
   public class subscribeController : ApiController
{
private DataWatcher dataWatcher;
public subscribeController()
{
dataWatcher = DataWatcher.GetInit();//获取Datawatcher实例
if (!dataWatcher.CallBackRecords.Contains(RealtimeDataCallBack))
{
dataWatcher.CallBackRecords.Add(RealtimeDataCallBack);
         //注册委托,若无特殊的场景需求可以将委托列表封装为事件event
} } public HttpResponseMessage Get()
{
       //对原请求返回101变更协议状态码,开启一个异步线程通过websocket进行数据传输
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(CreateWS);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
} private async Task CreateWS(AspNetWebSocketContext aspNetWebSocketContext)
{
var socket = aspNetWebSocketContext.WebSocket; dataWatcher.WebSocket = socket; //将上下文中的websocket注入到Datawatcher中
//防止后台线程将socket释放
while (true)
{
if (socket.State != WebSocketState.Open)
{
break;
}
}
} private void WriteJson(string res, WebSocket socket)
{
lock (this)
{
res = res.Replace("\\", "").Replace("\"{", "{").Replace("\"}", "}").Replace("\"[", "[");
ArraySegment<byte> bytes = new ArraySegment<byte>(Encoding.UTF8.GetBytes(res));
if (socket != null && socket.State == WebSocketState.Open)
{
socket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
Thread.Sleep();
}
} } private void RealtimeDataCallBack(dynamic dyobj, WebSocket ws)
{
var templist = new List<string>();
templist.Add(dyobj.value);
var temp = new Dictionary<string, string>();
temp.Add(dyobj.stationid, new { doseRateNaI = templist.toListString() }.ToJson());
var res = new { datastreamUpdate = temp.ToJson() }.ToJson();
WriteJson(res, ws);
}
}

  Datawatcher的设计是作为一个中间人的角色,既要提供调用回调的方法,也要提供websocket 的注入接口。考虑到Datawatcher是单例,故采用属性的方式为websocket提供外部访问(此处的设计有待商榷)。在外部注入了websocket之后,在合适的位置进行回调。注意:数据的处理是在监听时进行一次封装处理,在进行回调的时候再进行一次封装处理,如果类型是固定统一的可以只在监听的时候进行封装处理即可。同时应注意对回调方法中websocket的空判断增强健壮性。
  以上代码为提供思路的简易demo,使用时需要根据自己场景需求相应修改。

------------------------------------------------------------------------------分隔符----------------------------------------------------------------------------

存疑1:webapi中的controller是否是单例?(当然Mvc等的控制器也是同问)

如果是单例,Datawatcher的委托注册可以在控制器的构造方法中注册(前提是允许访问公有的构造方法);如果不是单例,可以在post或者get中注册委托,此时一定要判断委托是否被注册过,避免重复注册。这是采用委托列表,而不用事件的一个原因。事件的内部委托链表是不允许外部直接访问的,因此判断是否注册不是很方便。如果事件允许不判断,直接注册或注销委托不会抛出异常,即如果已经注册不会重复注册,保证委托唯一性,如果已经注销,再次注销不会抛出异常,那么Datawatcher中的设计应该采用事件,确保封装的安全性。(由于未对事件做深入了解,故此处存疑暂时保留)

释疑1:经测试,controller不是单例(起码webapi中不是)。测试方法,给控制器添加一个属性和一个公有构造方法,构造方法给属性赋一个初始值。写一个访问方法,每次给属性加1,并返回。通过页面进行访问,查看api返回结果。或者打一个断点在构造方法中,通过页面访问。

经测试,事件event中的委托链表在注销未注册的委托方法的时候不会抛出异常。注销的时候,会在委托链表中查找对应的方法,如果找到则注销掉,如果未找到也不会抛出异常。

参考资料:http://blog.csdn.net/snakorse/article/details/44208351

存疑2:websocket的async Task 为什么使用死循环?

释疑2:websocket不是持续的,其中的代码只执行一次,为了保持实时性,他的持续需要一个死循环。循环的作用只在于保持连接不断开,真正的数据传输则是由Datawatcher来处理。每监听到一条数据,会调用一次Datawatcher的update,这个时候如果websocket是打开的,数据则会被发送,如果此时socket为空或者关闭,则放弃发送或者插入缓存中等下次打开连接再发送,这部分的处理则需要根据业务需求来判断。
强调:以上demo只提供思路,确认这种处理是可行的。关于存疑部分的思考,我会尽快验证。

------------------------------------------------------------------------------最终代码-------------------------------------------------------------------------------------------

    public class DataWatcher
{
private static DataWatcher _dataWatcher;
static readonly object locker = new object(); private DataWatcher()
{
} public static DataWatcher GetInit()
{
if (_dataWatcher == null)
{
lock (locker)
{
if (_dataWatcher == null)
{
_dataWatcher = new DataWatcher();
}
}
}
return _dataWatcher;
} public WebSocket WebSocket { get; set; }
     //委托列表被事件替代
public event DynamicDelegate CallbackRecoedEvent;
public event DynamicDelegate CallbackAlarmEvent;
public event DynamicDelegate CallbackSpeEvent; /// <summary>
/// 更新谱图
/// </summary>
/// <param name="dyobj"></param>
/// <param name="type"></param>
public void Update(dynamic dyobj, CallBackType type)
{
switch (type)
{
case CallBackType.Spe:
CallbackSpeEvent?.Invoke(dyobj,WebSocket);
break;
case CallBackType.Record:
CallbackRecoedEvent?.Invoke(dyobj,WebSocket);
break;
case CallBackType.Alarm:
CallbackAlarmEvent?.Invoke(dyobj,WebSocket);
break;
}
}
}
    public class subscribeController : ApiController
{
public HttpResponseMessage Get()
{
if (HttpContext.Current.IsWebSocketRequest)
{
HttpContext.Current.AcceptWebSocketRequest(CreateWS);
}
return new HttpResponseMessage(HttpStatusCode.SwitchingProtocols);
} private async Task CreateWS(AspNetWebSocketContext aspNetWebSocketContext)
{
var socket = aspNetWebSocketContext.WebSocket;
var dataWatcher = DataWatcher.GetInit();
dataWatcher.WebSocket = socket;
//
dataWatcher.CallbackRecoedEvent -= RealtimeDataCallBack;
dataWatcher.CallbackRecoedEvent += RealtimeDataCallBack;
//防止后台线程将socket释放
while (true)
{
if (socket.State != WebSocketState.Open)
{
break;
}
}
} private void WriteJson(string res, WebSocket socket)
{
lock (this)
{
res = res.Replace("\\", "").Replace("\"{", "{").Replace("\"}", "}").Replace("\"[", "[");
ArraySegment<byte> bytes = new ArraySegment<byte>(Encoding.UTF8.GetBytes(res));
if (socket != null && socket.State == WebSocketState.Open)
{
socket.SendAsync(bytes, WebSocketMessageType.Text, true, CancellationToken.None);
Thread.Sleep();
}
}
} private void RealtimeDataCallBack(dynamic dyobj, WebSocket ws)
{
var templist = new List<string>();
templist.Add(dyobj.value);
var temp = new Dictionary<string, string>();
temp.Add(dyobj.stationid, new { doseRateNaI = templist.toListString() }.ToJson());
var res = new { datastreamUpdate = temp.ToJson() }.ToJson();
WriteJson(res, ws);
}
}

  由于确定了controller非单例,所以没有必要在构造中为Datawatcher注册事件,可以在其他合适的地方注册。

Webapi实现websocket实时通讯的更多相关文章

  1. Uniapp使用GoEasy实现websocket实时通讯

    Uniapp作为近来最火的移动端开发技术,一套代码,可以打包成Android/iOS app和各种平台的小程序,可谓是没有最方便只有更方便. GoEasy上架DCloud Uniapp插件市场已经有一 ...

  2. 微信小程序使用GoEasy实现websocket实时通讯

    不需要下载安装,便可以在微信好友.微信群之间快速的转发,用户只需要扫码或者在微信里点击,就可以立即运行,有着近似APP的用户体验,使得微信小程序成为全民热爱的好东西~ 同时因为微信小程序使用的是Jav ...

  3. 使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯

    前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款 ...

  4. 基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯

    https://www.cnblogs.com/wt645631686/p/7366924.html 前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交 ...

  5. 基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式

    API/RPC/webSocket三个看起来好像没啥相同的地方,在开发时,服务端,客户端实现代码也大不一样 最近整理了一下,通过动态代理的形式,整合了这些开发,都通过统一的接口约束,服务端实现和客户端 ...

  6. Java开发之使用websocket实现web客户端与服务器之间的实时通讯

    使用websocket实现web客户端与服务器之间的实时通讯.以下是个简单的demo. 前端页面 <%@ page language="java" contentType=& ...

  7. PHP基于TP5使用Websocket框架之GatewayWorker开发电商平台买家与卖家实时通讯

    前段时间公司提了一个新的需求,在商品的详情页要实现站内买家和商品卖家实时通讯的功能以方便沟通促成交易,要开发此功能当时首先考虑到的就是swoole和workerman了,从网上大概了解了一下关于这两款 ...

  8. 微信小程序实时通讯(websocket)问题

    这几天值班忙的不要不要,人工智能这块看的都是零零散散,今天就来写写小程序的实时通讯吧.小程序端://这个是连接 lianjie:function(){ var socketOpen = false / ...

  9. HTML5+NodeJs实现WebSocket即时通讯

    声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢! 最近都在学习HTML5,做canvas游戏之类的,发现HTML5中除了canvas这个强大的工具外,还有WebSocket也很值得注意.可 ...

随机推荐

  1. asp.net中c#求百分比

    double m= 50;double n= 100; Response.Write((m/ (m+ n)).ToString("0%"));Response.Write((m/ ...

  2. go-wingui 2018 全新 v2.0 版本发布,包含重大更新!

    go-wingui 2018 全新 v2.0 版本发布,包含重大更新!使用新版CEF内核Chromium 63.0.3239.109,页面可以使用最新的css3,html5技术.使用delphi7重写 ...

  3. 至Linux-2.6.32编译内核ipset-6.23坎坷的经历

    新的版本号ipset 上周,一名医生在儿童医院等待一段差距叫做数量.接受NetfilterPush信息的邮件列表,列表ipset最新6.23版本号的新功能,非常喜欢我现在需要的是,特别是timeout ...

  4. WPF的两棵树与绑定

    原文:WPF的两棵树与绑定   先建立测试基类 public class VisualPanel : FrameworkElement { protected VisualCollection Chi ...

  5. 44个 Javascript 变态题解析——分分钟让你怀疑人生

    原题来自: http://javascript-puzzlers.herokuapp.com/ 第1题 ["1", "2", "3"].ma ...

  6. virtualbox下ubuntu共享文件夹自动挂载

    1.若想删除挂载,可执行命令 umount -f /mnt/share 2.若想开机自动挂载,可以在 /etc/fstab 文件末添加一项     sharing /mnt/share vboxsf  ...

  7. C#WPF 如何绘制几何图形 图示教程 绘制sin曲线 正弦 绘制2D坐标系 有图有代码

    原文:C#WPF 如何绘制几何图形 图示教程 绘制sin曲线 正弦 绘制2D坐标系 有图有代码 C#WPF 如何绘制几何图形? 怎么绘制坐标系?绘制sin曲线(正弦曲线)? 这离不开Path(Syst ...

  8. debian8 root无法远程登录解决办法

    改一下root 密码,应该就能本地登录了. 改/etc/ssh/sshd.conf,然后重启ssh就能远程登录了:PermitRootLogin yesPermitEmptyPasswords no

  9. C 语言main 函数终极探秘(&& 的含义是:如果 && 前面的程序正常退出,则继续执行 && 后面的程序,否则不执行)

           所有的C程序必须定义一个称之为main的外部函数,这个函数是程序的入口,也就是当程序启动时所执行的第一个函数,当这个函数返回时,程序也将终止,并且这个函数的返回值被看成是程序成功或失败的 ...

  10. 【转载】如何使用docker部署c/c++程序

    原文地址:https://blog.csdn.net/len_yue_mo_fu/article/details/80189035 Docker介绍 Docker是一个开源的容器引擎,它有助于更快地交 ...