admin管理员组文章数量:1026989
websocket
下面先用一个图例来演示clinet和server之间建立websocket连接时握手部分,这个部分在nodejs中可以十分轻松的完成,因为node提供的net模块已经对socket套接字做了封装处理,开发者使用的时候只需要考虑数据的交互而不用处理连接的建立。而php没有,从socket的连接、建立、绑定、监听等,这些都需要自己去操作,如下1和2实际上就是一个HTTP的请求和响应,只不过我们在处理的过程中拿到的是没有经过解析的字符串。如: GET /chat HTTP /1.1 Host: server.example.com Origin:
一、php中处理websocket
webSocket连接是由客户端主动发起的,所以一切要从客户端出发。第一步是要解析拿到客户端发过来的Sec-WebSocket-Key字符串。 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: Http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
client请求的格式(如上),首先php建立一个socket连接,监听端口的信息。
1. socket连接的建立
关于socket套接字的建立,下面一张连接建立的过程:
// 建立一个socket套接字 $master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1); socket_bind($master, $address, $port); socket_listen($master);
相比node,这个地方处理比较麻烦,上面几行代码并未建立连接,只不过这些代码是建立一个socket套接字必须要写的东西。由于处理过程稍微复杂,所以我把各种处理写进一个类中,方便管理和调用。 <?php // demo.php Class WS { var $master; // 连接server的client var $sockets = array(); // 不同状态的socket管理 var $handshake = false; // 判断是否握手 function __construct($address, $port) { // 建立一个socket套接字 $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); socket_bind($this->master, $address, $port) or die("socket_bind() failed"); socket_listen($this->master, 2) or die("socket_listen() failed"); $this->sockets[] = $this->master; } // debug echo("Master socket :" . $this->master. "\n"); while(true) { // 自动选择来消息的socket 如果是握手 自动选择主机 $write = NULL; $except = NULL; socket_select($this->sockets, $write, $except, NULL); foreach ($this->sockets as $socket) { // 连接主机的client if ($socket == $this->master) { $client = socket_accept($this->master); if ($client < 0) { // debug echo "socket_accept() failed"; continue; } else { // connect($client); array_push($this->sockets, $client); echo "connect client\n"; } } else { $bytes = @socket_recv($socket, $buffer, 2048, 0); if ($bytes == 0) return; if (!$this->handshake) { // 如果没有握手,先握手回应 // doHandShake($socket, $buffer); echo "shakeHands \n"; } else { // 如果已经握手,直接接受数据,并处理 $buffer = decode($buffer); // process($socket, $buffer); echo "send file\n"; } } } } } 上面这段代码如果要测试,可以在cmd命令行中键入php /path/to/demo.php,还需要新建一个实例。 $ws = new WS('localhost', 4000); 客户端代码可以稍微简单点: 创建一个websocket.html <html>
<head>
<meta charset="utf-8" />
<title>test</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://localhost:4000");
ws.onopen = function(){
console.log("握手成功");
};
ws.onerror = function(){
console.log("error");
};
</script>
</body>
</html>
window doc环境下执行php demo.php
浏览器访问websocket.html页面时,doc环境下监听
通过上面代码能够看到整个流程,首先是建立连接,node中这一步已经封装到了net和http模块,然后判断是否握手,如果没有的话,就shakeHands。这里握手直接echo一个单词,表示进行这个东西。
2、计算Sec-WebSocket-Accpet值 function calcKey($key){
//基于websocket version 13
$accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
return $accept;
}
3、将SHA-1加密后的字符串再进行一次base64加密。如果加密算法错误,客户端在进行校验的时候会直接报错:
4、应答Sec-WebSocket-Accept function dohandshake($socket, $req) { // 获取加密key $acceptKey = $this->encry($req); $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" . "\r\n"; // 写入socket socket_write(socket, $upgrade.chr(0), strlen($upgrade.chr(0))); // 标记握手已经成功,下次接受数据采用数据帧格式 $this->handshake = true; } 这里要注意,每一个请求和相应的格式,最后有一个空行,也就是\r\n。
当客户端成功校验key后,会触发onopen函数:
5.数据帧处理
// 解析数据帧
function decode($buffer) {$len = $masks = $data = $decoded = null;$len = ord($buffer[1]) & 127;if ($len === 126) {$masks = substr($buffer, 4, 4);$data = substr($buffer, 8);} else if ($len === 127) {$masks = substr($buffer, 10, 4);$data = substr($buffer, 14);} else {$masks = substr($buffer, 2, 4);$data = substr($buffer, 6);}for ($index = 0; $index < strlen($data); $index++) {$decoded .= $data[$index] ^ $masks[$index % 4];}return $decoded;
}
// 返回帧信息处理
function frame($s) {$a = str_split($s, 125);if (count($a) == 1) {return "\x81" . chr(strlen($a[0])) . $a[0];}$ns = "";foreach ($a as $o) {$ns .= "\x81" . chr(strlen($o)) . $o;}return $ns;
}// 返回数据
function send($client, $msg){$msg = $this->frame($msg);socket_write($client, $msg, strlen($msg));
}
客户端代码:
var ws = new WebSocket("ws://localhost:4000");
ws.onopen = function(){console.log("握手成功");
};
ws.onmessage = function(e){console.log("message:" + e.data);
};
ws.onerror = function(){console.log("error");
};
ws.send("李靖");
在连通之后发送数据,服务器原样返回:
二、注意问题
1. websocket 版本问题
客户端在握手时的请求中有Sec-WebSocket-Version: 13
,这样的版本标识,这个是一个升级版本,现在的浏览器都是使用的这个版本。而以前的版本在数据加密的部分更加麻烦,它会发送两个key:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: : chat, superchat
Sec-WebSocket-Key1: xxxx
Sec-WebSocket-Key2: xxxx
如果是这种版本(比较老,已经没在使用了),需要通过下面的方式获取
function encry($key1,$key2,$l8b){//Get the numberspreg_match_all('/([\d]+)/', $key1, $key1_num);preg_match_all('/([\d]+)/', $key2, $key2_num);$key1_num = implode($key1_num[0]);$key2_num = implode($key2_num[0]);//Count spacespreg_match_all('/([ ]+)/', $key1, $key1_spc);preg_match_all('/([ ]+)/', $key2, $key2_spc);if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }//Some math$key1_sec = pack("N",$key1_num / $key1_spc);$key2_sec = pack("N",$key2_num / $key2_spc);return md5($key1_sec.$key2_sec.$l8b,1);
}
nodeJs的websocket操作方式:
//服务器程序var crypto = require('crypto');
var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
require('net').createServer(function(o){var key;o.on('data',function(e){if(!key){//握手key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];key = crypto.createHash('sha1').update(key + WS).digest('base64');o.write('HTTP/1.1 101 Switching Protocols\r\n');o.write('Upgrade: websocket\r\n');o.write('Connection: Upgrade\r\n');o.write('Sec-WebSocket-Accept: ' + key + '\r\n');o.write('\r\n');}else{console.log(e);};});
}).listen(8000);
websocket
下面先用一个图例来演示clinet和server之间建立websocket连接时握手部分,这个部分在nodejs中可以十分轻松的完成,因为node提供的net模块已经对socket套接字做了封装处理,开发者使用的时候只需要考虑数据的交互而不用处理连接的建立。而php没有,从socket的连接、建立、绑定、监听等,这些都需要自己去操作,如下1和2实际上就是一个HTTP的请求和响应,只不过我们在处理的过程中拿到的是没有经过解析的字符串。如: GET /chat HTTP /1.1 Host: server.example.com Origin:
一、php中处理websocket
webSocket连接是由客户端主动发起的,所以一切要从客户端出发。第一步是要解析拿到客户端发过来的Sec-WebSocket-Key字符串。 GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: Http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
client请求的格式(如上),首先php建立一个socket连接,监听端口的信息。
1. socket连接的建立
关于socket套接字的建立,下面一张连接建立的过程:
// 建立一个socket套接字 $master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1); socket_bind($master, $address, $port); socket_listen($master);
相比node,这个地方处理比较麻烦,上面几行代码并未建立连接,只不过这些代码是建立一个socket套接字必须要写的东西。由于处理过程稍微复杂,所以我把各种处理写进一个类中,方便管理和调用。 <?php // demo.php Class WS { var $master; // 连接server的client var $sockets = array(); // 不同状态的socket管理 var $handshake = false; // 判断是否握手 function __construct($address, $port) { // 建立一个socket套接字 $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); socket_bind($this->master, $address, $port) or die("socket_bind() failed"); socket_listen($this->master, 2) or die("socket_listen() failed"); $this->sockets[] = $this->master; } // debug echo("Master socket :" . $this->master. "\n"); while(true) { // 自动选择来消息的socket 如果是握手 自动选择主机 $write = NULL; $except = NULL; socket_select($this->sockets, $write, $except, NULL); foreach ($this->sockets as $socket) { // 连接主机的client if ($socket == $this->master) { $client = socket_accept($this->master); if ($client < 0) { // debug echo "socket_accept() failed"; continue; } else { // connect($client); array_push($this->sockets, $client); echo "connect client\n"; } } else { $bytes = @socket_recv($socket, $buffer, 2048, 0); if ($bytes == 0) return; if (!$this->handshake) { // 如果没有握手,先握手回应 // doHandShake($socket, $buffer); echo "shakeHands \n"; } else { // 如果已经握手,直接接受数据,并处理 $buffer = decode($buffer); // process($socket, $buffer); echo "send file\n"; } } } } } 上面这段代码如果要测试,可以在cmd命令行中键入php /path/to/demo.php,还需要新建一个实例。 $ws = new WS('localhost', 4000); 客户端代码可以稍微简单点: 创建一个websocket.html <html>
<head>
<meta charset="utf-8" />
<title>test</title>
</head>
<body>
<script>
var ws = new WebSocket("ws://localhost:4000");
ws.onopen = function(){
console.log("握手成功");
};
ws.onerror = function(){
console.log("error");
};
</script>
</body>
</html>
window doc环境下执行php demo.php
浏览器访问websocket.html页面时,doc环境下监听
通过上面代码能够看到整个流程,首先是建立连接,node中这一步已经封装到了net和http模块,然后判断是否握手,如果没有的话,就shakeHands。这里握手直接echo一个单词,表示进行这个东西。
2、计算Sec-WebSocket-Accpet值 function calcKey($key){
//基于websocket version 13
$accept = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
return $accept;
}
3、将SHA-1加密后的字符串再进行一次base64加密。如果加密算法错误,客户端在进行校验的时候会直接报错:
4、应答Sec-WebSocket-Accept function dohandshake($socket, $req) { // 获取加密key $acceptKey = $this->encry($req); $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" . "\r\n"; // 写入socket socket_write(socket, $upgrade.chr(0), strlen($upgrade.chr(0))); // 标记握手已经成功,下次接受数据采用数据帧格式 $this->handshake = true; } 这里要注意,每一个请求和相应的格式,最后有一个空行,也就是\r\n。
当客户端成功校验key后,会触发onopen函数:
5.数据帧处理
// 解析数据帧
function decode($buffer) {$len = $masks = $data = $decoded = null;$len = ord($buffer[1]) & 127;if ($len === 126) {$masks = substr($buffer, 4, 4);$data = substr($buffer, 8);} else if ($len === 127) {$masks = substr($buffer, 10, 4);$data = substr($buffer, 14);} else {$masks = substr($buffer, 2, 4);$data = substr($buffer, 6);}for ($index = 0; $index < strlen($data); $index++) {$decoded .= $data[$index] ^ $masks[$index % 4];}return $decoded;
}
// 返回帧信息处理
function frame($s) {$a = str_split($s, 125);if (count($a) == 1) {return "\x81" . chr(strlen($a[0])) . $a[0];}$ns = "";foreach ($a as $o) {$ns .= "\x81" . chr(strlen($o)) . $o;}return $ns;
}// 返回数据
function send($client, $msg){$msg = $this->frame($msg);socket_write($client, $msg, strlen($msg));
}
客户端代码:
var ws = new WebSocket("ws://localhost:4000");
ws.onopen = function(){console.log("握手成功");
};
ws.onmessage = function(e){console.log("message:" + e.data);
};
ws.onerror = function(){console.log("error");
};
ws.send("李靖");
在连通之后发送数据,服务器原样返回:
二、注意问题
1. websocket 版本问题
客户端在握手时的请求中有Sec-WebSocket-Version: 13
,这样的版本标识,这个是一个升级版本,现在的浏览器都是使用的这个版本。而以前的版本在数据加密的部分更加麻烦,它会发送两个key:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: : chat, superchat
Sec-WebSocket-Key1: xxxx
Sec-WebSocket-Key2: xxxx
如果是这种版本(比较老,已经没在使用了),需要通过下面的方式获取
function encry($key1,$key2,$l8b){//Get the numberspreg_match_all('/([\d]+)/', $key1, $key1_num);preg_match_all('/([\d]+)/', $key2, $key2_num);$key1_num = implode($key1_num[0]);$key2_num = implode($key2_num[0]);//Count spacespreg_match_all('/([ ]+)/', $key1, $key1_spc);preg_match_all('/([ ]+)/', $key2, $key2_spc);if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; }//Some math$key1_sec = pack("N",$key1_num / $key1_spc);$key2_sec = pack("N",$key2_num / $key2_spc);return md5($key1_sec.$key2_sec.$l8b,1);
}
nodeJs的websocket操作方式:
//服务器程序var crypto = require('crypto');
var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
require('net').createServer(function(o){var key;o.on('data',function(e){if(!key){//握手key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];key = crypto.createHash('sha1').update(key + WS).digest('base64');o.write('HTTP/1.1 101 Switching Protocols\r\n');o.write('Upgrade: websocket\r\n');o.write('Connection: Upgrade\r\n');o.write('Sec-WebSocket-Accept: ' + key + '\r\n');o.write('\r\n');}else{console.log(e);};});
}).listen(8000);
本文标签: websocket
版权声明:本文标题:websocket 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/IT/1687036662a57301.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论