WebSocket握手总结

我们都知道http有三次握手,如下图:


WebSocket握手总结_第1张图片

但是有没有听说过WebSocket握手呢?

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。


以前网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每隔1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客服端的浏览器。这种传统的HTTP request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。

在 WebSocket API,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

握手协议


在实现websocket连线过程中,需要通过浏览器发出websocket连线请求,然后服务器发出回应,这个过程通常称为“握手” (handshaking)。 

该方案处在草案阶段,目前在使用的有两个版本,一个是以chrome为首的使用的version 13(目前最新),该版本出现在RFC6455中。另一个是以safari(包括桌面和移动版本)为首的使用的draft-ietf-hybi版。 

chrome版–新版 

safari版–旧版

以下分别介绍两个版本的握手方法

Chrome版

客户端请求web socket连接时,会向服务器端发送握手请求

客户端请求:

var ws = new WebSocket('ws://192.168.0.10:8080');

请求内容大致如下:

GET / HTTP/1.1

Host: 192.168.0.10:8080

Connection: Upgrade

Pragma: no-cache

Cache-Control: no-cache

Upgrade: websocket

Origin: null

Sec-WebSocket-Version: 13

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36

Accept-Encoding: gzip, deflate, sdch

Accept-Language: zh-CN,zh;q=0.8

Sec-WebSocket-Key: VR+OReqwhymoQ21dBtoIMQ==

Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

请求包说明: 


* 必须是有效的http request 格式; 

* HTTP request method 必须是GET,协议应不小于1.1 如: Get / HTTP/1.1; 

* 必须包括Upgrade头域,并且其值为”websocket”; 

* 必须包括”Connection” 头域,并且其值为”Upgrade”; 

* 必须包括”Sec-WebSocket-Key”头域,其值采用base64编码的随机16字节长的字符序列; 

* 如果请求来自浏览器客户端,还必须包括Origin头域 。 该头域用于防止未授权的跨域脚本攻击,服务器可以从Origin决定是否接受该WebSocket连接; 

* 必须包括”Sec-webSocket-Version” 头域,当前值必须是13; 

* 可能包括”Sec-WebSocket-Protocol”,表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之; 

* 可能包括”Sec-WebSocket-Extensions”, 协议扩展, 某类协议可能支持多个扩展,通过它可以实现协议增强; 

* 可能包括任意其他域,如cookie.

服务器端响应如下:


HTTP/1.1 101 Web Socket Protocol Handshake

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept: Y+Te7S7wQJC0FwXumEdGbv9/Mek=

应答包说明: 


*必须包括Upgrade头域,并且其值为”websocket”; 

*必须包括Connection头域,并且其值为”Upgrade”; 

*必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码,就是“Sec-WebSocket-Accept”的值; 

*应答包中冒号后面有一个空格; 

*最后需要两个空行作为应答包结束。

<?php

//获取请求包中Sec-WebSocket-Key的值

//$req为请求包内容

if(preg_match("/Sec-WebSocket-Key: (.*)
/",$req,$match)){ $key=$match[1]; }

?>

<?php

    //计算Sec-WebSocket-Accept值算法

    private function websocket_accept_key($strkey){

        $strkey .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

        $hash_data = base64_encode(sha1($strkey,true));

        return $hash_data;

    }

?>

<?php

//组装应答包代码

$hash_data = $this->websocket_accept_key($strkey);

$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake
" .

    "Upgrade: websocket
" .

    "Connection: Upgrade
" .

    "Sec-WebSocket-Accept: " . $hash_data . "
" .

    "
";

?>

握手成功!

Safari版

请求内容大致如下:

GET / HTTP/1.1

Upgrade: WebSocket

Connection: Upgrade

Host: 192.168.0.10:8080

Origin: file://

Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5

Sec-WebSocket-Key2: 12998 5 Y3 1  .P00



^n:ds[4U

请求包说明: 

*必须包括”Sec-WebSocket-Key1”头域,由可见字符组成; 

*必须包括”Sec-WebSocket-Key2”头域,由可见字符组成; 

*在请求包的最后,会有一行请求正文,它不属于任何头域,我们可以理解为”Sec-WebSocket-Key3”,因为它需要参与到计算应答体; 

*请求正文的上一行是一行空行; 

*请求正文大部分情况下是非可见字符,俗称乱码,但这不影响后面的计算。

服务器端响应如下:

HTTP/1.1 101 WebSocket Protocol Handshake

Upgrade: WebSocket

Connection: Upgrade

Sec-WebSocket-Origin: file://

Sec-WebSocket-Location: ws://192.168.0.10:8080/



8jKS'y:G*Co,Wxa-

应答包说明: 

*必须包括”Upgrade”头域,其值为WebSocket; 

*必须包括”Connection”头域,其值为Upgrade; 

*必须包括”Sec-WebSocket-Origin”头域; 

*必须包括”Sec-WebSocket-Location”头域; 

*应答正文的上一行是一行空行; 

*应答正文后面没有任何结束符或者换行符。

<?php

//获取请求包中Sec-WebSocket-Key1和Sec-WebSocket-Key2值

//$req为请求包内容

if(preg_match("/Sec-WebSocket-Key1: (.*)
/",$req,$match)){ $key1=$match[1]; }

if(preg_match("/Sec-WebSocket-Key2: (.*)
/",$req,$match)){ $key2=$match[1]; }

?>



<?php

//获取请求正文内容

$arr = explode("
", $req);

$body = end($arr);

?>

<?php

 /*

    计算应答正文内容

    1.把Sec-WebSocket-Key1中的数字从左到右提取出来,上面的例子是:4146546015;

    2.把Sec-WebSocket-Key2中的数字从左到右提取出来,上面的例子是:1299853100;

    3.计算Sec-WebSocket-Key1中的空格数,上面的例子是:5(冒号后面的空格不算);

    4.计算Sec-WebSocket-Key2中的空格数,上面的例子是:5(冒号后面的空格不算);

    5.将提取出来的数字除以空格数,去整,分别得到829309203 和 259970620;

    6.将上一步计算得到的数字分别以Big-Endian的方式打包,拼接,然后再与请求正文的8个字节拼接,计算其MD5值。

 */

 public function websocket_accept_key_76($key1, $key2, $body){



        $tmp = array();

        $kv1 = 0;

        $kv2 = 0;

        $c1 = 0;

        $c2 = 0;



        $key1Len = strlen($key1);

        $key2Len = strlen($key2);



        for($i = 0; $i < $key1Len; $i++){

            if($key1[$i]>='0' && $key1[$i]<='9') $kv1 = $kv1*10+($key1[$i]-'0');

            else if($key1[$i]==' ') $c1++;

        }



        for($i = 0; $i < $key2Len; $i++){

            if($key2[$i]>='0' && $key2[$i]<='9') $kv2 = $kv2*10+($key2[$i]-'0');

            else if($key2[$i]==' ') $c2++;

        }



        $kv1 = $kv1/$c1;

        $kv2 = $kv2/$c2;



        $key1_sec = pack("N",$kv1);

        $key2_sec = pack("N",$kv2);



        $hash_data = md5($key1_sec.$key2_sec.$body,true);

        return $hash_data;

    }



?>

获得的这个就是应答体正文,通常是非可见字符–“乱码”。 

然后就是返回应答包给浏览器了

<?php

//组装应答包代码

$hash_data = $this->websocket_accept_key_76($strkey);

$upgrade = ""HTTP/1.1 101 WebSocket Protocol Handshake" .

                "Upgrade: WebSocket" .

                "Connection: Upgrade" .

                "Sec-WebSocket-Origin: $origin".

                "Sec-WebSocket-Location: ws://$host/
".

                "
" . $hash_data;

?>

握手成功!

--------------------- 

作者:edwingu 

来源:CSDN 

原文:https://blog.csdn.net/edwingu/article/details/44040961 

版权声明:本文为博主原创文章,转载请附上博文链接!

华丽的分隔线


640?wx_fmt=png

你可能感兴趣的:(WebSocket握手总结)