【问题标题】:PHP: how to use socket_select() and socket_read() properlyPHP:如何正确使用 socket_select() 和 socket_read()
【发布时间】:2013-08-25 23:17:47
【问题描述】:

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

我有一个多线程 PHP 应用程序,并且我正在使用套接字在线程之间进行通信。通信工作得很好,但我最终对没有准备好数据的套接字进行了很多不必要的读取。也许我缺少一些关于套接字编程的东西。

socket_select()PHP doc 表示 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 文档所说的,但我不希望那样。我想知道那里真的有数据。我做错了吗?还是没抓住重点?

【问题讨论】:

    标签: php multithreading sockets


    【解决方案1】:

    您在滥用套接字函数。

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

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

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

    取而代之的是,如果检测到连接,则代码接受它并再次返回socket_select;准备读取的套接字未得到服务。

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

    【讨论】:

    • 好的,有用的反馈,但我还是卡住了。首先,我现在检查socket_read 以返回false。它没有返回错误。我只是得到空字符串。其次,关于socket_select 指令的要点很好,但是我发布的代码被我的实验所扼杀。我实际上一开始就正确地遵循了这些说明。我把所有东西都原样放回去了,但我仍然有问题(我会修复我的帖子)。最后,关于你所说的问题:它永远不会再返回任何有意义的东西。你是指EOF之后,还是any零长度返回之后?
    • @GreatBigBore:零长度返回 == EOF。得到第一个后,所有后续返回都保证也是零长度。
    • @GreatBigBore:在socket_select 的文档中:套接字资源在文件结束时也已准备就绪,在这种情况下,socket_read() 将返回零长度字符串。套接字是流,因此 EOF 意味着远程主机已关闭其写入的一半连接。当然,您可以在单个连接的生命周期内多次交换数据;你在那里做错了什么,但我不知道是什么。
    • 我明白了。 socket_select() 确实完全按照我的预期工作。正如您所指出的,我的测试工具中的线程意外退出并导致socket_select() 报告就绪。非常感谢。
    猜你喜欢
    • 2011-02-05
    • 1970-01-01
    • 2013-07-26
    • 2011-04-09
    • 1970-01-01
    • 2013-06-21
    • 2012-02-04
    • 1970-01-01
    • 2015-06-02
    相关资源
    最近更新 更多