PHP websocket 只是偶尔会丢失用户的数据



我是websockets的新手,我正面临着一个我无法解决的问题。我从网上下载了服务器和客户端,并修改了它,使其按我的方式工作。问题是我想存储用户的数据(例如:名称、颜色等)在服务器端。当我想将消息从一个用户传播给所有其他用户时,有时有效,有时无效。有趣的是,它每次读取用户的id都很完美,但有时它会丢失名称或颜色,这些实际上都是同一个类的字段,我看不出有什么区别……请帮助。

服务器:

        <?php
    /**
    * Simple server class which manage WebSocket protocols
    * @author Sann-Remy Chea <http://srchea.com>
    * @license This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    * @version 0.1
    */
    class Server {
    /**
    * The address of the server
    * @var String
    */
    private $address;
    /**
    * The port for the master socket
    * @var int
    */
    private $port;
    /**
    * The master socket
    * @var Resource
    */
    private $master;
    /**
    * The array of sockets (1 socket = 1 client)
    * @var Array of resource
    */
    private $sockets;
    /**
    * The array of connected clients
    * @var Array of clients
    */
    private $clients;
    /**
    * If true, the server will print messages to the terminal
    * @var Boolean
    */
    private $verboseMode;
    private $maxConnections = 3;
    /**
    * Server constructor
    * @param $address The address IP or hostname of the server (default: 127.0.0.1).
    * @param $port The port for the master socket (default: 5001)
    */
    function Server($address = '127.0.0.1', $port = 5001, $verboseMode = false) {
    $this->console("Server starting...");
    $this->address = $address;
    $this->port = $port;
    $this->verboseMode = $verboseMode;
    // socket creation
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
    if (!is_resource($socket))
    $this->console("socket_create() failed: ".socket_strerror(socket_last_error()), true);
    if (!socket_bind($socket, $this->address, $this->port))
    $this->console("socket_bind() failed: ".socket_strerror(socket_last_error()), true);
    if(!socket_listen($socket, 20))
    $this->console("socket_listen() failed: ".socket_strerror(socket_last_error()), true);
    $this->master = $socket;
    $this->sockets = array($socket);
    $this->console("Server started on {$this->address}:{$this->port}");
    }
    /**
    * Create a client object with its associated socket
    * @param $socket
    */
    private function connect($socket) {
    $this->console("Creating client...");
    $client = new Client(uniqid(), $socket);
    $this->clients[] = $client;
    $this->sockets[] = $socket;
    $this->console("Client #{$client->getId()} is successfully created!");
    }
    /**
    * Do the handshaking between client and server
    * @param $client
    * @param $headers
    */
    private function handshake($client, $headers) {
    $this->console("Getting client WebSocket version...");
    if(preg_match("/Sec-WebSocket-Version: (.*)rn/", $headers, $match))
    $version = $match[1];
    else {
    $this->console("The client doesn't support WebSocket");
    return false;
    }
    $this->console("Client WebSocket version is {$version}, (required: 13)");
    if($version == 13) {
    // Extract header variables
    $this->console("Getting headers...");
    if(preg_match("/GET (.*) HTTP/", $headers, $match))
    $root = $match[1];
    if(preg_match("/Host: (.*)rn/", $headers, $match))
    $host = $match[1];
    if(preg_match("/Origin: (.*)rn/", $headers, $match))
    $origin = $match[1];
    if(preg_match("/Sec-WebSocket-Key: (.*)rn/", $headers, $match))
    $key = $match[1];
    $this->console("Client headers are:");
    $this->console("t- Root: ".$root);
    $this->console("t- Host: ".$host);
    $this->console("t- Origin: ".$origin);
    $this->console("t- Sec-WebSocket-Key: ".$key);
    $this->console("Generating Sec-WebSocket-Accept key...");
    $acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
    $acceptKey = base64_encode(sha1($acceptKey, true));
    $upgrade = "HTTP/1.1 101 Switching Protocolsrn".
    "Upgrade: websocketrn".
    "Connection: Upgradern".
    "Sec-WebSocket-Accept: $acceptKey".
    "rnrn";
    $this->console("Sending this response to the client #{$client->getId()}:rn".$upgrade);
    socket_write($client->getSocket(), $upgrade);
    $client->setHandshake(true);
    $this->console("Handshake is successfully done!");
    return true;
    }
    else {
    $this->console("WebSocket version 13 required (the client supports version {$version})");
    return false;
    }
    }
    /**
    * Disconnect a client and close the connection
    * @param $socket
    */
    private function disconnect($client)
    {
        $this->console("Disconnecting client #{$client->getId()}");
        $i = array_search($client, $this->clients);
        $j = array_search($client->getSocket(), $this->sockets);
        if($j >= 0)
        {
            array_splice($this->sockets, $j, 1);
            socket_shutdown($client->getSocket(), 2);
            socket_close($client->getSocket());
            $this->console("Socket closed");
        }
        if($i >= 0)
        array_splice($this->clients, $i, 1);
        $this->console("Client #{$client->getId()} disconnected");
        /*foreach ($this->clients as $cnt)
            $this->send($cnt,$this->usernames[$client->getId()] ." left!");*/
    }
    /**
    * Get the client associated with the socket
    * @param $socket
    * @return A client object if found, if not false
    */
    private function getClientBySocket($socket) {
    foreach($this->clients as $client)
    if($client->getSocket() == $socket) {
    $this->console("Client found");
    return $client;
    }
    return false;
    }
    /**
    * Do an action
    * @param $client
    * @param $action
    */
    private function action($client, $action)
    {
        $action = $this->unmask($action);
        $this->console("Performing action: ".$action);
        if($action == "exit" || $action == "quit")
        {
            $this->console("Killing a child process");
            posix_kill($client->getPid(), SIGTERM);
            $this->console("Process {$client->getPid()} is killed!");
        }
    }
    /**
    * Run the server
    */
    public function run()
    {
        $this->console("Start running...");
        while(true)
        {
            $changed_sockets = $this->sockets;
            @socket_select($changed_sockets, $write = NULL, $except = NULL, 1);
            foreach($changed_sockets as $socket)
            {
                if($socket == $this->master)
                {
                    if(($acceptedSocket = socket_accept($this->master)) < 0)
                    {
                        $this->console("Socket error: ".socket_strerror(socket_last_error($acceptedSocket)));
                    }
                    else
                    {
                        $this->connect($acceptedSocket);
                    }
                }
                else
                {
                    $this->console("Finding the socket that associated to the client...");
                    $client = $this->getClientBySocket($socket);
                    if($client)
                    {
                        $this->console("Receiving data from the client");
                        $data=null;
                        while($bytes = @socket_recv($socket, $r_data, 2048, MSG_DONTWAIT))
                        {
                            $data.=$r_data;
                        }
                        if(!$client->getHandshake())
                        {
                            $this->console("Doing the handshake");
                            if($this->handshake($client, $data))
                            $this->startProcess($client);
                        }
                        elseif($bytes === 0)
                        {
                            $this->console("disconnecting...");
                            $this->disconnect($client);
                        }
                        else
                        {
                            $data = (string)$this->unmask($data);
                            $data = json_decode($data);
                            if( $data != "" )$this->console("Got data from user: |" . implode(',',$data) . "|");
                            // When received data from client
                            if( $data[0] == "setName" )//set name, color for user with given id
                            {
                                $id = $client->getId();
                                $colors = array( '990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000' );
                                //$col = '#'.$colors[ rand(0, sizeof($colors)-1) ];
                                $col = '#'.$colors[ sizeof( $this->clients ) ];
                                $this->console( "Setting attributes for user $id | ". $data[1] ." | $col |" );
                                $client->setName( $data[1] );
                                $client->setColor( $col );
                            }
                            else if( $data != "" )
                            {
                                $counter = 0 ;
                                $id = $client->getId();
                                $name = null ;
                                $color = null ;
                                //while($name == null)
                                if(1)
                                {
                                    foreach ($this->clients as $c)
                                    {
                                        if( $c->getId() == $id )
                                        {
                                            $name = $c->getName();
                                            $color = $c->getColor();
                                            break;
                                        }
                                    }
                                    ++$counter;
                                }
                                foreach ($this->clients as $c)
                                {
                                    $this->send( $c, json_encode( array( 
                                                                        $name . "[$counter]", 
                                                                        $color, 
                                                                        $data[1] )
                                                                ) );
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    /**
    * Start a child process for pushing data
    * @param unknown_type $client
    */
    private function startProcess($client)
    {
        $this->console("Start a client process");
        $pid = pcntl_fork();
        if($pid == -1)
        {
            die('could not fork');
        }
        elseif($pid)
        { // process
            $client->setPid($pid);
        }
        else
        {
            if( sizeof( $this->clients ) > $this->maxConnections )
            {
                $this->send( $client, json_encode(array("No free slots!")) );
                $this->disconnect($client);
            }
            else
                $this->send($client, json_encode($client->getId()));
            // we are the child
            while(false)
            {
                //if the client is broken, exit the child process
                if($client->exists==false)
                {
                    break;
                }   
                // push something to the client
                $seconds = rand(2, 5);
                $this->send($client, "I am waiting {$seconds} seconds");
                sleep($seconds);
            }
        }
    }
    /**
    * Send a text to client
    * @param $client
    * @param $text
    */
    private function send($client, $text) {
        $this->console("Send '".$text."' to client #{$client->getId()}");
        $text = $this->encode($text);
        if( socket_write($client->getSocket(), $text, strlen($text)) === FALSE )
        {
            $client->exists=false; //flag the client as broken
            $this->console("Unable to write to client #{$client->getId()}'s socket");
            $this->disconnect($client);
        }
        else
            $this->console ("data sent!");
    }
    /**
    * Encode a text for sending to clients via ws://
    * @param $text
    * @param $messageType
    */
    function encode($message, $messageType='text') {
    switch ($messageType) {
    case 'continuous':
    $b1 = 0;
    break;
    case 'text':
    $b1 = 1;
    break;
    case 'binary':
    $b1 = 2;
    break;
    case 'close':
    $b1 = 8;
    break;
    case 'ping':
    $b1 = 9;
    break;
    case 'pong':
    $b1 = 10;
    break;
    }
    $b1 += 128;

    $length = strlen($message);
    $lengthField = "";
    if ($length < 126) {
    $b2 = $length;
    } elseif ($length <= 65536) {
    $b2 = 126;
    $hexLength = dechex($length);
    //$this->stdout("Hex Length: $hexLength");
    if (strlen($hexLength)%2 == 1) {
    $hexLength = '0' . $hexLength;
    }
    $n = strlen($hexLength) - 2;
    for ($i = $n; $i >= 0; $i=$i-2) {
    $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
    }
    while (strlen($lengthField) < 2) {
    $lengthField = chr(0) . $lengthField;
    }
    } else {
    $b2 = 127;
    $hexLength = dechex($length);
    if (strlen($hexLength)%2 == 1) {
    $hexLength = '0' . $hexLength;
    }
    $n = strlen($hexLength) - 2;
    for ($i = $n; $i >= 0; $i=$i-2) {
    $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
    }
    while (strlen($lengthField) < 8) {
    $lengthField = chr(0) . $lengthField;
    }
    }
    return chr($b1) . chr($b2) . $lengthField . $message;
    }

    /**
    * Unmask a received payload
    * @param $buffer
    */
    private function unmask($payload) {
    $length = ord($payload[1]) & 127;
    if($length == 126) {
    $masks = substr($payload, 4, 4);
    $data = substr($payload, 8);
    }
    elseif($length == 127) {
    $masks = substr($payload, 10, 4);
    $data = substr($payload, 14);
    }
    else {
    $masks = substr($payload, 2, 4);
    $data = substr($payload, 6);
    }
    $text = '';
    for ($i = 0; $i < strlen($data); ++$i) {
    $text .= $data[$i] ^ $masks[$i%4];
    }
    return $text;
    }
    /**
    * Print a text to the terminal
    * @param $text the text to display
    * @param $exit if true, the process will exit
    */
    private function console($text, $exit = false) {
    $text = date('[Y-m-d H:i:s] ').$text."rn";
    if($exit)
    die($text);
    if($this->verboseMode)
    echo $text;
    }
    }
    error_reporting(E_ALL);
    require_once 'client.php';
    set_time_limit(0);
    // variables
    $address = '127.0.0.1';
    $port = 5001;
    $verboseMode = true;
    $server = new Server($address, $port, $verboseMode);
    $server->run();

    ?>
客户:

        <?php
    /**
    * Define a Client object
    * @author Sann-Remy Chea <http://srchea.com>
    * @license This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
    * @version 0.1
    */
    class Client {
    private $id;
    private $socket;
    private $handshake;
    private $pid;
    private $name;
    private $color;
    public $exists=true; //to check if client is broken

    function Client($id, $socket) {
    $this->id = $id;
    $this->socket = $socket;
    $this->handshake = false;
    $this->pid = null;
    }
    public function setName( $n )
    {
        $this->name = $n;
    }
    public function getName()
    {
        return $this->name;
    }
    public function setColor( $c )
    {
        $this->color = $c;
    }
    public function getColor()
    {
        return $this->color;
    }
    public function getId() {
    return $this->id;
    }
    public function getSocket() {
    return $this->socket;
    }
    public function getHandshake() {
    return $this->handshake;
    }
    public function getPid() {
    return $this->pid;
    }
    public function setId($id) {
    $this->id = $id;
    }
    public function setSocket($socket) {
    $this->socket = $socket;
    }
    public function setHandshake($handshake) {
    $this->handshake = $handshake;
    }
    public function setPid($pid) {
    $this->pid = $pid;
    }
    }
    ?>

指数:

        <!DOCTYPE html>
    <html lang="pl">
    <head>
            <title>Websockets</title>
            <meta name="BB" content="" />
            <meta name="description" content="" />
            <meta name="keywords" content="" />
            <meta charset="UTF-8" />
            <style>
                html, body
                {
                    padding: 0;
                    margin: 0;
                }
                #out
                {
                    width:840px;
                    height:500px;
                    mragin-right: -20px;
                    color:#FFF;
                    font-family: Courier New;
                    overflow-x: hidden;
                    overflow-y: auto;
                }
                #outwrap
                {
                    width:800px;
                    height:500px;
                    margin:40px auto 0 auto;
                    display: block;
                    border: 1px solid #F00;
                    background-color: #000;
                    padding: 10px;
                    overflow:hidden;
                }
                #field
                {
                    width:820px;
                    margin:10px auto;
                    display: block;
                    background-color: #000;
                    color: #FFF;
                    height: 50px;
                }
                #btn
                {
                    background-color: #000;
                    margin:0 auto;
                    display:block;
                    width:100px;
                    height:50px;
                    font-size:20px;
                    border-radius: 10px;
                }
                #btn:hover
                {
                    background-color: #666;
                }
            </style>
            <script>
                "use strict";
                function $(id){return document.getElementById(id);}
                document.addEventListener("DOMContentLoaded",function(){
                    var name = "";
                    while( name === "" || name === null )name = window.prompt( "Name" );
                    var myId = '';
                    $("field").value = "";
                    $("field").focus();
                    //var colors = ['990099','660099','66ccff','339933','99ff00','ccff00','663300','cc0000'];
                    //var myColor = '#' + colors[Math.floor( Math.random()*colors.length )] ;
                    //$("btn").style.color = myColor ;

                    var ws = new WebSocket('ws://127.0.0.1:5001');
                    ws.onopen = function(msg) {
                        write( "connected as "+ name +"!" );
                        ws.send( JSON.stringify( new Array( "setName",name ) ) );
                        $("field").focus();
                        //ws.send( name+": connected" );
                        //write('Connection successfully opened (readyState ' + this.readyState+')');
                    };
                    ws.onclose = function(msg) {
                        if(this.readyState === 2)write( "disconnected!" );
                            /*write(
                                'Closing... The connection is going throught'
                                + 'the closing handshake (readyState '+this.readyState+')'
                            );*/
                        else if(this.readyState === 3)write( "disconnected!" );
                            /*write(
                                'Connection closed... The connection has been closed'
                                + 'or could not be opened (readyState '+this.readyState+')'
                            );*/
                        else write( "disconnected!" );
                            //write('Connection closed... (unhandled readyState '+this.readyState+')');
                    };
                    ws.onerror = function(event) {
                        if(typeof (event.data) != 'undefined')write( "ERROR:" + event.data );
                    };
                    ws.onmessage = function(e){
                        var msg = JSON.parse(e.data);
                        msg = msg.toString().split(",");
                        if( msg.length == 1 )myId = msg.toString();
                        else
                        {
                            write( "<p style='color:"+ msg[1] +";'>"+ msg[0] +": "+ msg[2] +"</p>" );
                        }
                        console.log( "Server -> " + msg );
                        //write( "inc: " + msg );
                    };
                    $("field").addEventListener("keydown",function(e){
                        if( e.keyCode == 13 )
                        {
                            $("btn").click();
                        }
                    });
                    $("btn").addEventListener("click",function(){
                        if( ws.readyState === 1 )
                            ws.send( JSON.stringify( new Array( myId, $("field").value ) ) );
                        else write( "Couldn't send the message..." );
                        $("field").value = "";
                        $("field").focus();
                    });
                    window.onbeforeunload = function()
                    {
                        //ws.send( name+": disconnected" );
                        ws.close();
                    }
                    function write( info )
                    {
                        //$("out").innerHTML = info + "<br />" + $("out").innerHTML;
                        var p = document.createElement( "p" );
                        p.innerHTML = info ;
                        $("out").appendChild( p );
                        $("out").scrollTop = $("out").scrollHeight;
                    }
                });

            </script>
    </head>
    <body>
        <div id="outwrap">
            <div id="out"></div>
        </div>
        <input type="text" id="field" />
        <input type="button" value="SEND" id="btn" />
    </body>
    </html>

如果你在第278行(server.php)取消注释,并在下一行注释:如果我说的是循环无限…

你犯了一个常见的(初学者?)错误,假设从TCP套接字写入/读取发送/接收的字节数与调用被告知发送/接收的字节数相同。不一定是的情况。

检查这些函数(socket_recv()/socket_send())返回的值并循环直到所有数据发送/接收完毕。

相关内容

  • 没有找到相关文章

最新更新