PHP流websockets多用户支持

时间:2014-12-16 19:13:34

标签: php stream websocket

我一直根据遍布互联网的稀缺信息制作自己的websocket服务器。这就是我所拥有的:

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);
        }
    }
}

我没有列出DoHandshakeMaskUnmask函数 - 它们都运行正常,仅用于握手。

列出的代码背后的想法是用户连接到套接字,进程分叉,主进程获得子进程的Resource #和副进程。当用户向(分叉)套接字发送一些数据时,它应该将数据发送到父进程。父进程应遍历保存在$this->Sockets数组中的所有客户端的列表,并将消息发送给所有客户端。但它不起作用,我似乎无法理解为什么。

1 个答案:

答案 0 :(得分:1)

答案隐藏在对用户连接套接字的过程以及如何收听他们发送的数据的错误理解中。

我认为,=== 并且错误 ===用户连接,获取套接字进程的副本,然后通过主进程的副本与套接字服务器通信。在我看来,它看起来像这样:

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

箭头表示正在发送的数据。

现在正确的解决方案变得更加简单。我们必须有$this->Sockets数组,但不包括$this->Socket,它是主套接字服务器。如果我们这样做,每次通话我们都会收到错误。不过,我们只将$this->Socket添加到显式数组中,仅用于监听。

$this->Sockets中,我们拥有所有客户端的数组,就是这样。我们不需要分叉流程,因为当客户端发送内容时,它足以迭代$this->Sockets

这是最终的代码:

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);
    }
}

希望这有助于理解websockets背后的逻辑。最好知道它是如何工作的,而不是盲目地使用现成的解决方案,对吧,OIS? ;)