2024-08-21 16:50:14 +08:00

238 lines
8.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}