1

PHP8 has introduced Socket class that replaces old socket resource. Now all socket functions works with the instance of this class.

The 2nd argument of EventListener's constructor (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 . 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.

Please advice how can I acquire instance of Socket for this file descriptor?


Solution that doesn't work:

I tried to find address and port that are bound to the file descriptor, then create new socket and bind it to this address and port. But that creates new socket with new file descriptor, not related to connection received in callback of event listener.

I'm stuck on finding any useful documentation or code samples for my problem while migrating my event-based socket server to php8.

4 Answers4

1

The short answer is: you can pass the numeric file descriptor as the 2nd argument to 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.

There is an inaccuracy in the the documentation of EventBufferEvent::__construct:

socket

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

It is slightly misleading because the second argument is actually as flexible as most of the Event functions that accept an "fd" or a "stream". Event tries to get numeric file descriptor from the argument, and if it is already a numeric value, it uses it.

See how the "Echo server" example creates an instance of EventBufferEvent for every connection:

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);
Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
0

We don't know much about Socket class, PHP documentation says it "A fully opaque class". Thus I have to look into C sources of sockets.so implementation sockets.c. I see, Socket object is described by php_socket structure.
I look into header file 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() function is implemented in sockets.c. It does what we need, but it isn't exported to PHP scope, thus we aren't able to call it.

I search for usages of socket_import_file_descriptor() among exported functions. It is called from socket_import_stream() only. Let's look to PHP documentation of socket_import_stream(resource $stream): Socket|false. It receives resource and results with instance of Socket. This is what we need, but file descriptor is not a resource. However, there is way to open file descriptor and acquire its resource. Here is solution:

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

Good news it works in linux. Any advice, how to make it work in windows?

0

Use can also use an undocumented EventUtil::createSocket function:

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

Only make sure to save the reference to the socket somewhere, if you are using the $fd with other classes such as EventBufferEvent. Otherwise, the $socket variable may be closed together with the underlying file descriptor.

Ruslan Osmanov
  • 20,486
  • 7
  • 46
  • 60
0

php8 can work with this (from pecl-event)

PHP_EVENT_METHOD(EventUtil, getSocketFd) {
        zval *pzfd = NULL;
        php_socket *php_sock;

        if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &pzfd) == FAILURE) {
                return;
        }
        php_sock = Z_SOCKET_P(pzfd);
        ENSURE_SOCKET_VALID(php_sock);

        if (Z_ISUNDEF(php_sock->zstream) && (!IS_INVALID_SOCKET(php_sock))) {
            RETURN_LONG(php_sock->bsd_socket);
        }

        RETVAL_LONG(pzfd ? php_event_zval_to_fd(pzfd) : -1);
}