Socket学习 - 撕开websocket神秘的外衣
2017-01-09 16:29
211 查看
websocket,我们可以理解嵌入在浏览器中的socket客户端
那么问题来了
1、它有专门的协议?
2、是否和HTTP协议一样,和服务端交互
3、服务端代码怎么写?
1.客户端websocket_client.html:
2.服务端websocket_server.php
3.把服务端运行起来,然后浏览器 客户端访问:
http://localhost:63343/socket/websocket_client.html
发现报错了,报错并不说明websocket就没有连接,我们查看服务端控制台会打印如下内容:
说明,只要客户端
4.报错的原因是:服务端没有按照websocket的协议回复内容
1、客户端会发送一个字段:
2、服务端需要截取此值,把该值累加字符串
3、拼凑对应的响应协议内容
拿来主义,直接使用现成的代码
在服务端回复数据的时候,用此函数处理
然后客户端浏览器请求:
我们客户端监听到了socket服务端的消息
客户端点击按钮,发送一条消息给服务端。
服务端
重新运行服务端,任何观察服务端控制的打印的信息。
刷新浏览器,服务端控制台打印:
点击“发送消息”,服务端控制台打印:
这样就完成了一个简单的websocket案例
那么问题来了
1、它有专门的协议?
2、是否和HTTP协议一样,和服务端交互
3、服务端代码怎么写?
1.客户端websocket_client.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> //创建一个socket实例 var socket = new WebSocket('ws://127.0.0.1:9090'); //打开socket socket.onopen = function (e) { //发送一个初始化消息 socket.send('init msg'); }; socket.onmessage = function(e){ console.log('收到消息',e); }; //监听socket的关闭 socket.onclose = function(e){ console.log('关闭',e); }; </script> </body> </html>
2.服务端websocket_server.php
<?php $server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机 socket_bind($server,'127.0.0.1',9090); // 绑定电话机 socket_listen($server,5); // 开机 while(true){ $client = socket_accept($server); //接收客户端连接 $buf = socket_read($client,8024); //一次读取数据的长度 echo $buf; //回复 socket_write($client,'404'); //关掉客户端 socket_close($client); } //关机 socket_close($server);
3.把服务端运行起来,然后浏览器 客户端访问:
http://localhost:63343/socket/websocket_client.html
发现报错了,报错并不说明websocket就没有连接,我们查看服务端控制台会打印如下内容:
说明,只要客户端
var socket = new WebSocket('ws://127.0.0.1:9090');这一步,服务端就会收到。
4.报错的原因是:服务端没有按照websocket的协议回复内容
握手方法
客户端指定服务端发送一个握手请求,如果服务端返回合法的HTTP头,则握手成功。1、客户端会发送一个字段:
Sec-WebSocket-Key: xxxxxooooooo
2、服务端需要截取此值,把该值累加字符串
258EAFA5-E914-47DA-95CA-C5AB0DC85B11,然后进行sha1,最后再base64_encode
3、拼凑对应的响应协议内容
<?php $server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机 socket_bind($server,'127.0.0.1',9090); // 绑定电话机 socket_listen($server,5); // 开机 while(true){ $client = socket_accept($server); //接收客户端连接 $buf = socket_read($client,8024); //一次读取数据的长度 //echo $buf; if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$matches)){ $key = base64_encode(sha1($matches[1].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true)); $res = 'HTTP/1.1 101 Switching Protocol'.PHP_EOL .'Upgrade: Websocket'.PHP_EOL .'Connection: Upgrade'.PHP_EOL .'WebSoket-Location: ws://127.0.0.1:9090'.PHP_EOL .'Sec-WebSocket-Accept:'.$key.PHP_EOL.PHP_EOL; //socket回复 socket_write($client,$res,strlen($res)); socket_write($client,'hello websocket'); } } //关机 socket_close($server);
握手成功后就比较复杂了
相关的数据发送是需要有一定的格式的,这里边涉及到一些二进制的计算,服务端要对数据进行处理,我们这里就不深入了。拿来主义,直接使用现成的代码
/** * @param $msg要处理的数据内容 */ function buildMsg($msg){ $frame = []; $frame[0] = '81'; $len = strlen($msg); if($len < 126){ $frame[1] = $len < 16 ? '0'.dechex($len) : dechex($len); }elseif($len < 65025){ $s = dechex($len); $frame[1] = '7e'.str_repeat('0',4-strlen($s)).$s; }else{ $s = dechex($len); $frame[1] = '7f'.str_repeat('0',16-strlen($s)).$s; } $data = ''; $l = strlen($msg); for($i=0;$i<$l;$i++){ $data .= dechex(ord($msg{$i})); } $frame[2] = $data; $data = implode('',$frame); return pack('H*',$data); }
在服务端回复数据的时候,用此函数处理
socket_write($client,buildMsg("hello websocket")); //注意此处要双引号
然后客户端浏览器请求:
我们客户端监听到了socket服务端的消息
socket.onmessage = function(e){ console.log('收到消息',e); };
我们继续优化,完成消息发送
客户端:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="sendMsg()">发送消息</button>
<script>
//创建一个socket实例
var socket = new WebSocket('ws://127.0.0.1:9090');
//打开socket
socket.onopen = function (e) {
//发送一个初始化消息
socket.send('init msg');
};
socket.onmessage = function(e){ console.log('收到消息',e); };
//监听socket的关闭
socket.onclose = function(e){
console.log('关闭',e);
};
//自定义按钮事件
function sendMsg(){
socket.send('hello server');
}
</script>
</body>
</html>
客户端点击按钮,发送一条消息给服务端。
服务端
websocket_server.php需要做相应处理:
<?php $server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); // 购买电话机 socket_bind($server,'127.0.0.1',9090); // 绑定电话机 socket_listen($server,5); // 开机 //定义一个数组 $allSockets = [$server]; while(true){ $copySockets = $allSockets; if(socket_select($copySockets,$write,$except,0) === false){ exit('error'); } if(in_array($server,$copySockets)){ $client = socket_accept($server); //接收客户端连接 $buf = socket_read($client,8024); //一次读取数据的长度 //echo $buf; if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$matches)){ $key = base64_encode(sha1($matches[1].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true)); $res = 'HTTP/1.1 101 Switching Protocol'.PHP_EOL .'Upgrade: Websocket'.PHP_EOL .'Connection: Upgrade'.PHP_EOL .'WebSoket-Location: ws://127.0.0.1:9090'.PHP_EOL .'Sec-WebSocket-Accept:'.$key.PHP_EOL.PHP_EOL; //socket回复 socket_write($client,$res,strlen($res)); //握手成功 socket_write($client,buildMsg("hello websocket")); //注意此处要双引号 //把webSocket客户端的socket保存起来 $allSockets[] = $client; } //把服务端的socket移除 $k = array_search($server,$copySockets); unset($copySockets[$k]); } foreach($copySockets as $s){ $buf = socket_read($s,8024); if(strlen($buf) < 9){ //意味着客户端主动关闭了链接 $k = array_search($s,$allSockets); unset($allSockets[$k]); //数组中删除该socket //服务端也要关掉 socket_close($s); continue; } echo getMsg($buf); //获取客户端消息并转码 echo PHP_EOL; } } //关机 socket_close($server); ///////////////功能函数////////////////////////// /** * 编码发送给客户端的数据 * @param $msg要处理的数据内容 */ function buildMsg($msg){ $frame = []; $frame[0] = '81'; $len = strlen($msg); if($len < 126){ $frame[1] = $len < 16 ? '0'.dechex($len) : dechex($len); }elseif($len < 65025){ $s = dechex($len); $frame[1] = '7e'.str_repeat('0',4-strlen($s)).$s; }else{ $s = dechex($len); $frame[1] = '7f'.str_repeat('0',16-strlen($s)).$s; } $data = ''; $l = strlen($msg); for($i=0;$i<$l;$i++){ $data .= dechex(ord($msg{$i})); } $frame[2] = $data; $data = implode('',$frame); return pack('H*',$data); } /** * 解析客户端发送过来的数据 * @param $buffer */ function getMsg($buffer){ $res = ''; $len = ord($buffer)&127; if($len === 126){ $masks = substr($buffer,4,4); $data = substr($buffer,8); }elseif($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++){ $res .= $data[$index]^$masks[$index % 4]; } return $res; }
重新运行服务端,任何观察服务端控制的打印的信息。
刷新浏览器,服务端控制台打印:
init msg
点击“发送消息”,服务端控制台打印:
hello server #这个是客户端发送的
这样就完成了一个简单的websocket案例
相关文章推荐
- Socket学习 - 撕开远程调用的逼格外衣(下)超简易仿制一个RPC客户端
- Socket学习 - 撕开远程调用的逼格外衣(上)
- 即时通讯之Socket.IO的学习,及使用nodejs 搭建websocket 聊天室
- SuperSocket框架学习笔记2-构建SuperWebSocket服务器程序
- websocket 学习SocketRocket开源库(三)
- SuperSocket框架学习笔记3-构建Unity3D__WebSocket4Net客户端程序
- WebSocket 学习(五)--用socketIO实现聊天室
- TcpSocket编程与Event编写学习的好例子
- Linux Socket学习(六)
- Linux Socket学习(一)
- Socket网络编程学习笔记(2):面向连接的Socket
- Linux Socket学习(十二)
- socket编程学习笔记, Unix Socket和Windows Socket
- Linux Socket 学习(九)
- Linux Socket学习二
- Socket网络编程学习笔记(2)
- Socket网络编程学习笔记(1):常用方法介绍
- C#2.0学习9--Socket编程与线程基础
- Socket网络编程学习笔记(5):发送和接收实体类数据
- 高速学习socket编程的10个步骤