Skip to content

Commit 4a32bf7

Browse files
committed
Initial commit
0 parents  commit 4a32bf7

14 files changed

+487
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/composer.lock
2+
/vendor

.travis.yml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
language: php
2+
php:
3+
- 5.3
4+
- 5.6
5+
- hhvm
6+
install:
7+
- composer install --prefer-source --no-interaction
8+
script:
9+
- phpunit --coverage-text

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Christian Lück
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is furnished
10+
to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# clue/multicast-react [![Build Status](https://travis-ci.org/clue/php-multicast-react.svg?branch=master)](https://travis-ci.org/clue/php-multicast-react)
2+
3+
Multicast UDP messages, built on top of [React PHP](http://reactphp.org/).
4+
5+
Multicast UDP messages are needed for quite a few (low-level) networking protocols.
6+
This library exposes a simple subset of commonly needed functionality for
7+
multicast networking through an easy to use API.
8+
9+
Among others, multicast networking is the basis for:
10+
11+
* MDNS (Multicast DNS)
12+
* HTTPU/HTTPMU (Multicast and Unicast UDP HTTP Messages)
13+
* UPNP/SSDP (Univeral Plug and Play / Simple Service Discovery Protocol).
14+
15+
> Note: This project is in early alpha stage! Feel free to report any issues you encounter.
16+
17+
## Quickstart example
18+
19+
Once [installed](#install), you can use the following code to create a simple
20+
echo server that listens for incoming multicast messages:
21+
22+
```php
23+
$loop = React\EventLoop\Factory::create();
24+
$factory = new Factory($loop);
25+
$socket = $factory->createReceiver('224.10.20.30:4050');
26+
27+
$socket->on('message', function ($data, $remote) use ($socket) {
28+
echo 'Sending back ' . strlen($data) . ' bytes to ' . $remote . PHP_EOL;
29+
$socket->send($data, $remote);
30+
});
31+
32+
$loop->run();
33+
```
34+
35+
See also the [examples](examples).
36+
37+
## Description
38+
39+
[PHP 5.4 added support](http://php.net/manual/en/migration54.global-constants.php)
40+
for the required multicast socket options and constants.
41+
42+
These options are only available to the low level socket API (ext-sockets), not
43+
to the newer stream based networking API.
44+
Because of this, this library depends on sockets based on `ext-sockets`, provided
45+
via [clue/socket-react](https://github.com/clue/php-socket-react)
46+
and [clue/socket-raw](https://github.com/clue/php-socket-raw).
47+
48+
For the most part, React PHP is built around the general purpose stream based API
49+
and has only somewhat limited support for the low level socket API.
50+
The package [clue/socket-react](https://github.com/clue/php-socket-react)
51+
works around this for the most part.
52+
Simply put, you should try to avoid using the default `StreamSelectLoop`,
53+
as it requires a workaround to poll the socket resources via a periodic timer
54+
every 10ms.
55+
56+
This library also provides somewhat limited support for PHP 5.3.
57+
While this version lacks the required socket options and constants for listening
58+
on multicast addresses for incoming messages, its underlying socket API is still
59+
[level 1 multicast conformant](http://www.tldp.org/HOWTO/Multicast-HOWTO-2.html#ss2.2).
60+
This means that it can be used for sending outgoing packages to multicast addresses
61+
and receiving incoming unicast responses in return.
62+
63+
## Install
64+
65+
The recommended way to install this library is [through composer](http://getcomposer.org). [New to composer?](http://getcomposer.org/doc/00-intro.md)
66+
67+
```JSON
68+
{
69+
"require": {
70+
"clue/multicast-react": "dev-master"
71+
}
72+
}
73+
```
74+
75+
## License
76+
77+
MIT

composer.json

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "clue/multicast-react",
3+
"description": "Simple async/streaming UDP multicast client and server",
4+
"keywords": ["multicast", "mcast", "udp", "ReactPHP", "async"],
5+
"homepage": "https://github.com/clue/php-multicast-react",
6+
"license": "MIT",
7+
"authors": [
8+
{
9+
"name": "Christian Lück",
10+
"email": "[email protected]"
11+
}
12+
],
13+
"autoload": {
14+
"psr-4": { "Clue\\React\\Multicast\\": "src/" }
15+
},
16+
"require": {
17+
"php": ">=5.3",
18+
"react/event-loop": "~0.3.0|~0.4.0",
19+
"react/promise": "~1.0|~2.0",
20+
"clue/socket-react": "~0.3.0"
21+
},
22+
"require-dev": {
23+
"clue/hexdump": "0.2.*"
24+
},
25+
"suggest": {
26+
"php": "PHP 5.4+ is required for listening on multicast addresses and IGMP announcements"
27+
}
28+
}

examples/dump-received.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
/**
3+
* Simple receiving example socket server that prints a hexdump of every message received
4+
*
5+
* Accepts a single argument socket address (defaults to 224.10.20.30:12345)
6+
*/
7+
8+
use Clue\React\Multicast\Factory;
9+
use Clue\Hexdump\Hexdump;
10+
11+
require __DIR__ . '/../vendor/autoload.php';
12+
13+
$address = '224.10.20.30:12345'; // random test address
14+
//$address = '239.255.255.250:1900'; // UPNP SSDP (simple service discovery protocol)
15+
16+
// use either above default address or the one given as first argument to this script
17+
if (isset($argv[1])) {
18+
$address = $argv[1];
19+
}
20+
21+
$loop = React\EventLoop\Factory::create();
22+
$factory = new Factory($loop);
23+
$socket = $factory->createReceiver($address);
24+
$hex = new Hexdump();
25+
26+
$socket->on('message', function ($data, $remote) use ($hex) {
27+
echo 'Received from ' . $remote . PHP_EOL;
28+
echo $hex->dump($data) . PHP_EOL;
29+
});
30+
31+
$loop->run();

examples/echo-received.php

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* Simple receiving socket example server that sends back every message it received
4+
*
5+
* Accepts a single argument socket address (defaults to 224.10.20.30:12345)
6+
*/
7+
8+
use Clue\React\Multicast\Factory;
9+
10+
require __DIR__ . '/../vendor/autoload.php';
11+
12+
$address = '224.10.20.30:12345'; // random test address
13+
//$address = '239.255.255.250:1900'; // UPNP SSDP (simple service discovery protocol)
14+
15+
// use either above default address or the one given as first argument to this script
16+
if (isset($argv[1])) {
17+
$address = $argv[1];
18+
}
19+
20+
$loop = React\EventLoop\Factory::create();
21+
$factory = new Factory($loop);
22+
$socket = $factory->createReceiver($address);
23+
24+
$socket->on('message', function ($data, $remote) use ($socket) {
25+
echo 'Sending back ' . strlen($data) . ' bytes to ' . $remote . PHP_EOL;
26+
$socket->send($data, $remote);
27+
});
28+
29+
$loop->run();

examples/send-once.php

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Simple sending socket example that exits after sending a single message
4+
*
5+
* Accepts a single argument socket address (defaults to 224.10.20.30:12345)
6+
*/
7+
8+
use Clue\React\Multicast\Factory;
9+
10+
require __DIR__ . '/../vendor/autoload.php';
11+
12+
$address = isset($argv[1]) ? $argv[1] : '224.10.20.30:12345';
13+
14+
$loop = React\EventLoop\Factory::create();
15+
$factory = new Factory($loop);
16+
$sender = $factory->createSender();
17+
18+
// do not wait for incoming messages
19+
$sender->pause();
20+
21+
// send a simple message
22+
$message = 'ping 123';
23+
$sender->send($message, $address);
24+
25+
$loop->run();

examples/send-wait.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Simple sending socket example that sends a single message and then prints a hexdump of every response it receives
4+
*
5+
* Accepts a single argument socket address (defaults to 224.10.20.30:12345)
6+
*/
7+
8+
use Clue\React\Multicast\Factory;
9+
use Clue\Hexdump\Hexdump;
10+
11+
require __DIR__ . '/../vendor/autoload.php';
12+
13+
$address = isset($argv[1]) ? $argv[1] : '224.10.20.30:12345';
14+
15+
$loop = React\EventLoop\Factory::create();
16+
$factory = new Factory($loop);
17+
$sender = $factory->createSender();
18+
$hex = new Hexdump();
19+
20+
// print a hexdump of every message received
21+
$sender->on('message', function ($data, $remote) use ($hex) {
22+
echo 'Received from ' . $remote . PHP_EOL;
23+
echo $hex->dump($data) . PHP_EOL;
24+
});
25+
26+
// send a simple message
27+
$message = 'ping 123';
28+
$sender->send($message, $address);
29+
30+
$loop->run();

examples/ssdp.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
/**
3+
* UPNP simple service discovery protocol (SSDP)
4+
*/
5+
6+
use Clue\React\Multicast\Factory;
7+
8+
require __DIR__ . '/../vendor/autoload.php';
9+
10+
$address = '239.255.255.250:1900';
11+
12+
$loop = React\EventLoop\Factory::create();
13+
$factory = new Factory($loop);
14+
$sender = $factory->createSender();
15+
16+
// dump all incoming messages
17+
$sender->on('message', function ($data, $remote) use ($hex) {
18+
echo 'Received from ' . $remote . PHP_EOL;
19+
echo $data . PHP_EOL;
20+
});
21+
22+
// stop waiting for incoming messages after 3.0s (MX is 2s)
23+
$loop->addTimer(3.0, function () use ($sender) {
24+
$sender->pause();
25+
});
26+
27+
// send a discovery message that all upnp/ssdp aware devices will respond to
28+
$data = "M-SEARCH * HTTP/1.1\r\n";
29+
$data .= "HOST: " . $address . "\r\n";
30+
$data .= "MAN: \"ssdp:discover\"\r\n";
31+
$data .= "MX: 2\r\n";
32+
$data .= "ST: ssdp:all\r\n";
33+
$data .= "\r\n";
34+
$sender->send($data, $address);
35+
36+
$loop->run();

phpunit.xml.dist

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<phpunit bootstrap="tests/bootstrap.php"
4+
colors="true"
5+
convertErrorsToExceptions="true"
6+
convertNoticesToExceptions="true"
7+
convertWarningsToExceptions="true"
8+
>
9+
<testsuites>
10+
<testsuite name="Multicast React Test Suite">
11+
<directory>./tests/</directory>
12+
</testsuite>
13+
</testsuites>
14+
<filter>
15+
<whitelist>
16+
<directory>./src/</directory>
17+
</whitelist>
18+
</filter>
19+
</phpunit>

src/Factory.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Clue\React\Multicast;
4+
5+
use React\EventLoop\LoopInterface;
6+
use Socket\React\Datagram\Factory as DatagramFactory;
7+
use Socket\Raw\Factory as RawFactory;
8+
9+
class Factory
10+
{
11+
private $loop;
12+
private $rawFactory;
13+
private $datagramFactory;
14+
15+
public function __construct(LoopInterface $loop, RawFactory $rawFactory = null, DatagramFactory $datagramFactory = null)
16+
{
17+
if ($rawFactory === null) {
18+
$rawFactory = new RawFactory();
19+
}
20+
21+
if ($datagramFactory === null) {
22+
$datagramFactory = new DatagramFactory($loop);
23+
}
24+
25+
$this->rawFactory = $rawFactory;
26+
$this->datagramFactory = $datagramFactory;
27+
}
28+
29+
public function createSender()
30+
{
31+
$socket = $this->rawFactory->createUdp4();
32+
return $this->datagramFactory->createFromRaw($socket);
33+
}
34+
35+
public function createReceiver($address)
36+
{
37+
if (!defined('MCAST_JOIN_GROUP')) {
38+
throw new BadMethodCallException('MCAST_JOIN_GROUP not defined');
39+
}
40+
41+
$parts = parse_url('udp://' . $address);
42+
43+
$socket = $this->rawFactory->createUdp4();
44+
45+
// allow multiple processes to bind to the same address
46+
$socket->setOption(SOL_SOCKET, SO_REUSEADDR, 1);
47+
48+
// join multicast group and bind to port
49+
$socket->setOption(
50+
IPPROTO_IP,
51+
MCAST_JOIN_GROUP,
52+
array('group' => $parts['host'], 'interface' => 0)
53+
);
54+
$socket->bind('0.0.0.0:' . $parts['port']);
55+
56+
return $this->datagramFactory->createFromRaw($socket);
57+
}
58+
}

0 commit comments

Comments
 (0)