网址:http://blog.csdn.net/edwingu/article/details/44040961

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');
  • 1
  • 2
  • 1
  • 2

请求内容大致如下:

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

请求包说明: 
* 必须是有效的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=
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

应答包说明: 
*必须包括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: (.*)\r\n/",$req,$match)){ $key=$match[1]; }
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
<?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;
}
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
<?php
//组装应答包代码
$hash_data = $this->websocket_accept_key($strkey);
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: " . $hash_data . "\r\n" .
"\r\n";
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

握手成功!

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

请求包说明: 
*必须包括”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-
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

应答包说明: 
*必须包括”Upgrade”头域,其值为WebSocket; 
*必须包括”Connection”头域,其值为Upgrade; 
*必须包括”Sec-WebSocket-Origin”头域; 
*必须包括”Sec-WebSocket-Location”头域; 
*应答正文的上一行是一行空行; 
*应答正文后面没有任何结束符或者换行符。

<?php
//获取请求包中Sec-WebSocket-Key1和Sec-WebSocket-Key2值
//$req为请求包内容
if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $key1=$match[1]; }
if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $key2=$match[1]; }
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
<?php
//获取请求正文内容
$arr = explode("\r\n", $req);
$body = end($arr);
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
<?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;
} ?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

获得的这个就是应答体正文,通常是非可见字符–“乱码”。 
然后就是返回应答包给浏览器了

<?php
//组装应答包代码
$hash_data = $this->websocket_accept_key_76($strkey);
$upgrade = ""HTTP/1.1 101 WebSocket Protocol Handshake\r\n" .
"Upgrade: WebSocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Origin: $origin\r\n".
"Sec-WebSocket-Location: ws://$host/\r\n".
"\r\n" . $hash_data;
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

握手成功!

WebSocket握手总结的更多相关文章

  1. 探讨Netty获取并检查Websocket握手请求的两种方式

    在使用Netty开发Websocket服务时,通常需要解析来自客户端请求的URL.Headers等等相关内容,并做相关检查或处理.本文将讨论两种实现方法. 方法一:基于HandshakeComplet ...

  2. 使用 Sa-Token 解决 WebSocket 握手身份认证

    前言 相比于 Http 的单项通信方式,WebSocket 可以从服务器向浏览器主动推送消息,这一特性可以帮助我们完成诸如 订单消息推送.IM实时聊天 等一些特定业务. 然而 WebSocket 本身 ...

  3. node.js 使用 net 模块模拟 websocket 握手,进行数据传递。

    websocket 是一种让浏览器与服务器之间建立持久的连接,并能进行双向数据传输的一种协议. websocket 属性应用层协议,基于tcp传输协议,并复用http的握手通道. 一.如何进行webs ...

  4. flask POOL,websocket握手

    一.POOL Pool就是为了多线程访问数据库,减少数据库压力 回顾pymysql import pymysql #建立连接 mysql_conn = pymysql.connect(host=&qu ...

  5. python模拟websocket握手过程中计算sec-websocket-accept

    背景 以前,很多网站使用轮询实现推送技术.轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器.轮询的缺点很明显,浏览器需要不断的向服 ...

  6. Workman websocket 握手连接

    默认的是TCP连接方式,如果需要WebSocket,则需要更改Gateway方式, 服务端协议要和客户端协议一致才能通讯.客户端是websocket协议,服务端也要设置成websocket协议.默认为 ...

  7. websocket 实现单聊群聊 以及 握手原理+加密方式

    WebSocket 开始代码 服务端 群聊 # type:WebSocket 给变量标注类型 # websocket web + socket from geventwebsocket.server ...

  8. Flask-session,WTForms,POOL,Websocket通讯原理 -握手,加密解密过程

    1.Flask-session Flask中的session 需要执行 session_interface - open_session存储到redis中,存的key:session:d3f07db2 ...

  9. 借助Nodejs探究WebSocket

    文章导读: 一.概述-what's WebSocket? 二.运行在浏览器中的WebSocket客户端+使用ws模块搭建的简单服务器 三.Node中的WebSocket 四.socket.io 五.扩 ...

随机推荐

  1. springAOP源码解析-190313

    Spring相关笔记 SpringAOP讲解 子路老师讲解 spring与aspectj的区别答:它们的区别是 spring是动态加载 aspectj是静态加载,再编译过程就已经实现切面,此时会往代码 ...

  2. NOIP2000方格取数(洛谷,动态规划递推)

    先上题目: P1004 方格取数 下面上ac代码: ///如果先走第一个再走第二个不可控因素太多 #include<bits/stdc++.h> #define ll long long ...

  3. 洛谷——P4296 [AHOI2007]密码箱

    P4296 [AHOI2007]密码箱 密码x大于等于0,且小于n,而x的平方除以n,得到的余数为1. 求这个密码,$1<=n<=2,000,000,000$ 暴力枚举,数据有点儿水$O( ...

  4. 利用stylist插件,简单两步屏蔽新浪微博上的广告

    以前新浪微博只是在侧栏有几块小小的广告,还算可以接受,想着忍忍就算了,可最近真是越来越不厚道了,自从和淘宝合作之后,侧栏就开始有一大块广告根据你在淘宝的搜索记录推荐商品,更可恶的是信息流里的祛痘微博现 ...

  5. 51NOD 1287 加农炮(不水的线段树)

    >>点击进入原题测试<< Input示例 Output示例 思路:刚开始以为结点存最大值就行了,然后大于左子树的最大值就能进入右子树:然后发现样例都过不了:后面发现,并不是这个 ...

  6. 勇者斗恶龙 UVA 11292

    Once upon a time, in the Kingdom of Loowater, a minor nuisance turned into a major problem. The shor ...

  7. DS26C31M和DS26C32AM

    DS26C31M是产生差分信号的芯片,实际使用的时候,测得在5V供电的情况下,输出+和输出-的压差是5V其中输出+和输入的特性相同. DS26C32AM是接收差分信号的芯片,与DS26C31M可以向配 ...

  8. type="timestamp"与type="date"区别

    type="timestamp"-----数据库中保存的时间为年月日时分秒 与type="date"---------数据库中保存的时间为年月日

  9. vue项目使用static目录存放图片解决方案

    我个人喜欢把所有引用文件全部放在打包文件src的同级文件static文件内部,方便整合. 提醒:vue项目中正常情况下图片是由 url-loader 处理,加入了hash值,如果放到static里面w ...

  10. BNUOJ 1260 Brackets Sequence

    Brackets Sequence Time Limit: 1000ms Memory Limit: 65536KB This problem will be judged on PKU. Origi ...