2019-02-23 10:24:30  468259 12

php之websocket介绍使用以及实战网络聊天室

 标签:   

websocket介绍


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  建立一次连接  一直保持连接状态  效率高。

QQ截图20190223095434.jpg


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编码,最后再返回给客户端
客户端发起连接Handshake请求信息


客户端请求头:


GET / HTTP/1.1
Host: 192.168.113.132:9999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://192.168.113.132:8080
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: W4DP8Q3mdbzAdGiCmEdyzQ==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket


服务端响应:


HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Version: 13
Connection: Upgrade
Sec-WebSocket-Accept:key
Sec-WebSocket-Protocol: chat

Upgrade:WebSocket
表示这是一个特殊的 HTTP 请求,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。
Sec-WebSocket-Key
是一段浏览器base64加密的密钥,server端收到后需要提取Sec-WebSocket-Key 信息,然后加密。
Sec-WebSocket-Accept
服务器端在接收到的Sec-WebSocket-Key密钥后追加一段神奇字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,并将结果进行sha-1哈希,然后再进行base64加密返回给客户端(就是Sec-WebSocket-Key)。
Sec-WebSocket-Protocol
表示客户端请求提供的可供选择的子协议,及服务器端选中的支持的子协议,“Origin”服务器端用于区分未授权的websocket浏览器
Sec-WebSocket-Version: 13
客户端在握手时的请求中携带,这样的版本标识,表示这个是一个升级版本,现在的浏览器都是使用的这个版本。

HTTP/1.1 101 Switching Protocols
101为服务器返回的状态码,所有非101的状态码都表示handshake并未完成。


实战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: (.*)\r\n/",$request,$match);
        $key = $match[1];
        $new_key = base64_encode(sha1($key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
    
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response .= "Upgrade: websocket\r\n";
        $response .= "Connection: Upgrade\r\n";
        $response .= "Sec-WebSocket-Accept: $new_key\r\n";
        $response .= "Sec-WebSocket-Protocol: chat\r\n\r\n";
    
        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

QQ截图20190124194532.jpg

12 条留言

  1. 匿名用户

    for whom is viagra contraindicated viagra prank done on boyfriend <a href=" https://pharm-usa-official.com/# #">viagra erection </a> girl in viagra commercial blue dress sister help i took viagra and it wont go down viagra falls viagra erection before and after <a href=https://pharm-usa-official.com/#>viagra online </a> viagra treatment for the heart problems rush limbaugh viagra http://100freechip.com/no-deposit-casinos/comment-page-1/?unapproved=573235&moderation-hash=fe5d3ea2d9159db3805094574f4558a6#comment-573235 http://100freechip.com/no-deposit-casinos/comment-page-1/?unapproved=567486&moderation-hash=4f33fc0fcef1870be3776b244975a696#comment-567486 https://www.livingogroup.com/2020/03/12/banyan-tree-gate-goldmark-city-vietnam/?unapproved=8149&moderation-hash=765ed50f91c36c793a128bc0e96798cb#comment-8149 https://www.malawivoice.com/2020/03/10/bt-city-presbytery-youth-launches-new-theme/?unapproved=12687&moderation-hash=822acdaabe461b262cdb654550b7c95d#comment-12687 https://situationistapp.com/jackpot-party-casino-free-coins-hack-cheats/?unapproved=249&moderation-hash=5cf7b171545ca8b8623abb334f0e1938#comment-249

  2. 匿名用户

    buy viagra viagra pills <a href="http://svoi-site.ru/ #">generic viagra </a> viagra online cheap viagra viagra pills viagra generic <a href=http://lalisa.ru>buy viagra </a> generic viagra viagra 100mg

给我留言

评论内容