简体   繁体   中英

PHP stream websockets multiuser support

I've been making my own websocket server based on scarce info scattered all over the Internet. Here's what I've got:

public function __construct() {
    if (($this->Socket = stream_socket_server("tcp://192.168.0.14:12369", $errno, $errstr)) === false) {
        echo "Creating socket server failed: " . socket_strerror(socket_last_error()) . "\r\n";
        exit;
    }
    else {
        echo "Socket server created on " . date("j F Y g:i:s") . "\r\n";
        echo "Socket Resource is ".$this->Socket."\r\n";
    }

    $this->Sockets[] = $this->Socket;
}

public function Run() {
    while (true) {
        $ReadFromSockets = array();
        foreach($this->Sockets as $_S) {
            $ReadFromSockets[] = $_S;
        }
        foreach($this->Children as $_C) {
            $ReadFromSockets[] = $_C;
        }
        stream_select($ReadFromSockets, $this->SocketsWrite, $this->SocketsExcept, null);

        foreach($ReadFromSockets as $_Socket) {
            if($_Socket === $this->Socket) {
                if (($SocketResource = stream_socket_accept($_Socket)) === FALSE) {
                    echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($_Socket)) . "\r\n";
                }
                $this->Sockets[] = $SocketResource;
                $this->SocketWrite = $this->Sockets;

                $this->DoHandshake($SocketResource);

                $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
                $PID = pcntl_fork();
                if($PID == -1) {
                    echo "There was an error forking the process";
                } elseif($PID) { 
                    fclose($pair[0]);
                    $this->Children[] = $pair[1];
                } else {
                    fclose($pair[1]);
                    $this->Parent = $pair[0];
                    $this->Children = array();
                    unset($this->Sockets[array_search($this->Socket, $this->Sockets)]);
                    break;
                }
            } else {
                $this->Listening($SocketResource);
            }
        }
    }
    socket_close($this->Socket);
}

private function Listening($SocketResource) {
    $data = fread($SocketResource, 100000);
    $data = $this->Unmask($data);
    $data = $this->Mask($data);

    if($this->Parent) {
        fwrite($this->Parent, $data);
    } else {
        foreach($this->Sockets as $_S) {
            fwrite($_S, $data);
        }
    }
}

I am not listing DoHandshake , Mask and Unmask functions - they all work fine and are there for only the handshaking.

The idea behind the listed code is that a user connects to the socket, the process gets forked and the main process gets the Resource # for the child and vice verca. When a user sends some data to the (forked) socket, it should send the data to the parent process. The parent process should iterate through the list of all clients that are saved in $this->Sockets array and send the message to all of them. But it doesn't work and I can't really seem to understand why.

The answer was hidden in the wrong understanding of the process of users connecting to the socket and how to listen to the data they send.

I though, that === and it was wrong === a user connects, gets a copy of a socket process and then talks to the socket server through the copy of the main process. In my mind, it looked like this:

client -->> child process -->> parent process
  ^                                    |
  ^                                    |
  |                                    ⇩
  |----------------- doing something with data

Where the arrows represent the data being sent.

Now the right solution turned out to be much simpler. We must have $this->Sockets array, but not include $this->Socket , which is the main socket server, in it. If we do, on every call we'll get an error. Still, we add $this->Socket to an explicit array only for listening.

In $this->Sockets we hold the array of all clients and that's it. And we do not need to fork the process, as it's enough to iterate through $this->Sockets when a client sends something.

Here's the final code:

public function __construct() {
    if (($this->Socket = stream_socket_server("tcp://192.168.1.14:12369", $errno, $errstr)) === false) {
        echo "Creating socket server failed: " . $errno . $errstr . "\r\n";
        exit;
    }
}

public function Run() {
    while (true) {          
        $ReadFromSockets = $this->Sockets;
        $ReadFromSockets[] = $this->Socket;

        $null = null;

        stream_select($ReadFromSockets, $null, $null, null);

        foreach($ReadFromSockets as $_Socket) {
            if($_Socket === $this->Socket) {
                if (($_Socket = stream_socket_accept($_Socket,-1)) === FALSE) {
                    echo "failed\r\n";
                }
                $this->Sockets[] = $_Socket;
                $this->DoHandshake($_Socket);
            } else {
                $this->Listening($_Socket);
            }
        }
    }
    socket_close($this->Socket);
}

private function Listening($SocketResource) {
    $data = fread($SocketResource, 100000);

    $data = $this->Unmask($data);
    $data = $this->Mask($data);
    // if we do not make Unmask/Mask process,
    // we'll catch a forced disconnect by the client

    foreach($this->Sockets as $_S) {
        fwrite($_S, $data);
    }
}

Hope this helps to understand the logic behind websockets. It's better to know how it works instead of mindlessly using ready-to-go solutions, right, OIS? ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM