websocket没准备好如何解决_那些很重要,但是不常用的技术,websocket

目录

1. 为什么会有websocket

2. websocket协议格式

3. 协议具体实现

一、为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起

举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP协议做不到服务器主动向客户端推送信息。

这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。

轮询的效率低,非常浪费资源(因为必须不停连接,或者HTTP连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket就是这样发明的

WebSocket协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

其他特点包括:

1)建立在 TCP 协议之上,服务器端的实现比较容易。

(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

(3)数据格式比较轻量,性能开销小,通信高效。

(4)可以发送文本,也可以发送二进制数据。

(5)没有同源限制,客户端可以与任意服务器通信。

(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

websocket协议格式

Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道http协议是一种单向的网络协议,在建立连接后,它只允许Browser/UA(UserAgent)向WebServer发出请求资源后,WebServer才能返回相应的数据。而WebServer不能主动的推送数据给Browser/UA,当初这么设计http协议也是有原因的,假设WebServer能主动的推送数据给Browser/UA,那Browser/UA就太容易受到攻击,一些广告商也会主动的把一些广告信息在不经意间强行的传输给客户端,这不能不说是一个灾难。那么单向的http协议给现在的网站或Web应用程序开发带来了哪些问题呢?一条连接上只可以发送一个请求

请求只能从客户端开始。客户端不可以接收除了响应以外的指令。

请求 / 响应首部未经过压缩就直接进行传输。首部的信息越多,那么延迟就越大。

发送冗长的首部。每次互相发送相同的首部造成的浪费越多

可以任意选择数据压缩格式。非强制压缩发送

ajax轮询

ajax(异步的javascript与xml技术)是一种有效利用javascript和dom的操作,以达到局部web页面的提花和加载的异步通信手段。和以前的同步通信相比,他只更新一部分页面,相应中传输饿数据量会因此的减少。

ajax轮询的原理是,让浏览器每隔一段时间就发送一次请求,询问服务器是否有新消息。

而利用ajax实时的从服务器获取内容,有可能导致大量的请求产生。

长轮询

原理和ajax轮询差不多,都是采用轮询的方式,不过采用的是阻塞模型。也就是说,当客户端发起连接后,如果服务器端内容没有更新,将响应至于挂起状态,一直不回复response给客户端,知道有内容更新,再返回响应。

虽然可以做到实时更新,但是为了保留响应,一次连接饿持续时间也变长了。期间,为了维持连接会消费更多的资源。

从上面两种方式中,其实可以看出是再不断的建立http连接,然后等待服务器处理,可以体现出了http的特点:被动性,即:请求只能由客户端发起。服务器端不能主动联系客户端。

不管怎么样,上面这两种都是非常消耗资源的。

ajax轮询 需要服务器有很快的处理速度和资源。(速度)

长轮询 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

除了以上这些,HTTP还是一个无状态协议。

通俗的说就是,服务器因为每天要接待太多浏览器了,是个健忘鬼,你一断连接,他就把你的东西全忘光了,把你的东西全丢掉了。你第二次还得再告诉服务器一遍。

WebSocket

WebSocket其实是HTTP协议上的一种补充,他们有交集但并不是全部。

一旦web服务器和客户端建立起websocket协议的通信连接,之后所有的通信都依靠这个专用连接进行。只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。

websocket是基于HTTP协议的,或者说借用了http的协议来完成一部分握手。为了实现websocket通信,在http建立连接后,还需要进行一次“握手”的步骤。

握手 · 请求

GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com

为了实现websocket通信,需要用到http的Upgrade首部字段,告知服务器通信协议已发生改变:我要发起的是websocket协议。以达到握手的目的。

Sec-WebSocket-Key字段记录着握手必不可少的键值,用于验证服务器是否支持websocket通信。

Sec-WebSocket-Protocol字段记录的是所需要使用的协议。

握手 · 响应

HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HsMrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat

对于客户端的请求,服务器返回状态码 101 Switching Protocols的响应。

返回Upgrate告诉客户端即将升级的协议是Websocket协议。

Sec-WebSocket-Accept字段值是由握手请求中的Sec-WebSocket-Key字段值加密过后生成的。

Sec-WebSocket-Protocol 则是表明最总使用的协议。

到这里,http已经完成所有他的工作了,接下来通信时不再使用HTTP的数据帧,而是使用websocket独立的数据帧。

因此,websocket协议具有以下的特点:推送功能

支持服务器端向客户端推送功能。服务器可以直接发送数据而不用等待客户端的请求。

减少通信量

只要建立起websocket连接,就一直保持连接,在此期间可以源源不断的传送消息,直到关闭请求。也就避免了HTTP的非状态性。

减少资源消耗

那么为什么他会解决服务器上消耗资源的问题呢?其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(PHP等)来处理。简单地说,我们有一个非常快速的接线员(Nginx),他负责把问题转交给相应的客服(Handler)。本身接线员基本上速度是足够的,但是每次都卡在客服(Handler)了,老有客服处理速度太慢。导致客服不够。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。这样就可以解决客服处理速度过慢的问题了。

协议具体实现

前提:

本人最近做的项目,服务器端用的是C++写的,而与客户端交互用的是websocket,服务器端要想正常的使用数据,必须要对websocket协议进行解析。

WebSocket数据格式

FIN:表示这个数据是不是接收完毕,为1表示收到的数据是完整的,占1bit

RSV1~3:用于扩展,通常都为0,各占1bit

OPCODE:表示报文的类型,占4bit 0x00:标识一个中间数据包0x01:标识一个text数据包0x02:标识一个二进制数据包0x03~07:保留0x08:标识一个断开连接数据包0x09:标识一个ping数据包0x0A:标识一个pong数据包0x0B~F:保留

MASK:用于表示数据是否经常掩码处理,为1时,Masking-key即存在,占1bit

Payload len:表示数据长度,即Payload Data的长度,当Payload len为0~125时,表示的值就是Payload Data的真实长度;当Payload len为126时,报文其后的2个字节形成的16bits无符号整型数的值是Payload Data的真实长度(网络字节序,需转换);当Payload len为127时,报文其后的8个字节形成的64bits无符号整型数的值是Payload Data的真实长度(网络字节序,需转换);

Masking-key:掩码,当Mask为1时存在,占4字节32bit

Payload Data:表示数据

C++对websocket协议处理

/**

* @brief getWSFrameData 解析websocket的协议包,不能解决粘包半包问题

* @param msg 待解析的数据

* @param msgLen 待解析的数据长度

* @param outBuf 解析完成数据

* @return

*/int unPackingWSFrameData(char *msg,int msgLen,std::vector &outBuf){//报文长度一定大于2字节,对于小于的,做返回处理if(msgLen < 2)

{return -3;

}uint8_t opcode_ = 0;uint8_t mask_ = 0;uint8_t masking_key_[4] = {0,0,0,0};uint64_t payload_length_ = 0;int pos = 0;//Opcodeopcode_ = msg[pos] & 0x0f;

pos++;//MASKmask_ = (unsigned char)msg[pos] >> 7;//Payload lengthpayload_length_ = msg[pos] & 0x7f;

pos++;if(payload_length_ == 126)

{uint16_t length = 0;memcpy(&length, msg + pos, 2);

pos += 2;

payload_length_ = ntohs(length);

}else if(payload_length_ == 127)

{uint32_t length = 0;memcpy(&length, msg + pos, 8);

pos += 8;

payload_length_ = ntohl(length);

}//Masking-keyif(mask_ == 1)

{for(int i = 0; i < 4; i++)

{

masking_key_[i] = msg[pos + i];

}

pos += 4;

}//取出消息数据if (msgLen >= pos + payload_length_ )

{

outBuf.clear();if(mask_ != 1)

{char* dataBegin = msg + pos;

outBuf.insert(outBuf.begin(), dataBegin, dataBegin+payload_length_);

}else{for(uint i = 0; i < payload_length_; i++)

{int j = i % 4;

outBuf.push_back(msg[pos + i] ^ masking_key_[j]);

}

}

}else{//此时包长小于报文中记录的包长return -2;

}//断开连接类型数据包if ((int)opcode_ == 0x8)return -1;return 0;

}

以上函数即实现了对收到websocket数据的解析,返回结果为:vectoroutput;

通常会在函数外面对此进行转换为char*,方便我们使用,见下:

vectoroutput;char* out = &output[0];

当然,现在的解析还不是完美的解决方法,因为在实际的使用当中,会存在接收的包粘包,半包等等问题,而以上函数只能解决收到包正好是一个完整的包的情况;具体解决粘包半包问题,留待下次博客吧!

参考资料:后台私信“资料”送

结尾:只为记录,只为分享! 愿所写能对你有所帮助。不忘记点个赞,谢谢~

后台私信可以领取 内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体, WebRTC ,CDN,P2P,K8S,Docker,Golang, TCP/IP,MTK , 嵌入式 , 协程,DPDK等等 。

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

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

相关文章

DFS深搜与BFS广搜专题

一般搜索算法的流程框架 DFS和BFS与一般搜索流程的关系 如果一般搜索算法流程4使用的是stack栈结构(先进后出&#xff0c;后进先出)那么就会越搜越深。即&#xff0c;DFS&#xff0c;DFS只保存当前一条路径&#xff0c;其目的是枚举出所有可能性。反之&#xff0c;如果流程4使…

cloud foundry_使用“另类” Cloud Foundry Gradle插件无需停机

cloud foundry我一直在尝试编写用于将应用程序部署到Cloud Foundry的gradle插件 &#xff0c;并在上一篇文章中写了有关此插件的文章 。 现在&#xff0c;我通过使用两种方法支持将无停机时间部署到Cloud Foundry中来增强此插件&#xff1a; 自动驾驶风格部署和更常用的蓝绿色风…

lisp文字上标源码_创建文本/标注样式源码 - AutoLISP/Visual LISP 编程技术 - CAD论坛 - 明经CAD社区 - Powered by Discuz!...

本帖最后由 zhengxiansz 于 2014-4-27 11:27 编辑GU_xl你好&#xff01;请帮我看一下这个创建文本/标注样式源码。第一次输入IT1命令时没有报错的提示。如果重复输入IT1命令时就会提示该名称已被使用&#xff0c;是否重新定义&#xff1f;请问有什么方法可以解决吗&#xff1f;…

从前有座山,山里有座庙:递归之法

递归描述 递归调用是函数内部调用自身的过程&#xff0c;递归必须要有结束条件&#xff0c;否则会进入无限递归状态。无法停止。 我们称这个条件为&#xff08;递归基&#xff09; 递归原理 递归包括&#xff08;递推&#xff09;和&#xff08;回归&#xff09;&#xff0c;…

你只是看起来很努力_我的方法在这方面看起来很大吗?

你只是看起来很努力以下Java方法的大小是多少&#xff1f; public Collection getDescription() {SystemLibrary systemLib registry.get(SystemLibrary.class);Analysis analysis systemLib.getCurrentAnalysis(registry);return getDescription(analysis);}这个不起眼的方…

0xFFFFFF的问题

这个有两个结果&#xff1a;16777215和-1 如果是单纯的16 进制数&#xff0c;那么转换为10 进制数为16777215。相信这个转换结果很好理解&#xff1a;15*16^015*16^1...15*16^5。j即&#xff08;int型所能表示的最大值&#xff09; 第二种情况就是 0xFFFFFF 如果是在计算机中…

js模仿f11全屏_JS实现全屏预览F11功能的示例代码

老是不通过&#xff0c;没办法&#xff0c;只能是重新发布了&#xff0c;反正我就是杠上了&#xff0c;大大小小写过很多前端特效&#xff0c;当然也经常在网上copy或者修改人家的代码&#xff0c;我觉得也挺好的&#xff0c;为什么&#xff1f;&#xff01;因为我想这样&#…

懒惰学习_懒惰评估

懒惰学习最近&#xff0c;我正在编写log4j附加程序&#xff0c;并希望在自定义附加程序创建过程中使用logger记录一些诊断详细信息&#xff0c;但是log4j初始化仅在创建附加程序实例后才完成&#xff0c;因此在此阶段记录的消息将被忽略。 我感到需要在自定义附加程序中进行延…

leetcode(动态规划专题)

线性DP 53. 最大子数组和 思路 code int maxSubArray(vector<int>& nums) {//res:最后所有状态的最终Max结果//lat:当前f[i]状态的Maxint res INT_MIN, last 0;for (int i 0; i < nums.size(); i){//当前f[i]状态最大值(使用下面的状态转移方程得出)//f[i] …

买个云服务器有啥用_买了一台云服务器到底能干嘛?

提起云计算、大数据&#xff0c;好像都属于科技前沿的东西&#xff0c;总是觉得它离我们很遥远。但是科技的发展应该是要惠及普罗大众的&#xff0c;所以云计算的发展也并不是与我们毫不相干的&#xff0c;很多云计算方面的功能是我们工作生活可以用到、并解决问题的&#xff0…

leetcode(链表专题)

数组模拟链表 #include<iostream> using namespace std;const int N 100; // 单链表 // head存储链表头&#xff0c;e[]存储节点的值&#xff0c;ne[]存储节点的next指针&#xff0c;idx表示当前用到了哪个节点 int head, e[N], ne[N], idx;// 初始化 void init() {hea…

lagom cqrs_Java和Lagom的CQRS

lagom cqrs我很高兴在Chicago Java User Group上进行了讨论&#xff0c;并讨论了Lagom如何实现CQRS&#xff08;命令查询责任隔离模式&#xff09;。 值得庆幸的是&#xff0c;有一个录音&#xff0c;我还把这些幻灯片发布在slideshare上 。 抽象&#xff1a; 一旦应用程序变…

python项目管理器 宝塔面板 django 404_宝塔面板成功部署Django项目流程(图文)

上线 Django 项目记录&#xff0c;超简单&#xff0c;避免无意义的踩坑&#xff01;第一步&#xff1a;安装python管理器在宝塔在线面板安装“ python项目管理器 ”第二步&#xff1a;安装适配python版本因为服务器 centos7 系统默认的 python 版本是 2.7而我们项目是基于最新版…

leetcode(字符串专题)

5. 最长回文子串 思路

使用Spring boot,Thymeleaf,AngularJS从零开始构建新的Web应用程序–第3部分

在之前的博客中&#xff0c;我们使用Thymeleaf&#xff0c;Bower和Bootstrap构建了登录页面&#xff0c;并将其部署到了Heroku。 在此博客中&#xff0c;我们将介绍用于前端的AngularJS和在后端的Spring Boot Web服务的一些功能。 我们将从“登录/注销”部分开始。 让我们开始…

android的oomkiller_Android Low memory killer

Android Low memorykillerby 永远的伊苏Android中&#xff0c;进程的生命周期都是由系统控制的&#xff0c;即使用户关掉了程序&#xff0c;进程依然是存在于内存之中。这样设计的目的是为了下次能快速启动。当然&#xff0c;随着系统运行时间的增长&#xff0c;内存会越来越少…

C++ 11 深度学习(十二)函数新特性、内联函数、const详解

函数后置返回类型 //后置返回类型 auto fun(int, int)->int; 内联函数 在函数定义前增加关键字 inline ,使得该函数变成内联函数 (1) 适用于函数体很小&#xff0c;调用很频繁的函数类型&#xff0c;可以引入内联函数 (2) inline影响编译器&#xff0c;在编译阶段对inc…

apache pdfbox_Apache PDFBox 2

apache pdfboxApache PDFBox 2已于今年早些时候发布 &#xff0c; Apache PDFBox 2.0.1和Apache PDFBox 2.0.2已发布。 Apache PDFBox是开源的&#xff08; Apache许可证版本2 &#xff09;并且基于Java&#xff08;因此易于使用&#xff0c;包括Java &#xff0c; Groovy &…

cad坐标归零lisp_CAD图怎么归零

展开全部原理&#xff0c;就是把图元Z轴线移动的负无穷远&#xff0c;然e68a84e8a2ad62616964757a686964616f31333335336530后移动到正无穷&#xff0c;除了块就可以Z轴线归零了。用autoLISP来解决。;;;;;;Z坐标归零;;(defun c:z0 ( / &kw &k1 #os1)(setvar "cmde…