forked from clue/reactphp-multicast
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFactory.php
More file actions
144 lines (130 loc) · 5.5 KB
/
Factory.php
File metadata and controls
144 lines (130 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
namespace Clue\React\Multicast;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use React\Datagram\Socket as DatagramSocket;
use BadMethodCallException;
use RuntimeException;
class Factory
{
/** @var LoopInterface */
private $loop;
/**
* The `Factory` is responsible for creating your [`SocketInterface`](#socketinterface) instances.
*
* This class takes an optional `LoopInterface|null $loop` parameter that can be used to
* pass the event loop instance to use for this object. You can use a `null` value
* here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
* This value SHOULD NOT be given unless you're sure you want to explicitly use a
* given event loop instance.
*
* @param ?LoopInterface $loop
*/
public function __construct($loop = null)
{
if ($loop !== null && !$loop instanceof LoopInterface) { // manual type check to support legacy PHP < 7.1
throw new \InvalidArgumentException('Argument #1 ($loop) expected null|React\EventLoop\LoopInterface');
}
$this->loop = $loop ?: Loop::get();
}
/**
* Creates a socket capable of sending outgoing multicast datagrams and receiving
* incoming unicast responses. It returns a [`SocketInterface`](#socketinterface) instance.
*
* ```php
* $socket = $factory->createSender();
*
* // send a multicast message to everybody listening on the given address
* $socket->send('hello?', '224.10.20.30:4050');
*
* // report incoming unicast replies
* $socket->on('message', function ($data, $address) {
* echo 'received ' . strlen($data) . ' bytes from ' . $address . PHP_EOL;
* });
* ```
*
* This method works on PHP versions as old as PHP 5.3 (and up), as its socket API has always been
* [level 1 multicast conformant](https://www.tldp.org/HOWTO/Multicast-HOWTO-2.html#ss2.2).
*
* @return \React\Datagram\SocketInterface
* @throws RuntimeException
*/
public function createSender()
{
$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);
}
/**
* Creates a socket capable of receiving incoming multicast datagrams and sending
* outgoing unicast or multicast datagrams. It returns a [`SocketInterface`](#socketinterface) instance.
*
* ```php
* $socket = $factory->createReceiver('224.10.20.30:4050');
*
* // report incoming multicast messages
* $socket->on('message', function ($data, $remote) use ($socket) {
* echo 'Sending back ' . strlen($data) . ' bytes to ' . $remote . PHP_EOL;
*
* // send a unicast reply to the remote
* $socket->send($data, $remote);
* });
* ```
*
* This method requires PHP 5.4+ and `ext-sockets`.
* Otherwise, it will throw a `BadMethodCallException`.
* This is a requirement because receiving multicast datagrams requires a
* [level 2 multicast conformant](https://www.tldp.org/HOWTO/Multicast-HOWTO-2.html#ss2.2)
* socket API.
* The required multicast socket options and constants have been added with PHP 5.4+.
* These options are only available to the low level socket API (ext-sockets), not
* to the newer stream based networking API.
*
* Internally, 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 is done because ReactPHP is built around the general purpose stream based API
* and has only somewhat limited support for the low level socket API.
*
* @param string $address
* @return \React\Datagram\SocketInterface
* @throws BadMethodCallException
* @throws RuntimeException
*/
public function createReceiver($address)
{
if (!defined('MCAST_JOIN_GROUP')) {
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);
$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
$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
$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);
}
}