WebSocket 網頁聊天室

先給你們開一個原始的websocket的鏈接使用範例javascript

<?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\n");//綁定Socket到端口
$result=socket_bind($socket,$host,$port) or die("Could not bind to socket\n");//開始監聽連接
$result=socket_listen($socket,3) or die("Could not setup socket listener\n");//accept in coming connections
//另外一個Socket來處理通訊
$spawn=socket_accept($socket) or die("Could not accept in coming connection\n");//得到客戶端的輸入
$input=socket_read($spawn,1024) or die("Could not read input\n");//清空輸入字符串
$input=trim($input);//處理客戶端輸入並返回結果
$output=strrev($input)."\n";
socket_write($spawn,$output,strlen($output)) or die("Could not write out put\n");//關閉
socket_close($spawn);
socket_close($socket);
/*
 * PHP服務器端的代碼
 * 實現webSocket的實現,先得是客戶端發起請求
 * 在服務器端這邊,建立socket操做,鏈接socket
 * 接下來進行死循環,對全部的鏈接進行監聽
 * 有消息發過來時,進行推送,
 *   一、對發送過來的數據要先進行解密,
 *   二、對推送的消息要進行加密
 * 這樣作是爲了推送的消息的安全性
 * */
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: (.*)\r\n/", $receved_header, $match)) {

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

        echo $cont;
    }

}

//客戶端的實現代碼php

<!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>
相關文章
相關標籤/搜索