Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
namespace React\Socket;
4
 
5
use Evenement\EventEmitter;
6
use React\EventLoop\LoopInterface;
7
use InvalidArgumentException;
8
use RuntimeException;
9
 
10
/**
11
 * The `TcpServer` class implements the `ServerInterface` and
12
 * is responsible for accepting plaintext TCP/IP connections.
13
 *
14
 * ```php
15
 * $server = new React\Socket\TcpServer(8080, $loop);
16
 * ```
17
 *
18
 * Whenever a client connects, it will emit a `connection` event with a connection
19
 * instance implementing `ConnectionInterface`:
20
 *
21
 * ```php
22
 * $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
23
 *     echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
24
 *     $connection->write('hello there!' . PHP_EOL);
25
 *     …
26
 * });
27
 * ```
28
 *
29
 * See also the `ServerInterface` for more details.
30
 *
31
 * @see ServerInterface
32
 * @see ConnectionInterface
33
 */
34
final class TcpServer extends EventEmitter implements ServerInterface
35
{
36
    private $master;
37
    private $loop;
38
    private $listening = false;
39
 
40
    /**
41
     * Creates a plaintext TCP/IP socket server and starts listening on the given address
42
     *
43
     * This starts accepting new incoming connections on the given address.
44
     * See also the `connection event` documented in the `ServerInterface`
45
     * for more details.
46
     *
47
     * ```php
48
     * $server = new React\Socket\TcpServer(8080, $loop);
49
     * ```
50
     *
51
     * As above, the `$uri` parameter can consist of only a port, in which case the
52
     * server will default to listening on the localhost address `127.0.0.1`,
53
     * which means it will not be reachable from outside of this system.
54
     *
55
     * In order to use a random port assignment, you can use the port `0`:
56
     *
57
     * ```php
58
     * $server = new React\Socket\TcpServer(0, $loop);
59
     * $address = $server->getAddress();
60
     * ```
61
     *
62
     * In order to change the host the socket is listening on, you can provide an IP
63
     * address through the first parameter provided to the constructor, optionally
64
     * preceded by the `tcp://` scheme:
65
     *
66
     * ```php
67
     * $server = new React\Socket\TcpServer('192.168.0.1:8080', $loop);
68
     * ```
69
     *
70
     * If you want to listen on an IPv6 address, you MUST enclose the host in square
71
     * brackets:
72
     *
73
     * ```php
74
     * $server = new React\Socket\TcpServer('[::1]:8080', $loop);
75
     * ```
76
     *
77
     * If the given URI is invalid, does not contain a port, any other scheme or if it
78
     * contains a hostname, it will throw an `InvalidArgumentException`:
79
     *
80
     * ```php
81
     * // throws InvalidArgumentException due to missing port
82
     * $server = new React\Socket\TcpServer('127.0.0.1', $loop);
83
     * ```
84
     *
85
     * If the given URI appears to be valid, but listening on it fails (such as if port
86
     * is already in use or port below 1024 may require root access etc.), it will
87
     * throw a `RuntimeException`:
88
     *
89
     * ```php
90
     * $first = new React\Socket\TcpServer(8080, $loop);
91
     *
92
     * // throws RuntimeException because port is already in use
93
     * $second = new React\Socket\TcpServer(8080, $loop);
94
     * ```
95
     *
96
     * Note that these error conditions may vary depending on your system and/or
97
     * configuration.
98
     * See the exception message and code for more details about the actual error
99
     * condition.
100
     *
101
     * Optionally, you can specify [socket context options](https://www.php.net/manual/en/context.socket.php)
102
     * for the underlying stream socket resource like this:
103
     *
104
     * ```php
105
     * $server = new React\Socket\TcpServer('[::1]:8080', $loop, array(
106
     *     'backlog' => 200,
107
     *     'so_reuseport' => true,
108
     *     'ipv6_v6only' => true
109
     * ));
110
     * ```
111
     *
112
     * Note that available [socket context options](https://www.php.net/manual/en/context.socket.php),
113
     * their defaults and effects of changing these may vary depending on your system
114
     * and/or PHP version.
115
     * Passing unknown context options has no effect.
116
     * The `backlog` context option defaults to `511` unless given explicitly.
117
     *
118
     * @param string|int    $uri
119
     * @param LoopInterface $loop
120
     * @param array         $context
121
     * @throws InvalidArgumentException if the listening address is invalid
122
     * @throws RuntimeException if listening on this address fails (already in use etc.)
123
     */
124
    public function __construct($uri, LoopInterface $loop, array $context = array())
125
    {
126
        $this->loop = $loop;
127
 
128
        // a single port has been given => assume localhost
129
        if ((string)(int)$uri === (string)$uri) {
130
            $uri = '127.0.0.1:' . $uri;
131
        }
132
 
133
        // assume default scheme if none has been given
134
        if (\strpos($uri, '://') === false) {
135
            $uri = 'tcp://' . $uri;
136
        }
137
 
138
        // parse_url() does not accept null ports (random port assignment) => manually remove
139
        if (\substr($uri, -2) === ':0') {
140
            $parts = \parse_url(\substr($uri, 0, -2));
141
            if ($parts) {
142
                $parts['port'] = 0;
143
            }
144
        } else {
145
            $parts = \parse_url($uri);
146
        }
147
 
148
        // ensure URI contains TCP scheme, host and port
149
        if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
150
            throw new \InvalidArgumentException('Invalid URI "' . $uri . '" given');
151
        }
152
 
153
        if (false === \filter_var(\trim($parts['host'], '[]'), \FILTER_VALIDATE_IP)) {
154
            throw new \InvalidArgumentException('Given URI "' . $uri . '" does not contain a valid host IP');
155
        }
156
 
157
        $this->master = @\stream_socket_server(
158
            $uri,
159
            $errno,
160
            $errstr,
161
            \STREAM_SERVER_BIND | \STREAM_SERVER_LISTEN,
162
            \stream_context_create(array('socket' => $context + array('backlog' => 511)))
163
        );
164
        if (false === $this->master) {
165
            throw new \RuntimeException('Failed to listen on "' . $uri . '": ' . $errstr, $errno);
166
        }
167
        \stream_set_blocking($this->master, false);
168
 
169
        $this->resume();
170
    }
171
 
172
    public function getAddress()
173
    {
174
        if (!\is_resource($this->master)) {
175
            return null;
176
        }
177
 
178
        $address = \stream_socket_get_name($this->master, false);
179
 
180
        // check if this is an IPv6 address which includes multiple colons but no square brackets
181
        $pos = \strrpos($address, ':');
182
        if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
183
            $address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
184
        }
185
 
186
        return 'tcp://' . $address;
187
    }
188
 
189
    public function pause()
190
    {
191
        if (!$this->listening) {
192
            return;
193
        }
194
 
195
        $this->loop->removeReadStream($this->master);
196
        $this->listening = false;
197
    }
198
 
199
    public function resume()
200
    {
201
        if ($this->listening || !\is_resource($this->master)) {
202
            return;
203
        }
204
 
205
        $that = $this;
206
        $this->loop->addReadStream($this->master, function ($master) use ($that) {
207
            $newSocket = @\stream_socket_accept($master, 0);
208
            if (false === $newSocket) {
209
                $that->emit('error', array(new \RuntimeException('Error accepting new connection')));
210
 
211
                return;
212
            }
213
            $that->handleConnection($newSocket);
214
        });
215
        $this->listening = true;
216
    }
217
 
218
    public function close()
219
    {
220
        if (!\is_resource($this->master)) {
221
            return;
222
        }
223
 
224
        $this->pause();
225
        \fclose($this->master);
226
        $this->removeAllListeners();
227
    }
228
 
229
    /** @internal */
230
    public function handleConnection($socket)
231
    {
232
        $this->emit('connection', array(
233
            new Connection($socket, $this->loop)
234
        ));
235
    }
236
}