238 lines
8.7 KiB
C#
Raw Normal View History

2024-08-21 16:50:14 +08:00
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;
}
}