1. 為什么會有websocket
2. websocket協議格式
3. 協議具體實現
初次接觸 WebSocket 的人,都會問同樣的問題:我們已經有了 HTTP 協議,為什么還需要另一個協議?它能帶來什么好處?
答案很簡單,因為 HTTP 協議有一個缺陷:通信只能由客戶端發起
什么是web應用、舉例來說,我們想了解今天的天氣,只能是客戶端向服務器發出請求,服務器返回查詢結果。HTTP協議做不到服務器主動向客戶端推送信息。
這種單向請求的特點,注定了如果服務器有連續的狀態變化,客戶端要獲知就非常麻煩。我們只能使用"輪詢":每隔一段時候,就發出一個詢問,了解服務器有沒有新的信息。最典型的場景就是聊天室。
輪詢的效率低,非常浪費資源(因為必須不停連接,或者HTTP連接始終打開)。因此,工程師們一直在思考,有沒有更好的方法。WebSocket就是這樣發明的
WebSocket協議在2008年誕生,2011年成為國際標準。所有瀏覽器都已經支持了。
它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬于服務器推送技術的一種。
其他特點包括:
websocket還沒準備好、1)建立在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有著良好的兼容性。默認端口也是80和443,并且握手階段采用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通信高效。
(4)可以發送文本,也可以發送二進制數據。
(5)沒有同源限制,客戶端可以與任意服務器通信。
(6)協議標識符是ws(如果加密,則為wss),服務器網址就是 URL。
html5 websocket?Browser已經支持http協議,為什么還要開發一種新的WebSocket協議呢?我們知道http協議是一種單向的網絡協議,在建立連接后,它只允許Browser/UA(UserAgent)向WebServer發出請求資源后,WebServer才能返回相應的數據。而WebServer不能主動的推送數據給Browser/UA,當初這么設計http協議也是有原因的,假設WebServer能主動的推送數據給Browser/UA,那Browser/UA就太容易受到攻擊,一些廣告商也會主動的把一些廣告信息在不經意間強行的傳輸給客戶端,這不能不說是一個災難。那么單向的http協議給現在的網站或Web應用程序開發帶來了哪些問題呢?
ajax(異步的javascript與xml技術)是一種有效利用javascript和dom的操作,以達到局部web頁面的提花和加載的異步通信手段。和以前的同步通信相比,他只更新一部分頁面,相應中傳輸餓數據量會因此的減少。
ajax輪詢的原理是,讓瀏覽器每隔一段時間就發送一次請求,詢問服務器是否有新消息。
而利用ajax實時的從服務器獲取內容,有可能導致大量的請求產生。
?
原理和ajax輪詢差不多,都是采用輪詢的方式,不過采用的是阻塞模型。也就是說,當客戶端發起連接后,如果服務器端內容沒有更新,將響應至于掛起狀態,一直不回復response給客戶端,知道有內容更新,再返回響應。
nodejs websocket?
?
雖然可以做到實時更新,但是為了保留響應,一次連接餓持續時間也變長了。期間,為了維持連接會消費更多的資源。
從上面兩種方式中,其實可以看出是再不斷的建立http連接,然后等待服務器處理,可以體現出了http的特點:被動性,即:請求只能由客戶端發起。服務器端不能主動聯系客戶端。
不管怎么樣,上面這兩種都是非常消耗資源的。
ajax輪詢 需要服務器有很快的處理速度和資源。(速度)
長輪詢 需要有很高的并發,也就是說同時接待客戶的能力。(場地大小)
除了以上這些,HTTP還是一個無狀態協議。
通俗的說就是,服務器因為每天要接待太多瀏覽器了,是個健忘鬼,你一斷連接,他就把你的東西全忘光了,把你的東西全丟掉了。你第二次還得再告訴服務器一遍。
WebSocket其實是HTTP協議上的一種補充,他們有交集但并不是全部。
websocket 支持。
?
一旦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的弊端?為了實現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協議具有以下的特點:
c++ websocket?本人最近做的項目,服務器端用的是C++寫的,而與客戶端交互用的是websocket,服務器端要想正常的使用數據,必須要對websocket協議進行解析。
?
/**
* @brief getWSFrameData 解析websocket的協議包,不能解決粘包半包問題
* @param msg 待解析的數據
* @param msgLen 待解析的數據長度
* @param outBuf 解析完成數據
* @return
*/
int unPackingWSFrameData(char *msg,
int msgLen,
std::vector<char> &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;//Opcode
opcode_ = msg[pos] & 0x0f;
pos++;
//MASK
mask_ = (unsigned char)msg[pos] >> 7;
//Payload length
payload_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-key
if(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數據的解析,返回結果為:vector<char>output;
通常會在函數外面對此進行轉換為char*,方便我們使用,見下:
vector<char>output;
char* out = &output[0];
當然,現在的解析還不是完美的解決方法,因為在實際的使用當中,會存在接收的包粘包,半包等等問題,而以上函數只能解決收到包正好是一個完整的包的情況;具體解決粘包半包問題,留待下次博客吧!
websocket原理與機制,?
只為記錄,只為分享! 愿所寫能對你有所幫助。不忘記點個贊,謝謝~
后臺私信可以領取 內容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體, WebRTC ,CDN,P2P,K8S,Docker,Golang, TCP/IP,MTK , 嵌入式 , 協程,DPDK等等 。
版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。
工作时间:8:00-18:00
客服电话
电子邮件
admin@qq.com
扫码二维码
获取最新动态