WebSocket

作者: 路漫漫在狂奔 | 来源:发表于2016-10-09 13:23 被阅读37次

    什么TCP/IP协议呀,什么WebSocket原理呀,到处都能搜到,我就不重复了;
    因为最近在搞跨平台的游戏,这时要即时通讯怎么办?当然WebSocket!
    so! 怎么用它发送消息呢?看代码:

    客户端

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>WebSocket</title>
    </head>
    <body>
        <input type="text" placeholder="说点啥?">
        <button onclick="sendMsg()">发送</button>
    </body>
    <script>
        var input = document.getElementsByTagName('input')[0];
        //请求服务器握手,端口和地址根据自己需要改动,没说一定是1935,只要与服务端一致就好
        var ws = new WebSocket("ws://127.0.0.1:1935");
        name = prompt('来个名字吧:');
        //握手成功触发
        ws.onopen = function(){
            console.log("握手成功");
            if(ws.readyState==1){
                ws.send(name+"加入房间");
            }
        };
        //服务器发送消息,触发
        ws.onmessage = function(e){
            //e就是服务器发送的信息
            console.log( JSON.parse(e.data) );
            //接下来爱干嘛干嘛去、
            //...code...
        };
        //出错时触发方法
        ws.onerror = function(){
            console.log("error");
        };
    
        function sendMsg(){
            var msg = input.value;
            //发送消息给服务器
            ws.send(name+' : '+msg);
            input.value = '';
        }
    </script>
    </html>
    

    没错,就是这么简单,50行不到的代码就OK!

    服务器端

    服务器语言用的PHP,用node.js会简单许多,而且PHP也不太适合拿来干这个,不过在流量不大的情况,还是没问题的;

    <?php
    error_reporting(E_ALL);
    ob_implicit_flush();
    
    $sk=new Sock('127.0.0.1',1935);
    $sk->run();
    
    class Sock{
        private $sockets;//socket数组
        private $users;
        private $master;
    
        public function __construct($address, $port){
            //开启端口,并监听
            $this->master=$this->WebSocket($address, $port);
            $this->sockets=array('s'=>$this->master);
        }
    
        public function run(){
            while(true){
                $changes=$this->sockets;
                socket_select($changes,$write=NULL,$except=NULL,NULL);
                //遍历连接
                foreach($changes as $sock){
                    if($sock==$this->master){
                        //接受一个Socket连接
                        $client=socket_accept($this->master);
                        //把该连接存到数组
                        $this->sockets[]=$client;
                        //给该连接一个识别符
                        $this->users[]=array(
                            'socket'=>$client,
                            'isShakeHand'=>false
                        );
                    }else{
                        //从连接里拿出请求信息
                        $len = socket_recv($sock,$buffer,2048,0);
                        $k = $this->search($sock);
                        if($len<7){
                            $this->close($sock);
                            continue;
                        }
                        if(!$this->users[$k]['isShakeHand']){
                            //先握手
                            $this->woshou($k,$buffer);
                        }else{
                            //握手后,处理请求数据
                            $buffer = $this->uncode($buffer);
                            //推送给所有客户端
                            $this->send($k,$buffer);
                        }
                    }
                }
            }
        }
    
        //关闭连接
        private function close($sock){
            $k=array_search($sock, $this->sockets);
            socket_close($sock);
            unset($this->sockets[$k]);
            unset($this->users[$k]);
            $this->e("连接:$k 关闭");
        }
    
        //获取发送socket的客户端
        private function search($sock){
            foreach ($this->users as $k=>$v){
                if($sock==$v['socket'])
                return $k;
            }
            return false;
        }
        //建立端口监听
        private function WebSocket($address,$port){
            $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
            socket_bind($server, $address, $port);
            socket_listen($server);
            $this->e('服务开启 : '.date('Y-m-d H:i:s'));
            $this->e('开始监听 : '.$address.' ,端口 : '.$port);
            return $server;
        }
    
        //php的websocket特殊处理,比起node.js哎,说多了都是泪
        private function woshou($k,$buffer){
            $buf  = substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);
            $key  = trim(substr($buf,0,strpos($buf,"\r\n")));
            $new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
            $new_message = "HTTP/1.1 101 Switching Protocols\r\n";
            $new_message .= "Upgrade: websocket\r\n";
            $new_message .= "Sec-WebSocket-Version: 13\r\n";
            $new_message .= "Connection: Upgrade\r\n";
            $new_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n";
            //告诉客户端握手成功
            socket_write($this->users[$k]['socket'],$new_message,strlen($new_message));
            $this->users[$k]['isShakeHand']=true;
            return true;
        }
    
        //帧数据解码,php在流泪
        private function uncode($str){
            $mask = array();
            $data = '';
            $msg = unpack('H*',$str);
            $head = substr($msg[1],0,2);
            if (hexdec($head{1}) === 8) {
                $data = false;
            }else if (hexdec($head{1}) === 1){
                $mask[] = hexdec(substr($msg[1],4,2));
                $mask[] = hexdec(substr($msg[1],6,2));
                $mask[] = hexdec(substr($msg[1],8,2));
                $mask[] = hexdec(substr($msg[1],10,2));
    
                $s = 12;
                $e = strlen($msg[1])-2;
                $n = 0;
                for ($i=$s; $i<= $e; $i+= 2) {
                    $data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2)));
                    $n++;
                }
            }
            return $data;
        }
    
        //帧数据编码(发回客户端用),php还是在流泪
        private function code($msg){
            $msg = preg_replace(array('/\r$/','/\n$/','/\r\n$/',), '', $msg);
            $frame = array();
            $frame[0] = '81';
            $len = strlen($msg);
            $frame[1] = $len<16?'0'.dechex($len):dechex($len);
            $frame[2] = $this->ord_hex($msg);
            $data = implode('',$frame);
            return pack("H*", $data);
        }
        private function ord_hex($data)  {
            $msg = '';
            $l = strlen($data);
            for ($i= 0; $i<$l; $i++) {
                $msg .= dechex(ord($data{$i}));
            }
            return $msg;
        }
    
        //编码后发回到客户端
        private function send($k,$msg){
            $msg = json_encode($msg);
            $msg = $this->code($msg);
            $this->e($msg);
            foreach($this->users as $v){
                //发送到客户端
                socket_write($v['socket'],$msg,strlen($msg));
            }
        }
    
        //记录到log
        private function e($str){
            $path=dirname(__FILE__).'/log.txt';
            $str=$str."\n";
            error_log($str,3,$path);
        }
    }
    ?>
    

    测试

    • 用命令行运行php


      Paste_Image.png
    • 开始监听端口


      Paste_Image.png
    • 运行网页,F12,一切正常


      Paste_Image.png
    • 第二个页面,通讯成功


      Paste_Image.png

    相关文章

      网友评论

          本文标题:WebSocket

          本文链接:https://www.haomeiwen.com/subject/gprkettx.html