创建WebSocket服务器
定义
TouchSocket.HttpTouchSocket.Http.WebSocketsdotnet add package TouchSocket.Http一、说明
WebSocket服务器是基于HTTP协议升级而来的长连接通信协议服务器。它继承自HttpService,在完成HTTP握手后,通过协议升级建立WebSocket连接。每个成功连接的客户端都会在服务器端创建一个对应的HttpSessionClient实例,后续的所有WebSocket通信都通过该实例完成。
二、特点
- 基于HTTP协议升级,支持标准WebSocket协议。
- 支持文本、二进制以及其他Type数据传输。
- 内置心跳机制(Ping/Pong)。
- 支持数据帧分包和组合。
- 支持WSS(WebSocket Secure)加密连接。
- 支持多种连接验证方式。
- 高性能异步处理。
- 基于插件驱动,支持AOP编程。
三、产品应用场景
- 实时通信应用:聊天室、在线客服、实时协作等。
- 实时数据推送:股票行情、游戏数据、监控数据等。
- 物联网设备通信:传感器数据上报、设备控制等。
- Web应用实时交互:在线编辑器、实时画板等。
四、服务器架构
4.1 连接架构
WebSocket服务器基于HTTP服务器,当收到WebSocket握手请求时,会将HTTP连接升级为WebSocket连接。每个WebSocket连接对应一个HttpSessionClient实例。
4.2 协议升级流程
- 客户端发送HTTP握手请求
- 服务器验证握手请求
- 服务器响应握手成功
- 连接升级为WebSocket协议
- 开始WebSocket数据帧通信
五、可配置项
继承HttpService的所有配置项,无特殊配置。
六、支持插件接口
| 插件方法 | 功能 |
|---|---|
| IWebSocketConnectingPlugin | 当收到握手请求之前,可以进行连接验证等 |
| IWebSocketConnectedPlugin | 当成功握手响应之后 |
| IWebSocketReceivedPlugin | 当收到Websocket的数据报文 |
| IWebSocketClosingPlugin | 当收到关闭请求时触发。如果对方直接断开连接,则此方法则不会触发。 |
| IWebSocketClosedPlugin | 当WebSocket连接断开时触发,无论是否正常断开。但如果是断网等操作,可能不会立即执行,需要结合心跳操作和CheckClear插件来进行清理。 |
七、创建WebSocket服务器
7.1 简单直接创建
最简单的方式是使用WebSocket插件,直接指定URL路由来接收WebSocket连接。
7.2 验证连接
可以对连接的URL、Query参数、Header等进行验证,决定是否允许WebSocket连接。
在验证过程中,如果url不匹配,或者不包含升级协议头的话,一般不需要额外处理,直接返回false即可。随后这个请求会被当作普通的HTTP请求处理。
如果url正确,但是其他鉴权没通过时,才需要直接进行Http响应。
7.3 其他方式创建
实际上,只要在升级协议后,能访问到HttpContext,即可通过SwitchProtocolToWebSocketAsync方式创建WebSocket连接更加灵活,可以方便地获取HTTP参数,实现多个URL的连接路由。
SwitchProtocolToWebSocketAsync后的WebSocket实例还是会放置在所在的HttpSessionClient中,可以通过HttpSessionClient.WebSocket获取到。
7.4 创建基于SSL的WebSocket服务(WSS)
创建WSS(WebSocket Secure)服务器,只需在配置中添加SSL选项。详情: Http服务器配置Ssl
八、接收消息
WebSocket服务器有多种接收消息的方式,可以根据不同的使用场景选择合适的方法。
8.1 插件方式接收消息
使用插件接收消息是最推荐的方式,它提供了高度解耦和灵活的数据处理能力。
(1)定义插件:
(2)配置使用插件:
在服务器端,默认情况下插件的所有函数都可能被并发执行,因此应当做好线程安全处理。更多详情请参考:插件开发使用指南
8.2 异步读取(ReadAsync)
ReadAsync是IWebSocket提供的异步读取方法,允许以顺序化的代码风格轮询等待并处理数据,非常适合需要处理复杂数据逻辑、有状态协议或请求-响应模式的场景。
ReadAsync是异步非阻塞的接收方式,不会占用线程资源,只会挂起当前Task直到数据到达。因此可以大量使用,无需担心性能问题。
使用ReadAsync方式时,框架不会触发IWebSocketReceivedPlugin插件中的接收事件。两种接收方式不能同时混用。
在服务器端使用ReadAsync时,有以下两个关键要点:
- 必须将
autoReceive设为false:调用SwitchProtocolToWebSocketAsync(false)时,false参数表示关闭框架的自动接收循环,从而由开发者通过ReadAsync主动拉取数据。若不设置,框架会自动消费数据,ReadAsync将永远收不到消息。 - 必须在新的异步任务中执行读取循环:不能在调用
SwitchProtocolToWebSocketAsync的当前任务线程中直接执行读取,必须使用EasyTask.SafeNewRun启动新任务,因为只有当升级函数执行完毕并return后,WebSocket升级才算完成。
8.3 使用ReadStringAsync和ReadBinaryAsync
框架提供了ReadStringAsync和ReadBinaryAsync两个扩展方法,是对ReadAsync的高级封装,能够自动处理分包、拼包,无需手动判断WSDataType.Cont中继包,让代码更加简洁:
ReadStringAsync:自动合并所有文本类型的数据帧,直接返回完整字符串。ReadBinaryAsync:自动合并所有二进制类型的数据帧,将完整数据写入ByteBlock或Stream。
ReadStringAsync和ReadBinaryAsync在收到非预期类型的数据帧时会抛出异常,请确保通信双方的数据类型约定一致。
8.4 接收中继数据
WebSocket在接收大数据时,可能会分包接收。可以通过WSDataFrame.Opcode的值是不是WSDataType.Cont来判断是不是分包数据。
分包数据的处理方式有很多,下面提供一种内存缓存的方式:
内存缓存的方式适合数据量不大的场景,如果数据量较大,建议使用其他缓存等方式。
九、发送数据
按照WebSocket服务器架构,每个成功连接的客户端都会在服务器端创建一个HttpSessionClient实例。要发送WebSocket消息,需要通过这些实例进行操作。
9.1 获取客户端实例
一般的,如果在插件中收到信息时,可以直接拿到HttpSessionClient实例。或者IWebSocket对象。此时直接操作即可。
如果在服务器的其他地方想要发送消息,可以通过下面代码获取所有在线客户端,然后选择需要发送的客户端。
9.2 发送文本消息
9.3 发送二进制消息
9.4 发送自定义数据帧
9.5 发送Ping、Pong消息
9.7 发送大数据(分包)
发送大数据时,需要分包发送,可以使用SendAsync的重载方法,设置FIN标志。
十、连接管理
10.1 主动关闭连接
在使用WebSocket时,如果想主动关闭连接,可以使用CloseAsync方法,同时可以携带一个关闭原因。
默认关闭状态码为1000。意为:正常关闭。