From ba697a220767690743e5e504fc3243b531f54f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Mar 2015 22:45:08 +0100 Subject: [PATCH 1/4] Prototype for using stream based API --- composer.json | 3 +-- src/Factory.php | 33 +++++++++++++-------------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index d629f96..d2abf7b 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,7 @@ "require": { "php": ">=5.3", "react/event-loop": "~0.3.0|~0.4.0", - "react/promise": "~1.0|~2.0", - "clue/socket-react": "~0.3.0" + "react/datagram": "~1.0" }, "require-dev": { "clue/hexdump": "0.2.*" diff --git a/src/Factory.php b/src/Factory.php index fd7e8e0..50e197d 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -3,8 +3,7 @@ namespace Clue\React\Multicast; use React\EventLoop\LoopInterface; -use Socket\React\Datagram\Factory as DatagramFactory; -use Socket\Raw\Factory as RawFactory; +use React\Datagram\Socket as DatagramSocket; use BadMethodCallException; class Factory @@ -13,24 +12,16 @@ class Factory private $rawFactory; private $datagramFactory; - public function __construct(LoopInterface $loop, RawFactory $rawFactory = null, DatagramFactory $datagramFactory = null) + public function __construct(LoopInterface $loop) { - if ($rawFactory === null) { - $rawFactory = new RawFactory(); - } - - if ($datagramFactory === null) { - $datagramFactory = new DatagramFactory($loop); - } - - $this->rawFactory = $rawFactory; - $this->datagramFactory = $datagramFactory; + $this->loop = $loop; } public function createSender() { - $socket = $this->rawFactory->createUdp4(); - return $this->datagramFactory->createFromRaw($socket); + $stream = stream_socket_server('udp://0.0.0.0:0', $errno, $errstr, STREAM_SERVER_BIND); + + return new DatagramSocket($this->loop, $stream); } public function createReceiver($address) @@ -41,19 +32,21 @@ public function createReceiver($address) $parts = parse_url('udp://' . $address); - $socket = $this->rawFactory->createUdp4(); + $stream = stream_socket_server('udp://0.0.0.0:' . $parts['port'], $errno, $errstr, STREAM_SERVER_BIND); + + $socket = socket_import_stream($stream); // allow multiple processes to bind to the same address - $socket->setOption(SOL_SOCKET, SO_REUSEADDR, 1); + socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); // join multicast group and bind to port - $socket->setOption( + socket_set_option( + $socket, IPPROTO_IP, MCAST_JOIN_GROUP, array('group' => $parts['host'], 'interface' => 0) ); - $socket->bind('0.0.0.0:' . $parts['port']); - return $this->datagramFactory->createFromRaw($socket); + return new DatagramSocket($this->loop, $stream); } } From e86e8a086db9c75c0dd0c7f816d4c90380e9eab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Mar 2015 23:14:29 +0100 Subject: [PATCH 2/4] Improve documentation --- README.md | 12 +++--------- composer.json | 3 ++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 52cd884..d7ac0da 100644 --- a/README.md +++ b/README.md @@ -41,17 +41,11 @@ for the required multicast socket options and constants. These options are only available to the low level socket API (ext-sockets), not to the newer stream based networking API. -Because of this, this library depends on sockets based on `ext-sockets`, provided -via [clue/socket-react](https://github.com/clue/php-socket-react) -and [clue/socket-raw](https://github.com/clue/php-socket-raw). - For the most part, React PHP is built around the general purpose stream based API and has only somewhat limited support for the low level socket API. -The package [clue/socket-react](https://github.com/clue/php-socket-react) -works around this for the most part. -Simply put, you should try to avoid using the default `StreamSelectLoop`, -as it requires a workaround to poll the socket resources via a periodic timer -every 10ms. +Because of this, this library uses a workaround to create stream based sockets +and then sets the required socket options on its underlying low level socket +resource. This library also provides somewhat limited support for PHP 5.3. While this version lacks the required socket options and constants for listening diff --git a/composer.json b/composer.json index d2abf7b..d50d00f 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "clue/hexdump": "0.2.*" }, "suggest": { - "php": "PHP 5.4+ is required for listening on multicast addresses and IGMP announcements" + "php": "PHP 5.4+ is required for listening on multicast addresses (socket options to send IGMP announcements)", + "ext-sockets": "Low level socket API required for listening on multicast addresses (socket options to send IGMP announcements)" } } From 371095b9c1716c7e422b6d0710f83207deba0f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Mar 2015 23:24:31 +0100 Subject: [PATCH 3/4] Improve error reporting --- src/Factory.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Factory.php b/src/Factory.php index 50e197d..1bbf348 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -5,6 +5,7 @@ use React\EventLoop\LoopInterface; use React\Datagram\Socket as DatagramSocket; use BadMethodCallException; +use RuntimeException; class Factory { @@ -19,7 +20,10 @@ public function __construct(LoopInterface $loop) public function createSender() { - $stream = stream_socket_server('udp://0.0.0.0:0', $errno, $errstr, STREAM_SERVER_BIND); + $stream = @stream_socket_server('udp://0.0.0.0:0', $errno, $errstr, STREAM_SERVER_BIND); + if ($stream === false) { + throw new RuntimeException('Unable to create sending socket: ' . $errstr, $errno); + } return new DatagramSocket($this->loop, $stream); } @@ -32,20 +36,32 @@ public function createReceiver($address) $parts = parse_url('udp://' . $address); - $stream = stream_socket_server('udp://0.0.0.0:' . $parts['port'], $errno, $errstr, STREAM_SERVER_BIND); + $stream = @stream_socket_server('udp://0.0.0.0:' . $parts['port'], $errno, $errstr, STREAM_SERVER_BIND); + if ($stream === false) { + throw new RuntimeException('Unable to create receiving socket: ' . $errstr, $errno); + } $socket = socket_import_stream($stream); + if ($stream === false) { + throw new RuntimeException('Unable to access underlying socket resource'); + } // allow multiple processes to bind to the same address - socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); + $ret = socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); + if ($ret === false) { + throw new RuntimeException('Unable to enable SO_REUSEADDR'); + } // join multicast group and bind to port - socket_set_option( + $ret = socket_set_option( $socket, IPPROTO_IP, MCAST_JOIN_GROUP, array('group' => $parts['host'], 'interface' => 0) ); + if ($ret === false) { + throw new RuntimeException('Unable to join multicast group'); + } return new DatagramSocket($this->loop, $stream); } From 4feac45f03432f0fa1a6f2124bfef14f6fae8518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 24 Mar 2015 23:32:17 +0100 Subject: [PATCH 4/4] Explicitly check for socket_import_stream() Could be missing in a PHP 5.4+ installation without ext-sockets --- src/Factory.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Factory.php b/src/Factory.php index 1bbf348..2e9cc25 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -10,8 +10,6 @@ class Factory { private $loop; - private $rawFactory; - private $datagramFactory; public function __construct(LoopInterface $loop) { @@ -31,7 +29,10 @@ public function createSender() public function createReceiver($address) { if (!defined('MCAST_JOIN_GROUP')) { - throw new BadMethodCallException('MCAST_JOIN_GROUP not defined'); + throw new BadMethodCallException('MCAST_JOIN_GROUP not defined (requires PHP 5.4+)'); + } + if (!function_exists('socket_import_stream')) { + throw new BadMethodCallException('Function socket_import_stream missing (requires ext-sockets and PHP 5.4+)'); } $parts = parse_url('udp://' . $address);