一.简介
http请求只好由客户端主动发起,服务器响应的方式, 服务器难以主动向客户端推数据,websocket的出现完美的解决了这一问题。websocket跟http处于同一层,都是基于TCP合同的,客户端跟服务器使用websocket通信的时侯还要握手跟传输数据两步,握手利用http状态码101 switch protocol从http协议转化至websocket合同,之后便跟http合同无关了。
二.握手
websocket首先由浏览器主动发起一个http请求,主要恳求头内容如下:Connection: 告知服务器当前恳求连结是升级的Upgrade: websocket Upgrade 告诉服务器这个http链接是升级的websocket链接Sec-WebSocket-Version: 13 协议版本Sec-WebSocket-Key: IYiYjdXLDgHybP4teMOnsQ== 验证key
服务器响应头如下HTTP/1.1 101 Switching Protocols 表示变换合同Upgrade: websocketConnection:Upgrade 服务器返回的告知客户端同意使用升级并使用websocket合同,用来建立HTTP升级响应Sec-WebSocket-Accept:Ev/nT3aIpWH9deAfyYMPbBwkQWo= 客户端 Sec-WebSocket-Key经过加密后的字符串算法 base64_encode(sha1(Sec-WebSocket-Key +258EAFA5-E914-47DA-95CA-C5AB0DC85B11));
三.数据幀构造跟解读
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
构造合同文本幀的算法(PHP)
其中的opcode为1代表一个文本帧
private function encode($data){
$len = strlen($data);
$encode = '';
if($len < 126){
$encode = chr(0x81) . chr($len) . $data;
}else if($len >= 126 && $len <= 65535){
$low = $len & 0x00FF;
$high = ($len & 0xFF00) >> 8;
$encode = chr(0x81) . chr(0x7F) . chr($high) . chr($low) . $data;
}else{
encode = chr(0x81) .
}
return $encode;
}
如果playload len < 126,则playloadlen 是数据的真实宽度假如playload len = 126,数据的宽度等于playload len上面2个字节对应的无符号整数就是数据的真实宽度假如playload len = 127,数据的宽度等于playload len上面8个字节对应的无符号整数就是数据的真实宽度
之前对位运算并不熟悉,这里也写下建立数据帧具体的方法php使用chr将数据转化为标准ascii所指定的单个字符宽度 < 126 FIN + RSV1 + RSV2 + RSV3 + opcode = 0x81 = 10000001, 再加上数据宽度跟数据宽度 >=126 <= 65535 FIN + RSV1 + RSV2 + RSV3 + opcode = 0x81 = 10000001 加上 Payload len = 0x7E = 126 因为ASCII范围为 0-127即1个字节,所以应当将2个字节分拆成单个字节即低位$high跟低位$low来表示 $low = $len & (11111111 = 0x00FF)这样就取得了$len第二个字节的值。 因为$len是两个字节 取第一个字节的值还要 $len & (1111111100000000 = 0xFF00) 然后往右移8个位
解析文本帧的算法
private function decode($data){
if(!$data) return array();
//第一个字节和00001111按位与运算取得的后4位数据就是opcode
$opcode = ord(substr($data, 0, 1)) & 0x0f;
//第二个字节和10000000按位与运算,保留第一位的值,然后右移7位取得的就是ismask
$ismask = (ord(substr($data, 1, 1)) & 0x80) >> 7;
//第二个字节和01000000按位与运算取得后7位的值就是playloadlen
$playloadlen = ord(substr($data, 1, 1)) & 0x7f;
$cdata = $maskkey = $decode = '';
if($playloadlen < 126){
$maskkey = substr($data, 2, 4);
$cdata = substr($data, 6);
}else if($playloadlen == 126){
$maskkey = substr($data, 4, 4);
$cdata = substr($data, 8);
}else if($playloadlen == 127){
$maskkey = substr($data, 10, 4);
$cdata = substr($data, 14);
}
if($cdata && $maskkey){
for($i = 0; $i < strlen($cdata); $i++){
$decode{$i} = $cdata{$i} ^ $maskkey[$i % 4];
}
$decode = join('', $decode);
}
return array($opcode, $ismask, $decode);
}
websocket规定客户端发送给服务端的数据应当经过网段处理,服务器端发送给客户端的数据无需网段处理,解码算法: 将playload的原始数据的每位字符角标与4取模,然后将这个原始字符与上面取模后相应位置的网段字符进行卷积运算即可data[i] = source[i] ^ maskkey[i / 4];
四.PHP服务端
之前对于socket的select方式也不是太了解,
function socket_select (array &$read, array &$write, array &$except, $tv_sec, [, int $tv_usec = 0 ])
1.新连结到来时,被的端口是活跃的,如果是新数据到来或则客户端关掉链接时,活跃的是对应的客户端socket而不是服务器上被的端口2.如果客户端发来数据没有被读走,则socket_select将要一直显示客户端是活跃状态并将其保存在read链表中3.假如客户端先关掉了,则应当自动关掉服务器上相对应的客户端socket,否则socket_select也一直显示该客户端活跃(这个道理和"有新连结到来之后没有用socket_access把它读下来,导致的端口仍然活跃"是一样的)
$read是一个引用变量,每次执行的时侯传到我们还要的socket资源,执行之后php实现websocketphp实现websocket,返回活跃的socket资源,核心伪代码如下
$socket = socketcreate();$socket_select = array($socket);while(true){$read = $socket_select; socket_select($read, $write, $except, null);foreach($read as $sock){if($sock == $socket){//新连结到来时}else{//客户端发送数据或则客户端关掉的时侯}}}
五.客户端
客户端websocket api就很简单了
// 创建一个 websocket 连接
var ws = new WebSocket("ws:XXXXX");
// websocket 创建成功事件
ws.onopen = function () {
};
// websocket 接收到消息事件
ws.onmessage = function (e) {
var msg = JSON.parse(e.data);
}
// websocket 错误事件
ws.onerror = function () {
};
//websocket 关闭事件
ws.close = function () {
};
完整代码在我的github
;utm_medium=referral
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/ruanjian/article-135202-1.html