C# DotNetty (3) WebSocketServer

前两篇:
1.C# DotNetty (1) EchoServer
2.C# DotNetty (2) EchoClient

WebSocket本质还是Socket
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
Ajax 轮询:在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
和Socket的
参考资料:https://www.liaoxuefeng.com/wiki/1022910821149312/1103303693824096

根据DotNetty官方给出的案例中,DoNetty分为以下两部分:
(github上下载的,参考前面的两篇)
在这里插入图片描述
第一类Http请求
第二类WebSocket帧

对于请求需要做的动作是:
1.判断请求是否成功
在这里插入图片描述

2.仅允许get方法:
在这里插入图片描述
3.根据url判断返回回应(一个demo界面和图表,实际使用中暂时用不上,但是可以作为参考)
在这里插入图片描述
4.一次握手,建立连接
在这里插入图片描述
根据不同版本获取握手的回应
在这里插入图片描述

第二类:WebSocket帧
在这里插入图片描述
案例中除了关闭帧,其他类型的帧原封不动返回

以官方给出的例子进行改写:
用于管理通道及两个事件循环组,以及具有广播事件,消息队列及定时广播的封装

public class ServerChannel
        {
            public IChannel Channel { get; set; }
            public IEventLoopGroup Bossgroup { get; set; }
            public IEventLoopGroup Workgroup { get; set; }
            public WebSocketBroadCastEvent WebSocketBroadCastEvent { get; set; }
            public ConcurrentQueue<String> MsgQueue { get; set; }
            private Timer T { get; set; }
            public void StartBroadCast(int dueTime, int period)
            {
                if (T == null)
                {
                    T = new Timer((obj) =>
                    {
                        if (MsgQueue.TryPeek(out string res))//Debug
                        {
                            WebSocketBroadCastEvent.Active(res);
                        }
                    });
                    T.Change(dueTime, period);
                }
            }
            public void Stop()
            {
                if (T != null)
                {
                    T.Change(Timeout.Infinite, Timeout.Infinite);
                    T.Dispose();
                }
            }
        }

广播事件类:

using System;
using System.Collections.Generic;
using System.Text;

namespace LocateAnalyzer.DotNetty
{
    public delegate void BroadCastdelegate(String Msg);
    public class WebSocketBroadCastEvent
    {
        public event BroadCastdelegate Myevent;
        public void Active(String Msg)
        {
            Myevent?.Invoke(Msg);
        }
    }
}

WebSocket服务端开启,关闭,及广播消息

private static Dictionary<int, ServerChannel> ChannelDic = new Dictionary<int, ServerChannel>();//用于存储服务端通道
        public static async Task RunServerAsync(Boolean UseLibuv, Boolean IsSsl, int Port)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
                //https://docs.microsoft.com/zh-cn/dotnet/api/system.runtime.gclatencymode?redirectedfrom=MSDN&view=netframework-4.8
                //调整垃圾收集器侵入应用程序的时间。
            }

            IEventLoopGroup bossGroup;
            IEventLoopGroup workGroup;
            if (UseLibuv)
            {
                var dispatcher = new DispatcherEventLoopGroup();
                bossGroup = dispatcher;
                workGroup = new WorkerEventLoopGroup(dispatcher);
            }
            else
            {
                bossGroup = new MultithreadEventLoopGroup(1);
                workGroup = new MultithreadEventLoopGroup();
            }

            X509Certificate2 tlsCertificate = null;
            if (IsSsl)
            {
                tlsCertificate = new X509Certificate2(Path.Combine(AppContext.BaseDirectory, "dotnetty.com.pfx"), "password");
            }
            try
            {
                var bootstrap = new ServerBootstrap();
                bootstrap.Group(bossGroup, workGroup);

                if (UseLibuv)
                {
                    bootstrap.Channel<TcpServerChannel>();
                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
                        || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                    {
                        bootstrap
                            .Option(ChannelOption.SoReuseport, true)
                            .ChildOption(ChannelOption.SoReuseaddr, true);
                    }
                }
                else
                {
                    bootstrap.Channel<TcpServerSocketChannel>();
                }
                WebSocketBroadCastEvent webSocketBroadCastEvent = new WebSocketBroadCastEvent();
                WebSocketServerHandler.SetSsl(IsSsl);
                bootstrap
                    .Option(ChannelOption.SoBacklog, 8192)
                    .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                    {
                        IChannelPipeline pipeline = channel.Pipeline;
                        if (tlsCertificate != null)
                        {
                            pipeline.AddLast(TlsHandler.Server(tlsCertificate));
                        }
                        pipeline.AddLast(new HttpServerCodec());
                        pipeline.AddLast(new HttpObjectAggregator(65536));
                        var Handler = new WebSocketServerHandler();
                        Handler.SetBroadCastEvent(webSocketBroadCastEvent);
                        pipeline.AddLast(Handler);
                    }));

                IChannel bootstrapChannel = await bootstrap.BindAsync(IPAddress.Loopback, Port).ConfigureAwait(true);
                if (ChannelDic.ContainsKey(Port))
                    throw new Exception("Port:" + Port + " is already Ocuppied");
                ChannelDic[Port] = new ServerChannel()
                {
                    Channel = bootstrapChannel,
                    Bossgroup = bossGroup,
                    Workgroup = workGroup,
                    MsgQueue = new ConcurrentQueue<string>(),
                    WebSocketBroadCastEvent = webSocketBroadCastEvent
                };
                BroadCast(Port, "123");//Debug
                ChannelDic[Port].StartBroadCast(5000, 1000);//Debug
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ChannelDic.ContainsKey(Port))
                    await ChannelDic[Port].Channel.CloseAsync().ConfigureAwait(true);
                workGroup.ShutdownGracefullyAsync().Wait();
                bossGroup.ShutdownGracefullyAsync().Wait();
            }
        }

        public static void Stop(int Port)
        {
            if (!ChannelDic.ContainsKey(Port))
                return;
            var server = ChannelDic[Port];
            server.Channel.CloseAsync().ConfigureAwait(true);
            server.Workgroup.ShutdownGracefullyAsync().Wait();
            server.Bossgroup.ShutdownGracefullyAsync().Wait();
            ChannelDic.Remove(Port);
        }

        public static void BroadCast(int Port, String Msg)
        {
            if (!ChannelDic.ContainsKey(Port))
                return;
            ChannelDic[Port].MsgQueue.Enqueue(Msg);
        }

WebSocketServerHandler:处理消息

using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Transport.Channels;
using static DotNetty.Codecs.Http.HttpVersion;
using static DotNetty.Codecs.Http.HttpResponseStatus;
using DotNetty.Buffers;
using System.Text;
using System;
using System.Threading.Tasks;
using System.Diagnostics;
using DotNetty.Common.Utilities;
using System.Collections.Generic;
using System.Threading;
using System.Collections.Concurrent;

namespace LocateAnalyzer.DotNetty
{
    public sealed class WebSocketServerHandler : SimpleChannelInboundHandler<object>
    {
        const string WebsocketPath = "/websocket";
        private static Boolean IsSsl;
        WebSocketServerHandshaker handshaker;
        public static void SetSsl(bool isSsl)
        {
            IsSsl = isSsl;
        }

        protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
        {
            if (ctx == null)
                return;
            if (msg is IFullHttpRequest request)
            {
                this.HandleHttpRequest(ctx, request);
            }
            else if (msg is WebSocketFrame frame)
            {
                this.HandleWebSocketFrame(ctx, frame);
            }
        }

        public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();

        void HandleHttpRequest(IChannelHandlerContext ctx, IFullHttpRequest req)
        {
            // Handle a bad request.
            if (!req.Result.IsSuccess)
            {
                SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest));
                return;
            }

            // Allow only GET methods.
            if (!Equals(req.Method, HttpMethod.Get))
            {
                SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden));
                return;
            }

            // Handshake
            String url = GetWebSocketLocation(req);
            var wsFactory = new WebSocketServerHandshakerFactory(url, 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);
                var clientAddress = ctx.Channel.RemoteAddress.ToString();
                Console.WriteLine(url + ":" + clientAddress + " Connected!");
                channel = ctx.Channel;
                webSocketBroadCastEvent.Myevent += Send;
            }
        }
        private IChannel channel;
        private WebSocketBroadCastEvent webSocketBroadCastEvent;
        public void SetBroadCastEvent(WebSocketBroadCastEvent webSocketBroadCastEvent)
        {
            this.webSocketBroadCastEvent = webSocketBroadCastEvent;
        }
        private void Send(String Msg)
        {
            WebSocketFrame frame = new TextWebSocketFrame(Msg);
            channel.WriteAndFlushAsync(frame.Retain());
        }
        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.WriteAsync(new PongWebSocketFrame((IByteBuffer)frame.Content.Retain()));
                return;
            }

            if (frame is TextWebSocketFrame)
            {
                // Echo the frame
                int readableBytes = frame.Content.ReadableBytes;
                var msg = frame.Content.GetString(0, readableBytes, Encoding.UTF8);
                Console.WriteLine("Recv from ws Client:" + msg);
                WebSocketFrame replyFrame = frame;

                replyFrame.Content.Clear();
                replyFrame.Content.WriteBytes(Encoding.UTF8.GetBytes("reply:" + msg));
                ctx.WriteAsync(replyFrame.Retain());
                return;
            }

            if (frame is BinaryWebSocketFrame)
            {
                // Echo the frame
                ctx.WriteAsync(frame.Retain());
            }
        }

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

        public override void ExceptionCaught(IChannelHandlerContext ctx, Exception e)
        {
            Console.WriteLine($"{nameof(WebSocketServerHandler)} {0}", e);
            if (ctx != null)
                ctx.CloseAsync();
            webSocketBroadCastEvent.Myevent -= Send;
        }

        public override Task CloseAsync(IChannelHandlerContext context)
        {
            webSocketBroadCastEvent.Myevent -= Send;
            Console.WriteLine(context.Channel.RemoteAddress.ToString() + " Close");
            return base.CloseAsync(context);
        }
        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() + WebsocketPath;

            if (IsSsl)
            {
                return "wss://" + location;
            }
            else
            {
                return "ws://" + location;
            }
        }
    }
}

目前用于调试:
每秒服务端会向客户端推送123字符串
然后客户端像服务端发送消息时,服务端会回:
reply:消息
实际使用中需要将这些代码删除

在建立连接后,客户端会将广播事件注册到服务端管道中,关闭会取消注册
然后服务端会开启一个Timer,定期从队列中取消息触发事件给客户端进行推送

效果:
服务端启动:
在这里插入图片描述
第一个客户端连接并收到推送:
在这里插入图片描述
第二个客户端连接并收到推送:
在这里插入图片描述
发送456,收到回复:
在这里插入图片描述第二个客户端断开连接:
在这里插入图片描述
第一个仍然收到推送:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以按照以下步骤创建一个 C# WebSocketServer 项目: 1. 创建一个新的 C# 控制台应用程序项目。 2. 添加 `System.Net.WebSockets` 命名空间。 3. 在 `Program.cs` 文件中添加以下代码: ```csharp using System; using System.Net; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; namespace WebSocketServer { class Program { static async Task Main(string[] args) { var listener = new HttpListener(); listener.Prefixes.Add("http://localhost:8080/"); listener.Start(); Console.WriteLine("Listening..."); while (true) { var context = await listener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { ProcessWebSocketRequest(context); } else { context.Response.StatusCode = 400; context.Response.Close(); } } } static async void ProcessWebSocketRequest(HttpListenerContext context) { try { var webSocket = await context.AcceptWebSocketAsync(subProtocol: null); Console.WriteLine("WebSocket connected"); var buffer = new byte[1024 * 4]; WebSocketReceiveResult result = null; while (webSocket.State == WebSocketState.Open) { result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); if (result.MessageType == WebSocketMessageType.Text) { var message = System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count); Console.WriteLine($"Received message: {message}"); var responseBytes = System.Text.Encoding.UTF8.GetBytes($"Echo: {message}"); await webSocket.SendAsync(new ArraySegment<byte>(responseBytes), WebSocketMessageType.Text, true, CancellationToken.None); } else if (result.MessageType == WebSocketMessageType.Close) { await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); Console.WriteLine("WebSocket closed"); } } } catch (Exception ex) { Console.WriteLine($"WebSocket error: {ex.Message}"); } } } } ``` 4. 运行程序,WebSocket 服务器将在 `http://localhost:8080/` 上监听连接请求。当客户端连接时,服务器将打印一条消息并开始接收客户端发送的消息。当接收到消息时,服务器将打印消息内容并将其回显给客户端。客户端可以通过发送 `Close` 消息来关闭 WebSocket 连接。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值