BeetleX之Websocket协议分析详解

        Websocket应用协议已经普及多年了,它是HTTP1.1的内部升级协议,主要作用是补充HTTP1.1无法灵活地主动推送消息给客户端的缺陷问题。在这里主要介绍一下使用组件如何扩展一个完整的Websocket协议。

协议介绍

        Websocket并不复杂,但协议文档内容还是很全面的,以下是协议原文

https://tools.ietf.org/html/rfc6455。其实一个简单的图可以看出Websocket协议结构。

在这里主要介绍组件是如何实现的就不详细介绍内容了。

存储顺序

        在协议中有一个地方需要关注存储顺序,那就是消息长度描述。不同语言平台对于基础值类型的存储顺序都不一样分别是:大端和小端。这个协议使用的是大端存储顺序,但.NET则是使用小端存储顺序;所以使用组件解Weboskcet协议前要更改一下流读写的存储顺序。

IServer.Options.LittleEndian = false;

组件可以通过配置来统一更改网络流针对大小端读写配置,应用中也可以默认用小端读出来后再移位转换也是可以。

分析状态

        虽然Websocket已经有协议描述,但在分析过程中还是需要一些状态来处理。在TCP流中无法知道当前buffer里的情况,有可能不到一个消息帧,或存在多个消息帧;更有可能当前流的尾部可能只两个字节内容的playload len 127的情况;为了应对存在不同状态的网络流,在分析协议过程需要制定各种状态,以便于下一次网络数据到来直接跑到相关状态分配处理。

public enum DataPacketLoadStep
{//量开始状态None,//分析完头部信息Header,//分析完成内容长度信息Length,//内容在校检状态Mask,//分析完成Completed
}

握手处理

        其实Websocket设计作为http 1.1的一个升级协议,所以在连接开始是通过http协议作为应用握手确认;确认后双方即可随意发送基于websocket协议描述的帧数据。

        当服务端收到HTTP请求存在Upgrade头部信息的内容是Websocket的情况说明客户端要求升级到Websocket协议。

        GET /chat HTTP/1.1Host: server.example.comUpgrade: websocketConnection: UpgradeSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Origin: http://example.comSec-WebSocket-Protocol: chat, superchatSec-WebSocket-Version: 13

如果接受升级,服务端响应相关内容即可

        HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

组件FastHttpApi对应代码

https://github.com/IKende/FastHttpApi/blob/master/src/HttpApiServer.cs#L691

数据帧解包

        WebSocket的数据帧解释比起http协议麻烦些,毕竟http协议都是换行拆分即可;而WebSocket则需要涉及到位信息处理。

        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){mLoadStep = DataPacketLoadStep.Completed;}else{if ((ulong)stream.Length >= this.Length){if (this.IsMask)ReadMask(stream);Body = this.DataPacketSerializer.FrameDeserialize(this, stream);mLoadStep = DataPacketLoadStep.Completed;}}}return mLoadStep;}

看完以上代码相信会有人问,写这么复杂干什么吗,几个字节的长度都需要判断吗?一次接收的信息不可能几个字节都没有。出现这情况的主要原因是当某端推送大量的消息,这些消息经过不同的网络环境和MTU限制后,可能出现帧的头部内容被拆到两个接收缓冲区中,所以在处理上需要完全考虑这种情况。

数据帧封包 

void IDataResponse.Write(PipeStream stream)
{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;//大于135小于unit16长度的消息头写入if (len > 125 && len <= UInt16.MaxValue){header[1] |= (byte)126;stream.Write(header, 0, 2);stream.Write((UInt16)len);}//大于unit16长度头写入else if (len > UInt16.MaxValue){header[1] |= (byte)127;stream.Write(header, 0, 2);stream.Write((ulong)len);}else{//小于126长度写入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);}
}

封包就简单了,除了判断长度写入不同的头信息外其他都是直接写入。以上代码可以查看

https://github.com/IKende/FastHttpApi/blob/master/src/WebSockets/DataFrame.cs

【BeetleX通讯框架代码详解】
BeetleX

开源跨平台通讯框架(支持TLS)
轻松实现高性能:tcp、http、websocket、redis、rpc和网关等服务应用

https://beetlex.io

如果你想了解某方面的知识或文章可以把想法发送到

henryfan@msn.com|admin@beetlex.io

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307350.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

甲骨文是否可以要求 Java API 享有版权?这场10年官司怎么结

美国最高法院10月7日就 Oracle 甲骨文诉 Google 谷歌一案进行口头辩论。案件一直以来争议的核心是&#xff0c;甲骨文是否可以要求 Java API 享有版权&#xff0c;如果可以&#xff0c;那么谷歌是否侵犯了这些版权&#xff1f;Java API 是否可以享有版权&#xff1f;Sun 公司在…

C#知多少 | 每个版本都更新了什么?

总所周知&#xff0c;.NET5.0马上就要来了&#xff0c;最后一个预览版RC2也已经发布了&#xff0c;在11月的时候&#xff0c;我们就正式的发布了&#xff0c;然后我们就可以迁移使用了&#xff0c;当然今天说的重点不是.NET&#xff0c;今天说的是伴随着.NET5一起到来的C#9.0&a…

leetcode40. 组合总和 II(树层去重)

一:题目 二:上码 class Solution {/**注意这里的去重:1.我们分为树层去重比如[1,1 2,5] target 8,那么如果我们用树枝去重的话,那么就会出现[1,2,5],[1,2,5]那么的话我们就需要树层去重 就是横向遇见相同的元素跳过。2.树枝去重的 树枝去重就是 我们数组前面使用过的元素不…

WPF开源控件扩展库 - MaterialDesignExtensions

WPF开源控件扩展库 - MaterialDesignExtensionsMaterialDesignExtensions仓库截图logoMaterial Design Extensions 在WPF开源控件库 ????Material Design in XAML Toolkit(本站介绍&#xff1a;????链接)的基础上进行了控件扩展和特性新增。本开源项目中的控件或许不在…

leetcode131. 分割回文串

一:题目 二:上码 class Solution {/**思路:1.纵向递归的话 我们递归深度是 我们的 切割线切到了最后 2.横向for 我们的宽度就是字符串宽度3.在哪截取的话 那就是在哪个坐标的后面*/List<List<String>> ans new ArrayList<>();List<String> path n…

WordPress 已过时?创始人与新架构拥护者开战

喜欢就关注我们吧&#xff01;近日&#xff0c;WordPress 创始人 Matt Mullenweg 与 Netlify 首席执行官 Matt Biilmann 展开了一场口水战&#xff0c;双方就 WordPress 与新兴 WEB 应用架构 JAMStack 孰优孰劣进行了激烈的辩论。何为 JAMStack?JAMStack 是一种将 Web 应用部署…

leetcode93. 复原 IP 地址(思路+详解)

一:题目 二:上码 class Solution {/**思路: 1.跟分割字符串差不多,但是我们需要记录添加的.的个数 当其个数等于3的时候 我们就已经切割成4段了。2.判断字符是否合法1>:以0开头的数字不合法2>:遇见非数字字符不合法3>:大于255不合法*/private List<String> an…

今天的你,又被带节奏了吗?

这是头哥侃码的第220篇原创十一期间&#xff0c;我在某微信群看到几个哥们在讨论 与「带节奏」有关的话题。很显然&#xff0c;像这种略带吐槽的的话题是很容易引起共鸣的。于是你一句他一句的&#xff0c;用自带表情包的方式开始说起了「带节奏」的各种操作。先是A君&#xff…

谷歌最终还是把Knative交了出来

谷歌方面正计划将其开源 Knative 项目的直接控制权让渡给一个指导委员会。Knative 是谷歌开源的一套 Serverless 架构方案&#xff0c;它扩展了 Kubernetes&#xff0c;专注于解决容器为核心的 Serverless 应用的构建、部署和运行的问题&#xff0c;它构建在 Kubernetes 和 Ist…

你那么普通 所以一定要自信啊

你这么普通&#xff0c;必须自信啊 ​ 昨天讲到&#xff0c;不管你准没准备好&#xff0c;该来的都来了。只要你进入了社会&#xff0c;这一切都没得选&#xff0c;好的坏的甭管啥事&#xff0c;都一股脑摆在你面前&#xff0c;你必须要处理。你说我不行啊&#xff0c;我做不到…

leetcode293周赛6064. 不含特殊楼层的最大连续楼层数

一:题目 二:上码 // class Solution { // public:// bool find(vector<int>& v,int i) { // for (auto nums:v) { // if (nums i) return true;//包含某个数 就返回true // } // return false; // }// int maxCon…

leetcode293周赛5234. 移除字母异位词后的结果数组

一:题目 二:上码 class Solution { public:/**1.所谓字母异位词就是相同的字母组成的字符串&#xff08;这个字符串可以相同&#xff09;2.首先判断两个字符串中的字母是否相同用map进行计数&#xff0c;然后在另一个字符串中查找某个字符进行--;3.如果最终的map中所有的value…

七国要求科技巨头预留后门 应对马甲芯片高度警惕

日前&#xff0c;美国、英国、日本、澳大利亚、加拿大、新西兰、印度呼吁科技公司设计产品时确保政府可以访问经过加密的消息和内容。其实&#xff0c;西方科技公司配合西方政府在产品中预留后门早已不是新鲜事&#xff0c;斯诺登就曾经爆料多个美国科技巨头配合美国情报部门收…

leetcode78. 子集

一:题目 二:上码 class Solution {/**横向递归的是:我们子集的长度纵向递归的是:我们子集的中元素的更改*/List<List<Integer>> ans new ArrayList<>();List<Integer> path new ArrayList<>();public void getAns(int[] nums,int st) {ans.ad…

跟我一起学Redis之看完这篇比常人多会三种类型实战(又搞了几个小时)

前言对于Redis而言&#xff0c;很多小伙伴只关注其关键的五大基础类型&#xff1a;string、hash、list、set、sorted set(有序集合)&#xff0c;其实还有三种特殊类型在很多应用场景也比较适合使用&#xff0c;分别是&#xff1a;bitmap、geospatial、hyperloglog&#xff1b;上…

leetcode90. 子集 II(树层去重)

一:题目 二:上码 class Solution {/**- 先排序- 树层去重:那么直接在从横向循环中去重即可*/List<List<Integer>> ans new ArrayList<>();List<Integer> path new ArrayList<>();public void getAns(int[] nums,int st) {ans.add(new ArrayLi…

Rancher 2.5 正式发布,多项重大更新来袭!

喜欢就关注我们吧&#xff01;近日&#xff0c;Kubernetes 管理平台 Rancher 2.5 正式发布&#xff0c;官方公布了在 Rancher 2.5 中的多项重大更新&#xff0c;包括全新的安装体验、针对边缘集群的大规模 GitOps 以及 EKS 集群的全生命周期管理。具体更新信息如下&#xff1a;…

leetcode491. 递增子序列(树层去重魔改版)

一:题目 二:上码 class Solution {/**思路:1.这里已经指明了不允许有重复的&#xff0c;那么树层去重 树枝去重(以前用过的元素不再使用)2.坑:这里不能用 传统的树层去重了 因为我们无法排序了3.这里给出的解决办法是 是利用 hashmap,对每个数出现的频率进行统计如果其频率…

数据结构与算法专题——第十二题 Trie树

今天来聊一聊Trie树&#xff0c;Trie树的名字有很多&#xff0c;比如字典树&#xff0c;前缀树等等。一&#xff1a;概念 下面有and,as,at,cn,com这几个关键词&#xff0c;构建成 trie 树如下。从上面图中&#xff0c;应该可以或多或少的发现一些好玩的特性。根节点不包含字符&…

leetcode46. 全排列

一:题目 二:上码 class Solution {/**1.全排列问题,我们的横向都是从0开始不再是st了 因为我们用到前面已经使用过的元素单是在纵向递归当中我们不能使用重复的元素&#xff0c;这里我们用used来去重。*/private List<List<Integer>> ans new ArrayList<>(…