PHP8/EventListener - 从文件描述符获取 Socket 实例

PHP8/EventListener - Getting Socket instance from file descriptor

PHP8 引入了 Socket class 来替换旧的套接字资源。现在所有套接字函数都适用于此 class.

的实例

EventListener 构造函数的第二个参数 (https://www.php.net/manual/en/class.eventlistener.php) is callback function. It is called when new connection event arises. There is signature of this callback function: https://www.php.net/manual/en/eventlistener.setcallback.php 。它接收文件描述符作为第二个参数。 文件描述符是数值,但我需要 Socket 的实例 class,因为接下来我会将它传递给 EventBufferEvent 的构造函数。

请指教如何为这个文件描述符获取套接字实例?


无效的解决方案:

我试图找到绑定到文件描述符的地址和端口,然后创建新的套接字并将其绑定到这个地址和端口。但这会创建具有新文件描述符的新套接字,与事件侦听器回调中接收到的连接无关。

在将基于事件的套接字服务器迁移到 php8 时,我无法找到任何有用的文档或代码示例来解决我的问题。

我们对 Socket class 了解不多,PHP 文档说它是“完全不透明的 class”。因此,我必须查看 sockets.so 实现 sockets.c 的 C 源代码。我明白了,Socket 对象是由 php_socket 结构描述的。
我查看了头文件 php_sockets.h as well. It contains the function signature PHP_SOCKETS_API int socket_import_file_descriptor(PHP_SOCKET socket, php_socket *retsock);. It receives file descriptor of socket connection and results with pointer to structure that defines instance of PHP Socket class. Let's check how socket_import_file_descriptor() 函数是在 sockets.c 中实现的。它可以满足我们的需求,但不会导出到 PHP 范围,因此我们无法调用它。

我在导出函数中搜索 socket_import_file_descriptor() 的用法。它仅从 socket_import_stream() 调用。让我们看看 socket_import_stream(resource $stream): Socket|false 的 PHP 文档。它通过 Socket 实例接收资源和结果。这是我们需要的,但文件描述符不是资源。但是,有办法打开文件描述符并获取其资源。这是解决方案:

$filename = sprintf("php://fd/%d", $clientResourceFd);
$clientResource = fopen($filename, 'rb');
$clientSocket = socket_import_stream($clientResource);

好消息它在 linux 中有效。任何建议,如何让它在 windows 中工作?

简短的回答是:您可以将数字文件描述符作为 2nd 参数传递给 EventBufferEvent::__construct


https://www.php.net/manual/en/eventlistener.setcallback.php . It receives file descriptor as the 2nd argument. File descriptor is numeric value, but I need instance of Socket class, because next I will pass it to constructor of EventBufferEvent.

EventBufferEvent::__construct 的文档中存在错误:

socket

May be created as a stream(not necessarily by means of sockets extension)

这有点误导,因为第二个参数实际上与大多数接受“fd”或“流”的事件函数一样灵活。事件尝试从参数中获取数值文件描述符,如果它已经是数值,则使用它。

查看 "Echo server" example 如何为每个连接创建 EventBufferEvent 的实例:

class MyListenerConnection {
    public function __construct($base, $fd) {
        $this->base = $base;
        $this->bev = new EventBufferEvent($base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);

// ...

$this->conn[] = new MyListenerConnection($base, $fd);

Use 也可以使用未记录的 EventUtil::createSocket 函数:

$socket = EventUtil::createSocket($fd);
socket_set_nonblock($socket);
$socketFd = EventUtil::getSocketFd($socket);

如果您将 $fd 与其他 类(例如 EventBufferEvent)一起使用,请确保将对套接字的引用保存在某处。否则,$socket 变量可能与底层文件描述符一起关闭。