238 lines
8.7 KiB
C#
238 lines
8.7 KiB
C#
![]() |
using DotNetty.Buffers;
|
|||
|
using DotNetty.Codecs.Http;
|
|||
|
using DotNetty.Codecs.Http.WebSockets;
|
|||
|
using DotNetty.Common.Utilities;
|
|||
|
using DotNetty.Transport.Channels;
|
|||
|
using DotNetty.Transport.Channels.Groups;
|
|||
|
using DotNettyHelper;
|
|||
|
using Newtonsoft.Json;
|
|||
|
using System;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.Text;
|
|||
|
using System.Threading.Tasks;
|
|||
|
using ToolLibrary.LogHelper;
|
|||
|
using Yunda.ISAS.DataMonitoringServer.WebSocket.Model;
|
|||
|
using static DotNetty.Codecs.Http.HttpResponseStatus;
|
|||
|
using static DotNetty.Codecs.Http.HttpVersion;
|
|||
|
using HttpMethod = DotNetty.Codecs.Http.HttpMethod;
|
|||
|
|
|||
|
namespace Yunda.ISAS.DataMonitoringServer.WebSocket.DotNetty.Server
|
|||
|
{
|
|||
|
public delegate void ExceptionCaughtDelegate(ExceptionCaughtEventArgs exceptionCaughtEventArgs);
|
|||
|
|
|||
|
public delegate void HandlerDelegate(HandlerEventArgs hanlerEventArgs);
|
|||
|
|
|||
|
public delegate void ReceiveMessageDelegate(ReceiveMessageEventArgs<DataMonitorMessageModel> recMsgEventArgs);
|
|||
|
|
|||
|
public sealed class WebSocketServerHandler : SimpleChannelInboundHandler<object>
|
|||
|
{
|
|||
|
private string _websocketPath;
|
|||
|
public WebSocketServerHandler(string path)
|
|||
|
{
|
|||
|
_websocketPath = path;
|
|||
|
}
|
|||
|
private WebSocketServerHandshaker _handshaker;
|
|||
|
|
|||
|
public event HandlerDelegate HandlerRemovedEvent;
|
|||
|
|
|||
|
public event HandlerDelegate HandlerAddedEvent;
|
|||
|
|
|||
|
public event ExceptionCaughtDelegate ExceptionCaughtEvent;
|
|||
|
|
|||
|
public event ReceiveMessageDelegate ReceiveMessageEvent;
|
|||
|
|
|||
|
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
|
|||
|
{
|
|||
|
if (msg is IFullHttpRequest request)
|
|||
|
{
|
|||
|
this.HandleHttpRequest(ctx, request);
|
|||
|
}
|
|||
|
else if (msg is WebSocketFrame frame)
|
|||
|
{
|
|||
|
this.HandleWebSocketFrame(ctx, frame);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void HandleHttpRequest(IChannelHandlerContext ctx, IFullHttpRequest req)
|
|||
|
{
|
|||
|
// Handle a bad request.
|
|||
|
if (!req.Result.IsSuccess)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest));
|
|||
|
}
|
|||
|
catch (JsonReaderException ex)
|
|||
|
{
|
|||
|
// return;
|
|||
|
throw new JsonReaderException(ex.ToString());
|
|||
|
}
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Allow only GET methods.
|
|||
|
if (!Equals(req.Method, HttpMethod.Get))
|
|||
|
{
|
|||
|
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden));
|
|||
|
return;
|
|||
|
}
|
|||
|
// Send the demo page and favicon.ico
|
|||
|
|
|||
|
if ("/favicon.ico".Equals(req.Uri))
|
|||
|
{
|
|||
|
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, NotFound));
|
|||
|
return;
|
|||
|
}
|
|||
|
string webSocketPath = @"/" + _websocketPath;
|
|||
|
string[] requestStrs = req.Uri.Split("?");
|
|||
|
if ("/".Equals(requestStrs[0]) || !webSocketPath.Equals(requestStrs[0]))
|
|||
|
{
|
|||
|
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, NotFound));
|
|||
|
return;
|
|||
|
}
|
|||
|
// Handshake
|
|||
|
string location = GetWebSocketLocation(req);
|
|||
|
var wsFactory = new WebSocketServerHandshakerFactory(location, null, true, 5 * 1024 * 1024);
|
|||
|
this._handshaker = wsFactory.NewHandshaker(req);
|
|||
|
if (this._handshaker == null)
|
|||
|
{
|
|||
|
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
this._handshaker.HandshakeAsync(ctx.Channel, req);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void HandleWebSocketFrame(IChannelHandlerContext ctx, WebSocketFrame frame)
|
|||
|
{
|
|||
|
// Check for closing frame
|
|||
|
if (frame is CloseWebSocketFrame)
|
|||
|
{
|
|||
|
this._handshaker.CloseAsync(ctx.Channel, (CloseWebSocketFrame)frame.Retain());
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (frame is PingWebSocketFrame)
|
|||
|
{
|
|||
|
ctx.WriteAndFlushAsync(new PongWebSocketFrame((IByteBuffer)frame.Content.Retain()));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (frame is TextWebSocketFrame)
|
|||
|
{
|
|||
|
try
|
|||
|
{
|
|||
|
#region 接收客户端消息并群发
|
|||
|
|
|||
|
var frameCopy = frame.Copy();
|
|||
|
////群发消息
|
|||
|
//DotNettyWebSocketServer
|
|||
|
// .GetWebSocketServerSingleton()
|
|||
|
// .GetChannelGroup()
|
|||
|
// .WriteAndFlushAsync(frameCopy, new EveryOneBut(ctx.Channel.Id));
|
|||
|
//// Echo the frame
|
|||
|
//ctx.WriteAndFlushAsync(frame.Retain());
|
|||
|
|
|||
|
#endregion 接收客户端消息并群发
|
|||
|
string msg = frameCopy.Content.ToString(Encoding.UTF8);
|
|||
|
DataMonitorMessageModel msgModel = JsonConvert.DeserializeObject<DataMonitorMessageModel>(msg);
|
|||
|
if (msgModel == null || msgModel.GroupType == GroupTypeEnum.Error)
|
|||
|
return;
|
|||
|
ReceiveMessageEvent?.Invoke(new ReceiveMessageEventArgs<DataMonitorMessageModel>(ctx, msgModel));
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
catch (Exception ex)
|
|||
|
{
|
|||
|
//throw new Exception(ex.ToString());
|
|||
|
Log4Helper.Error(this.GetType(), "接收客户端消息并群发", ex);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (frame is BinaryWebSocketFrame)
|
|||
|
{
|
|||
|
// Echo the frame
|
|||
|
ctx.WriteAndFlushAsync(frame.Retain());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static void SendHttpResponse(IChannelHandlerContext ctx, IFullHttpRequest req, IFullHttpResponse res)
|
|||
|
{
|
|||
|
// Generate an error page if response getStatus code is not OK (200).
|
|||
|
if (res.Status.Code != 200)
|
|||
|
{
|
|||
|
IByteBuffer buf = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(res.Status.ToString()));
|
|||
|
res.Content.WriteBytes(buf);
|
|||
|
buf.Release();
|
|||
|
HttpUtil.SetContentLength(res, res.Content.ReadableBytes);
|
|||
|
}
|
|||
|
|
|||
|
// Send the response and close the connection if necessary.
|
|||
|
Task task = ctx.Channel.WriteAndFlushAsync(res);
|
|||
|
if (!HttpUtil.IsKeepAlive(req) || res.Status.Code != 200)
|
|||
|
{
|
|||
|
task.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(),
|
|||
|
ctx, TaskContinuationOptions.ExecuteSynchronously);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static string GetWebSocketLocation(IFullHttpRequest req)
|
|||
|
{
|
|||
|
bool result = req.Headers.TryGet(HttpHeaderNames.Host, out ICharSequence value);
|
|||
|
Debug.Assert(result, "Host header does not exist.");
|
|||
|
string location = value.ToString() + req.Uri;
|
|||
|
return "ws://" + location;
|
|||
|
}
|
|||
|
|
|||
|
// 输出到客户端,也可以在上面的方法中直接调用WriteAndFlushAsync方法直接输出
|
|||
|
//public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
|
|||
|
|
|||
|
//捕获 异常,并输出到控制台后断开链接,提示:客户端意外断开链接,也会触发
|
|||
|
public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
|
|||
|
{
|
|||
|
|
|||
|
Log4Helper.Error(this.GetType(), "客户端意外断开链接", exception);
|
|||
|
context.CloseAsync();
|
|||
|
ExceptionCaughtEvent?.Invoke(new ExceptionCaughtEventArgs(context, exception));
|
|||
|
}
|
|||
|
|
|||
|
//客户端连接进来时
|
|||
|
public override void HandlerAdded(IChannelHandlerContext context)
|
|||
|
{
|
|||
|
base.HandlerAdded(context);
|
|||
|
HandlerAddedEvent?.Invoke(new HandlerEventArgs(context));
|
|||
|
}
|
|||
|
|
|||
|
//客户端下线断线时
|
|||
|
public override void HandlerRemoved(IChannelHandlerContext context)
|
|||
|
{
|
|||
|
base.HandlerRemoved(context);
|
|||
|
HandlerRemovedEvent?.Invoke(new HandlerEventArgs(context));
|
|||
|
}
|
|||
|
|
|||
|
//服务器监听到客户端活动时
|
|||
|
public override void ChannelActive(IChannelHandlerContext context)
|
|||
|
{
|
|||
|
base.ChannelActive(context);
|
|||
|
}
|
|||
|
|
|||
|
//服务器监听到客户端不活动时
|
|||
|
public override void ChannelInactive(IChannelHandlerContext context)
|
|||
|
{
|
|||
|
base.ChannelInactive(context);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
internal class EveryOneBut : IChannelMatcher
|
|||
|
{
|
|||
|
private readonly IChannelId id;
|
|||
|
|
|||
|
public EveryOneBut(IChannelId id)
|
|||
|
{
|
|||
|
this.id = id;
|
|||
|
}
|
|||
|
|
|||
|
public bool Matches(IChannel channel) => channel.Id != this.id;
|
|||
|
}
|
|||
|
}
|