经过三天艰苦地探索,终于初探websocket成功
环境,chrome 版本 36.0.1985.125
参考:
wiki关于websocket 握手协议 : http://zh.wikipedia.org/wiki/WebSocket#.E6.8F.A1.E6.89.8B.E5.8D.8F.E8.AE.AE
les5332295 : http://blog.csdn.net/les5332295/article/details/7014799
一:握手协议
这是chrome发给我的数据,我们只需要关心,Sec-WebSocket-Key的值
把“Sec-WebSocket-Key”加上一个魔幻字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用 SHA-1 加密,之后进行 BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端.
这是发送给浏览器的内容
当浏览器接收到正确的Sec-WebSocket-Accept之后,websocket连接就建立成功了,这时发送数据要按照websocket协议进行
二:收发数据
1.首先是client to server
示例:
"hi"
服务器接收到的数据的16进制为:
0x81: 0x81是固定的
0x82 第二个字节是字符串长度 0x02加上固定值0x80
70 b1 28 71 接下来4个字节是密码字符串,用来加密字符串,
0x18 0xd8 之后的数据是加密后的字符串 (0x18 0xd8就是加密后的h 和 i)
加密方法是这样:
密码: makr[4]浏览器随机生成
要加密的字符串: data[n] 长度n
加密后的字符串: res[n] 长度n
for(i=0;i<n;++i)
res[i]=data[i]^mark[i%4]; // ^ 异或
示例中的0x18='h'^0x70
0xd8='i'^0xb1
2.然后是server to client
示例:
"zup human"
服务器发送数据的16进制是这样的
0x81 同样0x81是固定的
0x09 第二个字节是字符串长度 "zup human"的长度是9
0x75~0x6e 接下来是没有加密的字符串
(图中 '>' 字符之后的是发送的数据)
具体协议请参考: (本人英语很差,没有领悟)
英文资料 : http://datatracker.ietf.org/doc/rfc6455/?include_text=1
三:下面我的测试代码
服务器:
用命令 php + 文件名 启动
<?php
class WebSocket{
var $master;
var $sockets = array();
var $users = array();
var $debug = true;
function __construct($address,$port){
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();
$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,20) or die("socket_listen() failed");
$this->sockets[] = $this->master;
$this->say("Server Started : ".date('Y-m-d H:i:s'));
$this->say("Listening on : ".$address." port ".$port);
$this->say("Master socket : ".$this->master."\n");
while(true){
$changed = $this->sockets;
socket_select($changed,$write=NULL,$except=NULL,NULL);
foreach($changed as $socket){
if($socket==$this->master){
$client=socket_accept($this->master);
if($client<0){$this->log("socket_accept() failed"); continue; }
else{$this->connect($client); }
}
else{
$bytes = @socket_recv($socket,$buffer,2048,0);
if($bytes==0){ $this->disconnect($socket); }
else{
$user = $this->getuserbysocket($socket);
$this->log("handshake:".$user->handshake);
if(!$user->handshake){ $this->dohandshake($user,$buffer); }
else{
$payload = $this->read($buffer,$bytes);
$this->process($user,$payload);
}
}
}
}
}
}
function process($user,$msg){
$this->send($user->socket,$msg);
}
function read($buffer,$bytes){
for($i=0;$i<$bytes;++$i){
printf("%x ",ord($buffer[$i]));
}
printf("\n");
if ($bytes<126) {
$this->log(dechex(ord($buffer[0])));
$this->log(dechex(ord($buffer[1])));
$mask="";
for ($i = 0; $i <= 3; $i++) {
$mask.=$buffer[2+$i];
}
$payload="";
$payload_start = 6;
for ($i = $payload_start; $i < $bytes; $i++) {
$payload.=$mask[($i - $payload_start) % 4]^$buffer[$i];
}
}
return $payload;
}
function send($client,$msg){
$msg = chr(0x81).chr(strlen($msg)).$msg;
for($i=0;$i<strlen($msg);++$i){
printf("%x ",ord($msg[$i]));
}
printf("\n");
$this->say("> ".$msg);
socket_write($client,$msg,strlen($msg));
//@socket_send($client,$msg,strlen($msg),0);
$this->say("! ".strlen($msg));
}
function connect($socket){
$user = new User();
$user->id = uniqid();
$user->socket = $socket;
array_push($this->users,$user);
array_push($this->sockets,$socket);
$this->log($socket." CONNECTED!");
$this->log(date("d\n/Y ")."at ".date("H:i:s T"));
}
function disconnect($socket){
$found=null;
$n=count($this->users);
for($i=0;$i<$n;$i++){
if($this->users[$i]->socket==$socket){ $found=$i; break; }
}
if(!is_null($found)){ array_splice($this->users,$found,1); }
$index=array_search($socket,$this->sockets);
socket_close($socket);
$this->log($socket." DISCONNECTED!");
if($index>=0){ array_splice($this->sockets,$index,1); }
}
function dohandshake($user,$buffer){
$this->log("\nRequesting handshake...");
$this->log($buffer);
list($resource,$upgrade,$connection,$host,$origin,$key,$version) = $this->getheaders($buffer);
$this->log("Handshaking...");
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: " . base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true)) . "\r\n".
"Sec-WebSocket-Origin: null\r\n" .
"Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" .
"\r\n";
socket_write($user->socket,$upgrade,strlen($upgrade));
$user->handshake=true;
$this->log($upgrade);
$this->log("Done handshaking...");
return true;
}
function getheaders($req){
if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; }
if(preg_match("/Upgrade: (.*)\r\n/" ,$req,$match)){ $u=$match[1]; }
if(preg_match("/Connection: (.*)\r\n/" ,$req,$match)){ $c=$match[1]; }
if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; }
if(preg_match("/Origin: (.*)\r\n/" ,$req,$match)){ $o=$match[1]; }
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/" ,$req,$match)){ $k=$match[1]; }
if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/" ,$req,$match)){ $v=$match[1]; }
return array($r,$u,$c,$h,$o,$k,$v);
}
function getuserbysocket($socket){
$found=null;
foreach($this->users as $user){
if($user->socket==$socket){ $found=$user; break; }
}
return $found;
}
function say($msg=""){ echo $msg."\n"; }
function log($msg=""){ if($this->debug){ echo "\n--->".$msg."\n"; } }
}
class User{
var $id;
var $socket;
var $handshake;
}
$master = new ChatBot("localhost",12345);
?>
客户端
用chrome打开
<html>
<head>
<title>WebSocket</title>
<style>
html,body{font:normal 0.9em arial,helvetica;}
#log {width:440px; height:200px; border:1px solid #7F9DB9; overflow:auto;}
#msg {width:330px;}
</style>
<script>
var socket;
function init(){
var host = "ws://localhost:12345/websocket/php/server.php";
try{
log("init");
socket = new WebSocket(host);
log('WebSocket - status '+socket.readyState);
socket.onopen = function(msg){ log("Welcome - status "+this.readyState); };
socket.onmessage = function(msg){ log("Received: "+msg.data); };
socket.onclose = function(msg){ log("Disconnected - status "+this.readyState); };
}
catch(ex){ log(ex); }
$("msg").focus();
}
function send(){
var txt,msg;
txt = $("msg");
msg = txt.value;
if(!msg){ alert("Message can not be empty"); return; }
txt.value="";
txt.focus();
try{ socket.send(msg); log('Sent: '+msg); } catch(ex){ log(ex); }
}
function quit(){
log("Goodbye!");
socket.close();
socket=null;
}
// Utilities
function $(id){ return document.getElementById(id); }
function log(msg){ $("log").innerHTML+="<br>"+msg; }
function onkey(event){ if(event.keyCode==13){ send(); } }
</script>
</head>
<body οnlοad="init()">
<h3>WebSocket v2.00</h3>
<div id="log"></div>
<input id="msg" type="textbox" οnkeypress="onkey(event)"/>
<button οnclick="send()">Send</button>
<button οnclick="quit()">Quit</button>
</body>
</html>
代码参考来源 https://code.google.com/p/phpwebsocket/