为了账号安全,请及时绑定邮箱和手机立即绑定

BeetleX之WebSocket详解

标签:
JavaScript

对于BeetleX来说编写WebSocket服务是一件非常简单的事情,当你实现一个Web Api应用的同时这些API方法也是WebSocket服务方法。接下来主要讲解如何通过JavaScript调用BeetleXWebSocket服务方法或定义一个适合自己数据格式的WebSocket服务。

引用组件

通过Nuget引用最新版本的BeetleX.FastHttpApi或通过下载源码编译组件

 

实现服务

由于组件支持基于类方法的方式来制定服务,所以定义一个服务非常简单,以下是一个基于Websockethello world服务:

复制代码

    [BeetleX.FastHttpApi.Controller]    class Program
    {        private static BeetleX.FastHttpApi.HttpApiServer mApiServer;        static void Main(string[] args)
        {
            mApiServer = new BeetleX.FastHttpApi.HttpApiServer();
            mApiServer.Debug();
            mApiServer.Register(typeof(Program).Assembly);
            mApiServer.Open();
            Console.Write(mApiServer.BaseServer);
            Console.Read();
        }        public string Hello(string name)
        {            return $"{name} {DateTime.Now}";
        }
    }

复制代码

JavaScript调用

由于组件定义一个调用规范,针对上面的方法调用有着一定的格式要求,大体的json格式如下:

{
     url:''
     params:{{"name":"value"},{"name1":"value1"}}
}
  • url

    描述请求的方法路径,针对以上示例对应的路径是'/Hello',组件默认大小写不敏感。

  • params

    用于描述方法对应的参数列表

针对以上示例方法调用json如下:

{
      url: '/Hello', 
      params: { name: 'test' }
}

自己处理数据

组件的服务要求指定的请求格式和对应的响应格式,这样对于一些使用者来说有些限制,如果不希望组件提供的格式而是自己制定数据方式的话可以绑定WebSocket数据接收事件,当事件绑定后组件会把接收的数据直接路由给事件来处理,不会再按原有的方式来解析处理。绑定事件如下:

复制代码

  mApiServer.WebSocketReceive = (o, e) =>
            {
                Console.WriteLine(e.Frame.Body);                var freame = e.CreateFrame($"{DateTime.Now}" + e.Frame.Body.ToString());
                e.Response(freame);
            };

复制代码

不过这里的处理方式还是以文本为主,只是文本的格式解释和输出格式更多的进行控制。

处理非文本数据

默认情况都以文本的方式来处理数据,实际上websocket是支持二进制流的;如果希望在组件的基础上自己处理二进制流数据需要制定一个数据解析器,解析器的接口规范如下:

复制代码

    public interface IDataFrameSerializer
    {        object FrameDeserialize(DataFrame data, PipeStream stream);//反序列化对象方法
        ArraySegment<byte> FrameSerialize(DataFrame packet, object body);//序列化方法

        void FrameRecovery(byte[] buffer);//Buffer回收方法
    }

复制代码

组件默认的解析器实现如下:

复制代码

        public virtual object FrameDeserialize(DataFrame data, PipeStream stream)
        {            return stream.ReadString((int)data.Length);
        }        private System.Collections.Concurrent.ConcurrentQueue<byte[]> mBuffers = new System.Collections.Concurrent.ConcurrentQueue<byte[]>();        public virtual ArraySegment<byte> FrameSerialize(DataFrame data, object body)
        {            byte[] result;            if (!mBuffers.TryDequeue(out result))
            {
                result = new byte[this.Options.MaxBodyLength];
            }            string value;            if (body is string)
                value = (string)body;            else
                value = Newtonsoft.Json.JsonConvert.SerializeObject(body);            int length = Options.Encoding.GetBytes(value, 0, value.Length, result, 0);            return new ArraySegment<byte>(result, 0, length);
        }        public virtual void FrameRecovery(byte[] buffer)
        {
            mBuffers.Enqueue(buffer);
        }

复制代码

在制定完成数据解析器后把它设置到FrameSerializer属性上即可

HttpApiServer.FrameSerializer= new CustomFrameSerializer();

连接验证

当通过浏览器访问websocket服务的时候,在连接创建过程存在一个握手通讯包,这个通讯包一般都带有用户的Cookie,通过这个Cookie即可以验证连接的来源,从而确定连接的有效性。组件提供一个WebSocketConnect事件来扩展这个验证机制,事件制定如下:

            mApiServer.WebSocketConnect = (o, e) => {                //e.Request.Header                //e.Request.Cookies
                e.Cancel = true;
            };

使用者可以根据实际情况的需要判断对应的数据来确定是否取消当前WebSocket连接

基于流解释WebSocket协议

网上有很多WebSocket协议解释代码,之前也写过一份,不过都是针对byte[]进行分析,以下代码是基于Stream的方式来分析协议,通过stream操作起来会更简洁易懂

复制代码

        internal DataPacketLoadStep Read(PipeStream stream)
        {            if (mLoadStep == DataPacketLoadStep.None)
            {                if (stream.Length >= 2)
                {                    byte value = (byte)stream.ReadByte();                    this.FIN = (value & CHECK_B8) > 0;                    this.RSV1 = (value & CHECK_B7) > 0;                    this.RSV2 = (value & CHECK_B6) > 0;                    this.RSV3 = (value & CHECK_B5) > 0;                    this.Type = (DataPacketType)(byte)(value & 0xF);
                    value = (byte)stream.ReadByte();                    this.IsMask = (value & CHECK_B8) > 0;                    this.PayloadLen = (byte)(value & 0x7F);
                    mLoadStep = DataPacketLoadStep.Header;
                }
            }            if (mLoadStep == DataPacketLoadStep.Header)
            {                if (this.PayloadLen == 127)
                {                    if (stream.Length >= 8)
                    {
                        Length = stream.ReadUInt64();
                        mLoadStep = DataPacketLoadStep.Length;
                    }
                }                else if (this.PayloadLen == 126)
                {                    if (stream.Length >= 2)
                    {
                        Length = stream.ReadUInt16();
                        mLoadStep = DataPacketLoadStep.Length;
                    }
                }                else
                {                    this.Length = this.PayloadLen;
                    mLoadStep = DataPacketLoadStep.Length;
                }
            }            if (mLoadStep == DataPacketLoadStep.Length)
            {                if (IsMask)
                {                    if (stream.Length >= 4)
                    {                        this.MaskKey = new byte[4];
                        stream.Read(this.MaskKey, 0, 4);
                        mLoadStep = DataPacketLoadStep.Mask;
                    }
                }                else
                {
                    mLoadStep = DataPacketLoadStep.Mask;
                }
            }            if (mLoadStep == DataPacketLoadStep.Mask)
            {                if (this.Length > 0 && (ulong)stream.Length >= this.Length)
                {                    if (this.IsMask)
                        ReadMask(stream);
                    Body = this.DataPacketSerializer.FrameDeserialize(this, stream);
                    mLoadStep = DataPacketLoadStep.Completed;
                }
            }            return mLoadStep;
        }

复制代码

 

复制代码

void IDataResponse.Write(PipeStream stream)
        {            try
            {                byte[] header = new byte[2];                if (FIN)
                    header[0] |= CHECK_B8;                if (RSV1)
                    header[0] |= CHECK_B7;                if (RSV2)
                    header[0] |= CHECK_B6;                if (RSV3)
                    header[0] |= CHECK_B5;
                header[0] |= (byte)Type;                if (Body != null)
                {
                    ArraySegment<byte> data = this.DataPacketSerializer.FrameSerialize(this, Body);                    try
                    {                        if (MaskKey == null || MaskKey.Length != 4)                            this.IsMask = false;                        if (this.IsMask)
                        {
                            header[1] |= CHECK_B8;                            int offset = data.Offset;                            for (int i = offset; i < data.Count; i++)
                            {
                                data.Array[i] = (byte)(data.Array[i] ^ MaskKey[(i - offset) % 4]);
                            }
                        }                        int len = data.Count;                        if (len > 125 && len <= UInt16.MaxValue)
                        {
                            header[1] |= (byte)126;
                            stream.Write(header, 0, 2);
                            stream.Write((UInt16)len);
                        }                        else if (len > UInt16.MaxValue)
                        {
                            header[1] |= (byte)127;
                            stream.Write(header, 0, 2);
                            stream.Write((ulong)len);
                        }                        else
                        {
                            header[1] |= (byte)data.Count;
                            stream.Write(header, 0, 2);
                        }                        if (IsMask)
                            stream.Write(MaskKey, 0, 4);
                        stream.Write(data.Array, data.Offset, data.Count);
                    }                    finally
                    {                        this.DataPacketSerializer.FrameRecovery(data.Array);
                    }
                }                else
                {
                    stream.Write(header, 0, 2);
                }
            }            finally
            {                this.DataPacketSerializer = null;                this.Body = null;
            }
        }

复制代码

 

如果你对这代码有兴趣,最直接的方法是查看BeetleX的代码https://github.com/IKende/FastHttpApi

 作者:smark

原文出处:https://www.cnblogs.com/smark/p/10446152.html  

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消