一、说明
WebSocket是基于Http协议的升级协议,所以应当挂载在http服务器执行。
二、可配置项
继承HttpService
三、支持插件接口
支持ITcpPlugin、IHttpPlugin、IWebSocketPlugin
IWebSocketPlugin
OnHandshaking | 表示在即将握手连接时。 |
OnHandshaked | 表示完成握手后。 |
OnHandleWSDataFrame | 当收到WS数据时。 |
四、创建WebSocket服务
4.1 简单通过插件创建
通过插件创建的话,只能指定一个特殊url路由。如果想获得连接前的Http请求,也必须再添加一个实现IWebSocketPlugin接口的插件,然后从OnHandshaking方法中捕获。
var service = new HttpService();
service.Setup(new TouchSocketConfig()//加载配置
.UsePlugin()
.SetListenIPHosts(new IPHost[] { new IPHost(7789) })
.ConfigureContainer(a =>
{
a.SetSingletonLogger<ConsoleLogger>();
})
.ConfigurePlugins(a =>
{
a.Add<WebSocketServerPlugin>()//添加WebSocket功能
.SetWSUrl("/ws")
.SetCallback(WSCallback);//WSCallback回调函数是在WS收到数据时触发回调的。
a.Add<MyWebSocketPlugin>();//MyWebSocketPlugin是继承自WebSocketPluginBase的插件。
}))
.Start();
Console.WriteLine("Http服务器已启动");
Console.WriteLine("ws://127.0.0.1:7789/ws");
4.2 通过WebApi创建
通过WebApi的方式会更加灵活,也能很方便的获得Http相关参数。还能实现多个Url的连接路由。
实现步骤:
- 必须启用插件
- 必须配置ConfigureRpcStore,和注册MyServer
- 必须添加WebApiParserPlugin
```csharp
var service = new HttpService();
service.Setup(new TouchSocketConfig()//加载配置
.UsePlugin()
.SetListenIPHosts(new IPHost[] { new IPHost(7789) })
.ConfigureContainer(a =>
{
}) .ConfigureRpcStore(a=> {a.SetSingletonLogger<ConsoleLogger>();
}) .ConfigurePlugins(a => {a.RegisterServer<MyServer>();
})) .Start();a.Add<WebApiParserPlugin>();
Console.WriteLine(“服务器已启动,可使用下列地址连接”); Console.WriteLine(“ws://127.0.0.1:7789/MyServer/ConnectWS”);
```csharp
public class MyServer : RpcServer
{
private readonly ILog m_logger;
public MyServer(ILog logger)
{
this.m_logger = logger;
}
[Router("/[api]/[action]")]
[WebApi(HttpMethodType.GET, MethodFlags = MethodFlags.IncludeCallContext)]
public void ConnectWS(WebApiServerCallContext callContext)
{
if (callContext.Caller is HttpSocketClient socketClient)
{
if (socketClient.SwitchProtocolToWebSocket(callContext.Context))
{
m_logger.Message("WS通过WebApi连接");
}
}
}
}
创建基于Ssl的WebSocket服务
创建WSs服务器时,其他配置不变,只需要在config中配置SslOption代码即可。
在RRQMBox中,放置了一个自制Ssl证书,密码为“RRQMSocket”以供测试。使用配置非常方便。
var config = new TouchSocketConfig();
config.UsePlugin()
.SetReceiveType(ReceiveType.Auto)
.SetListenIPHosts(new IPHost[] { new IPHost(7789) })
.SetServiceSslOption(new ServiceSslOption() //Ssl配置,当为null的时候,相当于创建了ws服务器,当赋值的时候,相当于wss服务器。
{
Certificate = new X509Certificate2("RRQMSocket.pfx", "RRQMSocket"),
SslProtocols = SslProtocols.Tls12
});
接收消息
【回调接收】
在添加WebSocketServerPlugin插件后,可以调用SetCallback函数,然后设置一个回调函数(如下所示),然后该函数在服务器收到信息时,会触发(并发触发)。
static void WSCallback(ITcpClientBase client, WSDataFrameEventArgs e)
{
switch (e.DataFrame.Opcode)
{
case WSDataType.Cont:
Console.WriteLine($"收到中间数据,长度为:{e.DataFrame.PayloadLength}");
break;
case WSDataType.Text:
Console.WriteLine(e.DataFrame.ToText());
break;
case WSDataType.Binary:
if (e.DataFrame.FIN)
{
Console.WriteLine($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}");
}
else
{
Console.WriteLine($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}");
}
break;
case WSDataType.Close:
{
Console.WriteLine("远程请求断开");
client.Close("断开");
}
break;
case WSDataType.Ping:
break;
case WSDataType.Pong:
break;
default:
break;
}
}
【继承源插件接收】
实际上WebSocketServerPlugin是可以被继承的,然后重写OnHandleWSDataFrame函数,但尽量不要覆盖基类方法,不然插件其他将不会触发。
class MyWebSocketServerPlugin: WebSocketServerPlugin
{
protected override void OnHandleWSDataFrame(ITcpClientBase client, WSDataFrameEventArgs e)
{
base.OnHandleWSDataFrame(client, e);
}
}
【插件接口接收】
WS服务器,虽然是Http的插件,但是也能触发插件接口。适用于WS的插件接口是IWebSocketPlugin(或者从WebSocketPluginBase继承),声明任意类,实现该接口即可。
class MyWebSocketServerPlugin: WebSocketPluginBase
{
protected override void OnHandleWSDataFrame(ITcpClientBase client, WSDataFrameEventArgs e)
{
//此处的父类方法可以直接覆盖。
base.OnHandleWSDataFrame(client, e);
}
}
回复、响应数据
在以上接收、或直接从HttpService获取Clients,将client对象转为HttpSocketClient,即可使用扩展方法,进行发送。
不要直接Send,7.x版本直接Send可以,但8.0以后,Send只会以TCP数据回应。
作为一条消息发送
服务器广播发送
//广播给所有人
if (client is HttpSocketClient socketClient && socketClient.Service is HttpService service)
{
var clients = service.GetClients();
foreach (var item in clients)
{
item.SendWithWS(e.DataFrame.ToText());
}
}
将一个数据分包发送
例如:发送的数据为{0,1,2,3,4,5,6,7,8,9},当设置packageSize为5时,会先发送{0,1,2,3,4}作为头包,然后发送{5,6,7,8,9}的后继包。