PHP:如何正确使用socket_select()和socket_read()

时间:2013-08-25 23:17:47

标签: php multithreading sockets

编辑 *测试工具中的错误导致我误解了结果。 socket_select()正如您所期望的那样 正好 :它确实会等到套接字上有数据就绪。但是,如果在客户端关闭连接后调用它,它将报告就绪。事实证明这完全是我的错,但我会在这里留下问题以防其他人怀疑socket_select()的行为。

我有一个多线程PHP应用程序,我正在使用套接字在线程之间进行通信。通信工作非常好,但我最终做了很多不必要的读取没有数据就绪的套接字。或许有一些关于套接字编程的东西我不知道。

socket_select()的{​​{3}}表示将会监视read数组中列出的套接字,以查看读取是否会阻止

这正是我想要的行为:调用socket_select()等到我的一个子线程试图与我交谈。但是,在我接受连接之后,这仅适用于线程写入的第一次。在此之后,socket_select()将永远说套接字已准备就绪,即使我已经读取了它的所有数据。

我是否有某种方法可以将该套接字标记为“未准备好”,以便在更多数据到达之前socket_select()不会报告它已准备就绪?或者在我读完所有数据后关闭该连接是否常规,并等待另一个连接请求?我已经做了很多教程和解释阅读,但我还没弄清楚如何正确地做到这一点。也许我错过了一些明显的东西?

如果它有助于查看代码,这就是我正在做的事情:

// Server side setup in the main thread
$this->mConnectionSocket = socket_create(AF_INET, SOCK_STREAM, 0);

$arrOpt = array('l_onoff' => 1, 'l_linger' => 0);
@socket_set_option($this->mConnectionSocket, SOL_SOCKET, SO_LINGER, $arrOpt);
@socket_set_option($this->mConnectionSocket, SOL_SOCKET, SO_REUSEADDR, true);
@socket_bind($this->mConnectionSocket, Hostname, $this->mPortNumber);
@socket_listen($this->mConnectionSocket);
@socket_set_block($this->mConnectionSocket);
.
.
.
// Then call listen(), which looks like this:
    public function listen(&$outReadySockets) {
        $null = null;

        while(true) {
            $readyArray = array_merge(array($this->mConnectionSocket), $this->mReceiverSockets);
            socket_select($readyArray, $null, $null, $waitTime = null);

            if(in_array($this->mConnectionSocket, $readyArray) === true) {
                $this->acceptConnection();
                $key = array_search($this->mConnectionSocket, $readyArray);

                if($key === false) {
                    throw new IPCException("array_search() returned unexpected value");
                } else {
                    unset($readyArray[$key]);
                    if(in_array($this->mConnectionSocket, $readyArray) === true) {
                        throw new IPCException("in_array() says the key is still there");
                    }
                }
            }

            if(count($readyArray) > 0) {
                $outReadySockets = array_merge($readyArray);
                break;
            }
        }
    }


// Client side setup in the child thread
$this->mSocket = @socket_create(AF_INET, SOCK_STREAM, 0);
@socket_set_block($this->mSocket);
@socket_connect($this->mSocket, Hostname, $this->mPortNumber);
.
.
.
@socket_write($this->mSocket, $inDataToWrite, $lengthToWrite);



// Main thread reads the socket until it's empty
    $data = "";
    $totalBytesRead = 0;
    while($totalBytesRead < $inNumberOfBytesToRead) {
        // Strange that even if we set the socket to block mode, socket_read()
        // will not block. If there's nothing there, it will just return an
        // empty string. This is documented in the PHP docs.
        $tdata = socket_read($inSock, $inNumberOfBytesToRead);
        if($tdata === false) {
            throw new IPCException("socket_read() failed: " . socket_strerror(socket_last_error()));
        } else {
            $data .= $tdata;

            $bytesReadThisPass = strlen($tdata);
            if($bytesReadThisPass === 0) {
                break;
            }
        }

        $totalBytesRead += $bytesReadThisPass;
    }
.
.
.
// Then calls listen() again

正如我所说,它很有效,除了当我第二次调用listen()时,它告诉我套接字仍然准备就绪。这似乎是PHP文档所说的,但我不希望这样。我想知道什么时候确实有数据。我做错了吗?或者只是忽略了这一点?

1 个答案:

答案 0 :(得分:2)

您正在滥用套接字函数。

首先,socket_read会在出错时返回false;您的代码不会检查此错误,并将错误返回与空字符串相同(这是您正在使用的特定构造的工件,即字符串连接和strlen)。

另一个问题是,当检测到连接尝试时,您显然违反了socket_select的说明:

  

如果您不打算将套接字资源添加到任何集合中   在socket_select()调用之后检查其结果,并进行响应   适当。 socket_select()返回后,所有socket资源都在   必须检查所有数组。任何可用的套接字资源   必须写入写入,并且可以使用任何套接字资源   阅读必须从中读取。

而不是这样,如果检测到连接,代码会接受它并再次返回socket_select;准备好阅读的插座不能提供服务。

最后,您似乎对EOF在套接字上的含义感到困惑:这意味着客户端已关闭其连接的写入端。一旦socket_read第一次返回空字符串(而非false!),将永远不会再返回任何有意义的内容。一旦发生这种情况,您可以从服务器的写端发送数据和/或完全关闭连接。