ASP.NET Core的实时库: SignalR -- 预备知识
大纲
本系列会分为2-3篇文章.
- 第一篇介绍signalr的预备知识和原理
- 然后会介绍signalr和如何在asp.net core里使用signalr.
本文的目录如下:
实时web简述
大家都见过和用过实时web, 例如网页版的即时通讯工具, 网页直播, 网页游戏, 还有股票仪表板等等.
传统的web应用是这样工作的:
浏览器发送http请求到asp.net core web服务器, 如果一切顺利的话, web服务器会处理请求并返回响应, 在payload里面会包含所请求的数据.
但是这种工作方式对实时web是不灵的. 实时web需要服务器可以主动发送消息给客户端(可以是浏览器):
web服务器可以主动通知客户端数据的变化, 例如收到了新的对话消息.
"底层"技术
而signalr使用了三种"底层"技术来实现实时web, 它们分别是long polling, server sent events 和 websocket.
首先, 得知道什么是ajax. 这个就不介绍了.
long polling
polling
介绍long polling之前, 首先介绍一下polling.
polling是实现实时web的一种笨方法, 它就是通过定期的向服务器发送请求, 来查看服务器的数据是否有变化.
如果服务器数据没有变化, 那么就返回204 no content; 如果有变化就把最新的数据发送给客户端:
下面是polling的一个实现, 非常简单:
就看这个controller的get方法即可. 用到了myservice, 它在项目里是单例的. 它的方法非常简单:
myservice就是做了一个全局的count, 它的getlatestcount会返回最新的count.
controller里面的代码意思是: 如果count > 6 就返回一个对象, 里面包含count的值和传进来的id; 如果 count > 10, 还要返回一个finished标志.
看一下前端代码:
也是非常的简单, 点击按钮后定时发送请求, 如果有结果就显示最新count值; 如果有finished标志, 就显示最新值和已结束.
注意这里使用的是fetch api.
运行项目, count > 6的时候:
count > 10的时候结束:
这就是polling, 很简单, 但是比较浪费资源.
signalr没有采用polling这种技术.
long polling
long polling和polling有类似的地方, 客户端都是发送请求到服务器. 但是不同之处是: 如果服务器没有新数据要发给客户端的话, 那么服务器会继续保持连接, 直到有新的数据产生, 服务器才把新的数据返回给客户端.
如果请求发出后一段时间内没有响应, 那么请求就会超时. 这时, 客户端会再次发出请求.
例子, controller的代码稍有改动:
改动的目的就是在符合要求的数据出现之前, 保持连接开放.
前端也有一些改动:
pollwithtimeout方法使用了race, 如果请求后超过9秒没有响应, 那么就返回超时错误.
poll里面, 如果请求返回的结果是200, 那么就更新ui. 但是如果没有finished标志, 就继续发出请求.
运行:
可以看到只有一个请求, 请求的时间很长, 标识连接开放了很长时间.
这里需要注意的一点是, 服务器的超时时长和浏览器的超时时长可能不一样.
前边介绍的polling和long polling都是http请求, 这其实并不是很适合.
下面介绍稍微一个好点的技术:
server sent events (sse)
使用sse的话, web服务器可以在任何时间把数据发送到浏览器, 可以称之为推送. 而浏览器则会监听进来的信息, 这些信息就像流数据一样, 这个连接也会一直保持开放, 直到服务器主动关闭它.
浏览器会使用一个叫做eventsource的对象用来处理传过来的信息.
例子, 这和之前的代码有很多地方不同, 用到了reponse:
注意sse返回数据的只能是字符串, 而且以data:开头, 后边要跟着换行符号, 否则eventsource会失败.
客户端:
这个就很简单了, 使用eventsource的onmessage事件. 前一个请求等到响应回来后, 会再发出一个请求.
运行:
这个eventsource要比polling和long polling好很多.
它有以下优点: 使用简单(http), 自动重连, 虽然不支持老浏览器但是很容易polyfill.
而缺点是: 很多浏览器都有最大并发连接数的限制, 只能发送文本信息, 单向通信.
web socket
web socket是不同于http的另一个tcp协议. 它使得浏览器和服务器之间的交互式通信变得可能. 使用websocket, 消息可以从服务器发往客户端, 也可以从客户端发往服务器, 并且没有http那样的延迟. 信息流没有完成的时候, tcp socket通常是保持打开的状态.
使用线代浏览器时, signalr大部分情况下都会使用web socket, 这也是最有效的传输方式.
全双工通信: 客户端和服务器可以同时往对方发送消息.
并且不受sse的那个浏览器连接数限制(6个), 大部分浏览器对web socket连接数的限制是50个.
消息类型: 可以是文本和二进制, web socket也支持流媒体(音频和视频).
其实正常的http请求也使用了tcp socket. web socket标准使用了握手机制把用于http的socket升级为使用ws协议的 websocket socket.
生命周期
web socket的生命周期是这样的:
所有的一切都发生在tcp socket里面, 首先一个常规的http请求会要求服务器更新socket并协商, 这个叫做http握手. 然后消息就可以在socket里来回传送, 直到这个socket被主动关闭. 在主动关闭的时候, 关闭的原因也会被通信.
http 握手
每一个web socket开始的时候都是一个简单的http socket.
客户端首先发送一个get请求到服务器, 来请求升级socket.
如果服务器同意的话, 这个socket从这时开始就变成了web socket.
这个请求的示例如下:
第一行表明这就是一个http get请求.
upgrade 这个header表示请求升级socket到web socket.
sec-websocket-key, 也很重要, 它用于防止缓存问题, 具体请查看.
服务器理解并同意请求以后, 它的响应如下:
返回101状态码, 表示切换协议.
如果返回的不是101, 那么浏览器就会知道服务器没有处理websocket的能力.
此外header里面还有upgrade: websocket.
sec-websocket-accept是配合着sec-websocket-key来运作的, 具体请查阅.
消息类型
web socket的消息类型可以是文本, 二进制. 也包括控制类的消息: ping/pong, 和关闭.
每个消息由一个或多个frame组成:
所有的frame都是二进制的. 所以文本的话, 就会首先转化成二进制.
frame 有若干个header bits.
有的可以表示这个frame是否是消息的最后一个frame;
有的可以表示消息的类型.
有的可以表示消息是否被掩蔽了. 客户端到服务器的消息被掩蔽了, 为了防止缓存投毒(使用恶意数据替换缓存).
有的可以设置payload的长度, payload会占据frame剩下的地方.
实际上用的时候, 你基本不会观察到frame, 它是在后台处理的, 你能看到的就是完整的消息.
但是在浏览器调试的时候, 你看到的是frame挨个传递进来而不是整个消息.
看下例子:
首先asp.net core项目里已经内置了websocket, 但是需要配置和使用这个中间件, 在startup:
这里我们设置了每隔120秒就ping一下. 还设置用于接收和解析frame的缓存大小. 其实这两个值都是默认的值.
修改后的controller:
这里需要注入httpcontextaccessor. 然后判断请求是否是websocket请求, 如果是的话, 客户端会收到回复, 这时socket就升级完成了. 升级完返回一个websocket对象, 然后我把events通过它发送出去. 随后我关闭了websocket, 并指明了原因normalclosure.
然后看看sendevents方法:
这里的重点就是websocket对象的sendasync方法. 我需要把数据转化成buffer进行传送. 数据类型是text. 具体参数请查看文档.
看一下客户端:
也很简单, 这里有一个websocket对象, 注意这里的url开头是ws而不是http, 还有一个wss, 就先当与http里的https.
然后eventhandler和sse的差不多. 返回的json数据需要先parse, 然后再使用.
本文先到这, 随后再介绍下signalr和用法即可.
上一篇: C#内存映射大文件并使用Marshal解析结构体信息
下一篇: Python——时间换算