zoukankan      html  css  js  c++  java
  • WebSocket 网页聊天室

    先给大家开一个原始的websocket的连接使用范例

    <?php
    /*
    * recv是从套接口接收数据,也就是拿过来,但是不知道是什么
    * read是读取拿过来的数据,就是要知道recv过来的是什么
    * write是向套接口写数据,但是只是写,并没有发送出去
    * send是write之后,将数据传输到套接口,以便其他人recv之后read
    */
    
    //设置一些基本的变量
    $host="192.168.1.68";  //链接ip
    $port='1423';  //端口号
    //设置超时时间
    set_time_limit(0);
    //创建一个Socket
    $socket=socket_create(AF_INET,SOCK_STREAM,0) or die("Could not create socket
    ");//绑定Socket到端口
    $result=socket_bind($socket,$host,$port) or die("Could not bind to socket
    ");//开始监听链接
    $result=socket_listen($socket,3) or die("Could not setup socket listener
    ");//accept in coming connections
    //另一个Socket来处理通信
    $spawn=socket_accept($socket) or die("Could not accept in coming connection
    ");//获得客户端的输入
    $input=socket_read($spawn,1024) or die("Could not read input
    ");//清空输入字符串
    $input=trim($input);//处理客户端输入并返回结果
    $output=strrev($input)."
    ";
    socket_write($spawn,$output,strlen($output)) or die("Could not write out put
    ");//关闭
    socket_close($spawn);
    socket_close($socket);
    /*
     * PHP服务器端的代码
     * 实现webSocket的实现,先得是客户端发起请求
     * 在服务器端这边,创建socket操作,连接socket
     * 接下来进行死循环,对所有的连接进行监听
     * 有消息发过来时,进行推送,
     *   1、对发送过来的数据要先进行解密,
     *   2、对推送的消息要进行加密
     * 这样做是为了推送的消息的安全性
     * */
    ob_implicit_flush();  //将打开或关闭绝对(隐式)刷送。绝对(隐式)刷送将导致在每次输出调用后有一次刷送操作
    
    //地址与接口,即创建socket时需要服务器的IP和端口
    $sk = new WebSocket('127.0.0.1',1208);
    
    //对创建的socket循环进行监听,处理数据
    $sk->Run();
    
    class WebSocket{
    
        public $sockets; //socket的连接池,即client连接进来的socket标志
        public $users;  //所有client连接进来的信息,包括socket、client名字等
        public $master;  //socket的resource,即前期初始化socket时返回的socket资源
        /*
        * 构造函数
        * 实例化的时候,自动运行
        * */
        public function __construct($address, $port){
            //创建socket并把保存socket资源在$this->master
            $this->master = $this->WebSocket($address, $port);  //$socket
    
            //创建socket连接池  连接的用户
            $this->sockets = array($this->master);  //$clients
    
        }
        /*
        * 传相应的IP与端口进行创建socket操作
        * 连接socket  创建tcp socket
        * */
        function WebSocket($address,$port){
            $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的数据包
            socket_bind($server, $address, $port);
            socket_listen($server);  //监听端口
            return $server;
        }
    
        //对创建的socket循环进行监听,处理数据
        public function Run(){
            $null = NULL;
            //死循环,直到socket断开
            while(true){
                $changes = $this->sockets;
                $socket = $this->master;
                /*
                这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。
                socket_select ($sockets, $write = NULL, $except = NULL, NULL, timeout);
    
                $sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。
                $write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。
                $except是$sockets里面要被排除的元素,传入NULL是”监听”全部。
                最后一个参数是超时时间
                如果为0:则立即结束
                如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回
                如果为null:如遇某一个连接有新动态,则返回
                */
                /*
                * @socket_select  第四个参数
                * 第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
                * 第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
                * 第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
                */
                socket_select($changes,$null,$null,NULL);
                //socket_select($changed, $null, $null, 0, 10);
    
                /*
                * 如果有新的链接进来
                * */
                //如果有新的连接
                if (in_array($socket, $changes)) {
                    //接受一个socket连接
                    $socket_new = socket_accept($socket);
                    //给新连接进来的socket一个唯一的ID
                    $changes = $this->sockets[] = $socket_new;  //将新连接进来的socket存进连接池
                    //通过socket获取数据执行handshake
                    $header = socket_read($socket_new, 1024);
    
                    $this->perform_handshaking($header, $socket_new, '127.0.0.1', '1208');  //握手协议
    
                    //获取client ip 编码json数据,并发送通知
                    /*
                    * 获取远程套接口的名字,包括它的IP和端口。
                    * */
                    socket_getpeername($socket_new, $ip);
                    $response = $this->mask(json_encode(array('type'=>'system', 'message'=>'ip:'.$ip.' 已连接!')));
                    $this->send_message($response);
                    $found_socket = array_search($socket, $changes);
                    unset($changes[$found_socket]);
                }
    
                //轮询 每个client socket 连接  发送数据
                foreach ($changes as $key=>$changed_socket) {
    
                    //如果有client数据发送过来
                    /*
                    * @socket_recv ( resource $socket , string &$buf , int $len , int $flags )
                    * 从已连接的socket接收数据
                    * */
                    while(socket_recv($changed_socket, $buf, 1024, 0) >= 1)
                    {
                        //解码发送过来的数据
                        $received_text = $this->unmask($buf);
                        $tst_msg  = json_decode($received_text);
                        $user_name    = $tst_msg->name;
                        $user_message  = $tst_msg->message;
    
                        $this->writeLog('消息:', json_decode($received_text, true));
    
                        //把消息发送回所有连接的 client 上去
                        $response_text = $this->mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message)));
                        $this->send_message($response_text);
                        break 2;
                    }
    
                    //检查offline的client
                    $buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
                    if ($buf === false) {
                        $found_socket = array_search($changed_socket, $changes);
                        /*
                        * 获取远程套接口的名字,包括它的IP和端口。
                        * */
                        socket_getpeername($changed_socket, $ip);
                        unset($changes[$found_socket]);
                        $response = $this->mask(json_encode(array('type'=>'system', 'message'=>'ip:'.$ip.' 已断开!')));
                        $this->send_message($response);
                    }
                }
            }
            /*
            * 在监听外面关闭socket链接
            * 关闭监听的socket
            * */
            socket_close($this->master);
        }
    
        /*
        * 对发送的数据进行编码
        * */
        public function mask($text) {
            $b1 = 0x80 | (0x1 & 0x0f);
            $length = strlen($text);
    
            if ($length <= 125) {
                $header = pack('CC', $b1, $length);
            } elseif ($length > 125 && $length < 65536) {
                $header = pack('CCn', $b1, 126, $length);
            } elseif ($length >= 65536) {
                $header = pack('CCNN', $b1, 127, $length);
            }
    
            return $header.$text;
        }
    
        /*
        * 对接受来的数据进行解码
        * */
        public function unmask($text) {
            /*
            * @ ord
            *  函数返回字符串的首个字符的 ASCII 值。
            * */
            $length = ord($text[1]) & 127;
            if ($length == 126) {
                $masks = substr($text, 4, 4);
                $data = substr($text, 8);
            } elseif ($length == 127) {
                $masks = substr($text, 10, 4);
                $data = substr($text, 14);
            } else {
                $masks = substr($text, 2, 4);
                $data = substr($text, 6);
            }
    
            $text = "";
            $len = strlen($data);
            for ($i = 0; $i < $len; $i++) {
                $text .= $data[$i] ^ $masks[$i%4];
            }
            return $text;
        }
    
        //握手的逻辑
        public function perform_handshaking($receved_header,$client_conn, $host, $port)
        {
            $headers = array();
            $lines = preg_split("/rn/", $receved_header);
    
            foreach($lines as $line) {
                $line = chop($line);
                if(preg_match('/A(S ): (.*)z/', $line, $matches)) {
                    $headers[$matches[1]] = $matches[2];
                }
            }
    
            if (preg_match("/Sec-WebSocket-Key: (.*)
    /", $receved_header, $match)) {
    
                $key      = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
                $upgrade  = "HTTP/1.1 101 Switching Protocol
    " .
                    "Upgrade: websocket
    " .
                    "Connection: Upgrade
    " .
                    "Sec-WebSocket-Accept: ".$key."
    
    ";
                socket_write($client_conn,$upgrade,strlen($upgrade));
            }
    
            return true;
        }
    
        /*
        * @socket_
        * recv是从套接口接收数据,也就是拿过来,但是不知道是什么
        * read是读取拿过来的数据,就是要知道recv过来的是什么
        * write是向套接口写数据,但是只是写,并没有发送出去
        * send是write之后,将数据传输到套接口,以便其他人recv之后read
        * 发送消息
        * */
        //发送消息的方法
        public function send_message($msg) {
            //global $this->sockets;
            foreach($this->sockets as $changed_socket)
            {
                @socket_write($changed_socket,$msg,strlen($msg));
            }
            return true;
        }
    
        /*
        * 将发送的消息记录到log中
        * var_export 输出或返回一个变量的字符串表示
        * */
        //打印本地Err
        public function    writeLog($str, $arr) {
            $cont  = var_export($arr, true)."
    ";
            $time  = date('Y-m-d H:i:s',time());
    
            file_put_contents('./WebSocket/mylog',  $time, FILE_APPEND);
            file_put_contents('./WebSocket/mylog',  $str, FILE_APPEND);
            file_put_contents('./WebSocket/mylog',  $cont."
    ", FILE_APPEND);
    
            echo $cont;
        }
    
    }

    //客户端的实现代码

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
        <title>HTML5 websocket 网页聊天室 javascript php</title>
        <style type="text/css">
            body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}
            #ltian,.rin{width:98%; margin:5px auto;}
            #ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;}
            #ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}
            #us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}
            #us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
            #us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;}
            #us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;}
            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;}
            .rin p{margin-right:160px;}
            .rin span{float:right; padding:6px 5px 0px 5px; position:relative;}
            .rin span img{margin:0px 3px; cursor:pointer;}
            .rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;}
            .rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer}
    
            #ct p{padding:5px; line-height:20px;}
            #ct a{color:#069; cursor:pointer;}
            #ct span{color:#999; margin-right:10px;}
            .c2{color:#999;}
            .c3{background-color:#DBE9EC; padding:5px;}
            .qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-decoration:none; color:#069;}
            .tc{text-align:center; margin-top:5px;}
        </style>
    </head>
    <body>
    <div id="ltian">
        <div id="us" class="jb"></div>
        <div id="ct"></div>
        <a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>
    </div>
    <div class="rin">
        <button id="sd">发送</button>
        <p><input id="message"></p>
    </div>
    <div id="ems"><p></p><p class="tc"></p></div>
    
    </body>
    <script src="http://www.study.com/Reids/jquery-1.11.3.min.js"></script>
    <script>
        //console.log(Math.ceil((Math.random()*100000)+(Math.random()*100000)));
    if(typeof(WebSocket)=='undefined'){
        alert('你的浏览器不支持 WebSocket ,推荐使用Google Chrome 或者 Mozilla Firefox');
    }
    </script>
    <script>
        $(function(){
            var websocket;
            var name = Math.ceil((Math.random()*100000)+(Math.random()*100000));
            if(window.WebSocket) {
                websocket = new WebSocket('ws://'+ip+':1208/WebSocket/Class/WebSocket.php');
    
                //连接建立
                websocket.onopen = function(evevt) {
                    console.log("WebSocket已连接!");
                    $('#ct').append('<p><span>大家好,WebSocket已连接!</span></p>');
                }
    
                //收到消息
                websocket.onmessage = function(event) {
                    var msg = JSON.parse(event.data);  //解析收到的json消息数据
                    console.log(msg);
                    if(msg.type == 'system'){
                        $('#ct').append('<p><span>'+msg.message+'</span></p>');
                        console.log(msg.message);
                    }else if(msg.type == 'usermsg'){
                        $('#ct').append('<p><span>'+msg.message+'</span><a>用户:'+msg.name+'</a></p>');
                        console.log(msg.message);
                    }
                }
    
                //发生错误
                websocket.onerror = function(event) {
                    console.log("WebSocket连接出错!"+ event.data);
                    $('#ct').append('<p><span>WebSocket Error ' + event.data + '</span></p>');
                }
    
                //连接关闭
                websocket.onclose = function(event) {
                    console.log('WebSocket已断开连接');
                    $('#ct').append('<p><span>WebSocket已关闭!</span></p>');
                }
                /*  发送消息  */
                function send() {
                    var message = $('#message').val();
    
                    if(!message) {
                        alert('发送消息不能为空!'); return false;
                    }
    
                    var msg = { message: message, name: name };
    
                    try{
                        websocket.send(JSON.stringify(msg));
                        $('#message').val('');
                        //websocket.send(msg);
                    } catch(ex) {
                        console.log(ex);
                    }
                }
    
                //按下enter键发送消息
                $(window).keydown(function(event) {
                    if(event.keyCode == 13) {
                        send();
                    }
                });
    
                //点发送按钮发送消息
                $('#sd').bind('click',function() {
                    send();
                });
    
            } else {
                console.log('你的浏览器不支持Web Socket!');
            }
        })
    </script>
     
     
  • 相关阅读:
    JVM-堆内存
    生产者消费者模式-基于线程池
    nginx 499错误
    thrift入门
    RPC-基于原生java实现
    rocketMQ入门
    跟着刚哥深入学maven(通俗易懂)
    跟着刚哥学习Spring框架--AOP(五)
    跟着刚哥学习Spring框架--通过注解方式配置Bean(四)
    跟着刚哥学习Spring框架--Spring容器(二)
  • 原文地址:https://www.cnblogs.com/jing1208/p/6307204.html
Copyright © 2011-2022 走看看