WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。是一种网络通信协议。RFC6455 定义了它的通信标准。
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息
websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便c-s之间的通信
官方文档:
https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
http://www.rfcreader.com/#rfc6455
为什么需要websocket
HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。
解决:
1 ajax 轮询 浪费资源
2 websocket 建立一次连接 一直保持连接状态 效率高。
websocket特点
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
websocket应用场景
web聊天室
股票
图文直播
即时消息推送
….
websocket步骤
1 客户端连接服务端
2 握手
3 数据传输
WebSocket类的使用
构造方法:
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
const socket = new WebSocket(‘ws://localhost:8080’);
属性:
readyState属性返回实例对象的当前状态,共有四种。
CONNECTING:值为0,表示正在连接。
OPEN:值为1,表示连接成功,可以通信了。
CLOSING:值为2,表示连接正在关闭。
CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
实例对象的onopen属性,用于指定连接成功后的回调函数。
实例对象的onclose属性,用于指定连接关闭后的回调函数。
实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。
实例对象的onerror属性,用于指定报错时的回调函数。
方法:
实例对象的send()方法用于向服务器发送数据。
实例对象的close()方法关闭websocket连接
websocket 握手
基于flash的握手协议
基于md5加密方式的握手协议
基于sha1加密方式的握手协议
1 获取客户端上报的 Sec-WebSocket-Key
2 拼接 key+
258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID)
3 字符串做SHA-1 hash计算 ,然后再把得到的结果通过base64编码,最后再返回给客户端
PHP web实现聊天室
web页面 index.html
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/> <style type="text/css"> body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;} #box,.but-box{width:50%; margin:5px auto;border-radius:5px} #box{border:1px #ccc solid;height:400px;width:700px;margin-top:50px;overflow-y:auto; overflow-x:hidden; position:relative;} #user-box{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;} #msg-box{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;} button{float:right; width:80px; height:35px; font-size:18px;} input{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;} .but-box p{margin-right:160px;} </style> <script src="https://cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script> </head> <body> <h3 style="margin-left:600px">这是个web聊天室 </h3> <div id="box"> <div id="msg-box"></div> <div id="user-box"></div> </div> <div class="but-box"> <button id="send">发送</button> <p><textarea cols="60" style="resize:none" id="content"> </textarea></p> </div> </body> </html> <script> var name = prompt('请输入用户名:'); socket = new WebSocket('ws://192.168.113.136:8888'); console.log(socket) socket.onopen = function(){ console.log('connected success'); socket.send('login==='+name); } socket.onmessage = function(e){ data = JSON.parse(e.data); console.log(data); if(data.type=='login'){ $('#user-box').append('<li style="color:gray">'+data.msg+'</li>'); } if(data.type=='user'){ $('#msg-box').html(''); for(i=0;i<data.name.length;i++){ $('#msg-box').append('<li style="color:gray">'+data.name[i]+'</li>'); } } if(data.type=='con'){ $('#user-box').append('<li><span style="color:blue">'+data.time+'</span><span style="color:red">'+data.name+'</span><span style="color:blue">'+data.content+'</span></li>'); } } document.onkeydown = function(e){ if(e.keyCode==13){ send(); } } $('#send').click(function(){ send(); }) function send(){ content = $('#content').val(); $('#content').val(''); if(content==''){ return false; } socket.send('con==='+content); } </script>
服务端 server.php
<?php class Ws{ public $socket = null; public $sockts = []; public $write = null; public $except = null; public $user= []; public function __construct($ip,$port){ $this->socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP); socket_set_option($this->socket,SOL_SOCKET,SO_REUSEADDR,true); socket_bind($this->socket,$ip,$port); socket_listen($this->socket); $this->sockts[] = $this->socket; while(true){ $tmp_sockets = $this->sockts; socket_select($tmp_sockets,$write,$except,null); foreach($tmp_sockets as $sock){ if($sock==$this->socket){ $conSock = socket_accept($this->socket); $this->sockts[] = $conSock; $this->user[] = ['socket'=>$conSock,'handshake'=>false]; }else{ $request = socket_read($sock,1024); $k = $this->getUserIndex($sock); if(strlen($request)==8){ $this->close($k); continue; } if(!$this->user[$k]['handshake']){ $response = $this->handleShake($request); socket_write($sock,$response,strlen($response)); $this->user[$k]['handshake'] = true; }else{ $msg = $this->decode($request); $this->send($msg,$k); } } } } } private function close($k){ socket_close($this->user[$k]['socket']); unset($this->user[$k]); $this->sockts = null; $this->sockts[] = $this->socket; foreach($this->user as $v){ $this->sockts[] = $v['socket']; } } private function getUserName(){ foreach($this->user as $v){ $name[] = $v['name']; } return $name; } private function send($msg,$k){ $arr = explode('===',$msg); if($arr[0]=='login'){ $this->user[$k]['name'] = $arr[1]; $res['msg'] = $arr[1].':login success'; $res['type'] = 'login'; $names['name'] = $this->getUserName(); $names['type'] = 'user'; $names = $this->encode(json_encode($names)); foreach($this->user as $v){ socket_write($v['socket'],$names,strlen($names)); } } if($arr[0]=='con'){ $res['content'] = $arr[1]; $res['name'] = $this->user[$k]['name']; $res['time'] = date('Y-m-d H:i:s',time()); $res['type'] ='con'; } $res = $this->encode(json_encode($res)); foreach($this->user as $v){ socket_write($v['socket'],$res,strlen($res)); } } private function getUserIndex($sock){ foreach($this->user as $k=>$v){ if($v['socket']==$sock){ return $k; } } } private function encode($msg) { $frame = []; $frame[0] = '81'; $len = strlen($msg); if ($len < 126) { $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len); } else if ($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); } private function handleShake($request){ preg_match("/Sec-WebSocket-Key: (.*)rn/",$request,$match); $key = $match[1]; $new_key = base64_encode(sha1($key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true)); $response = "HTTP/1.1 101 Switching Protocolsrn"; $response .= "Upgrade: websocketrn"; $response .= "Connection: Upgradern"; $response .= "Sec-WebSocket-Accept: $new_keyrn"; $response .= "Sec-WebSocket-Protocol: chatrnrn"; return $response; } private function decode($buffer) { $decoded = ''; $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; } } new Ws(0,8888);
先运行服务端
php server.php
然后浏览器访问 index.html
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。