Index.xyl 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <overlay xmlns="http://hoa-project.net/xyl/xylophone">
  3. <yield id="chapter">
  4. <p>The WebSocket protocol allows a <strong>bidirectional</strong> and
  5. <strong>full-duplex</strong> communication between a client and a server. The
  6. <code>Hoa\Websocket</code> library allows to create WebSocket
  7. <strong>servers</strong> and <strong>clients</strong>.</p>
  8. <h2 id="Table_of_contents">Table of contents</h2>
  9. <tableofcontents id="main-toc" />
  10. <h2 id="Introduction" for="main-toc">Introduction</h2>
  11. <p>The WebSocket protocol is standardized in the
  12. <a href="https://tools.ietf.org/html/rfc6455">RFC6455</a>. It allows a client
  13. and a server to communicate between each other. This communication is
  14. <strong>bidirectional</strong>, it means that the client can send messages to
  15. the server, and <strong>vice versa</strong>. The server does not only send
  16. responses, it is able to send messages <strong>spontaneously</strong>. This
  17. changes the classical Web approach with its HTTP protocol. The WebSocket
  18. protocol is also <strong>full-duplex</strong>, it means that data are
  19. exchanged <strong>simultaneously</strong> in <strong>both</strong> directions:
  20. While a data is currently transiting from the server to the client, the latter
  21. is able to also send a message to the server. Consequently, the WebSocket
  22. protocol allows a high <strong>interactivity</strong> between both client and
  23. server. The client will often be a browser. It is important to notice that the
  24. URI schema (see the <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>)
  25. of the WebSocket is <code>ws://</code>.</p>
  26. <p>We can ask ourselves what are the differences between WebSocket and
  27. EventSource. Both solutions are fundamentaly differents: WebSocket allows a
  28. bidirectional and full-duplex communication, whereas EventSource is a
  29. technology based on the <strong>HTTP protocol</strong> and provides only a
  30. <strong>unidirectional</strong> communication. For this kind of usage, an
  31. EventSource server is more likely to be light, simple and it is designed to be
  32. robust regarding disconnections (see <a href="@hack:chapter=Eventsource">the
  33. <code>Hoa\Eventsource</code> library</a>).</p>
  34. <p>The WebSocket protocol starts by a handshake in order to start the
  35. communication and exchange messages formatted as frames.</p>
  36. <h3 id="Handshake_and_challenge" for="main-toc">Handshake and challenge</h3>
  37. <p>To <strong>start</strong> a communication with the WebSocket protocol, the
  38. client must send an HTTP request to the server asking to change the protocol.
  39. In this request, the client inserts a <strong>challenge</strong>, kind of
  40. enigma, that the server must solve. If it succeed, the communication will
  41. start.</p>
  42. <p>Starting by an HTTP request is not anodyne. It allows the WebSocket
  43. protocol to use the same network path as HTTP requests, and so, for example,
  44. traverse proxys, firewalls etc. This also eases the deployment of this
  45. protocol: No need to open a dedicated port, no need to have a specific
  46. configuration on the server etc. Finally, it allows to use a
  47. <strong>secured</strong> connection thought TLS. In this case, we will use the
  48. <code>wss://</code> URI schema.</p>
  49. <h3 id="Frame_and_opcode" for="main-toc">Frame and opcode</h3>
  50. <p>Messages between the client and the server are not exchanged verbatim.
  51. Actually, the message is <strong>formatted</strong> as a frame: A
  52. <strong>packet</strong> of bits with a specific design. In this case, we will
  53. find information about the type of the message, its length, some
  54. verification codes etc. We will find an explanatory schema in the
  55. <a href="https://tools.ietf.org/html/rfc6455#section-5.2">section 5.2, Base
  56. Framing Protocol</a> from the protocol specification for the most curious of
  57. you.</p>
  58. <p>The <strong>type</strong> of the message is called opcode. This is the most
  59. important information. We will find this term in the chapter many times. The
  60. <code>Hoa\Websocket\Connection</code> class provides constants to represent
  61. each opcode in order to simplify their use. This class ensures the protocol
  62. <strong>support</strong>.</p>
  63. <h3 id="History" for="main-toc">History</h3>
  64. <p>There is two versions of the WebSocket protocol in the nature: The standard
  65. version and the non-standard version. The standard version is the one
  66. described in the RFC6455. The last non-standard version has the following
  67. nickname: <em>draft-ietf-hybi-thewebsocketprotocol-00</em> (or
  68. <em>draft-hixie-thewebsocketprotocol-76</em>), or
  69. <a href="https://tools.ietf.org/wg/hybi/draft-ietf-hybi-thewebsocketprotocol/">Hybi00</a>
  70. for short. This non-standard version has many important security issues but it
  71. is in use in some languages such as Flash. Fortunately, this version
  72. disappears more and more and is replaced by the RFC6455.</p>
  73. <p>The <code>Hoa\Websocket</code> library supports both versions. It allows
  74. clients supporting different versions of the protocol to communicate between
  75. each other.</p>
  76. <h2 id="Write_a_server" for="main-toc">Write a server</h2>
  77. <p>The <code>Hoa\Websocket\Server</code> class allows to write a server
  78. <strong>manipulating</strong> the WebSocket protocol. This class inherits
  79. from <code>Hoa\Websocket\Connection</code>. The <strong>communication</strong>
  80. is based on a socket server. We will use the <code>Hoa\Socket\Server</code>
  81. class (from <a href="@hack:chapter=Socket">the <code>Hoa\Socket</code>
  82. library</a>) to fill this role.</p>
  83. <p>The WebSocket protocol runs on TCP, thus we will start a local WebSocket
  84. server on the 8889 port:</p>
  85. <pre><code class="language-php">$server = new Hoa\Websocket\Server(
  86. new Hoa\Socket\Server('tcp://127.0.0.1:8889')
  87. );</code></pre>
  88. <p>However, we can use the <code>ws://127.0.0.1:8889</code> URI directly
  89. instead of <code>tcp://127.0.0.1:8889</code>. This is an advantage when we
  90. use <code>wss://</code> for a secured connection because
  91. <code>Hoa\Websocket</code> will know that the connection will need to be
  92. secured and everything will be done for you automatically. Manipulating TLS,
  93. enabling encryption for certain connections etc. will not be required.
  94. Thus:</p>
  95. <pre><code class="language-php">$server = new Hoa\Websocket\Server(
  96. new Hoa\Socket\Server('ws://127.0.0.1:8889')
  97. );</code></pre>
  98. <p>Now, let's see how to <strong>interact</strong> with this server.</p>
  99. <h3 id="Listeners" for="main-toc">Listeners</h3>
  100. <p>The <code>Hoa\Websocket\Connection</code> class provides six listeners:</p>
  101. <ul>
  102. <li><code>open</code>, when a connection is <code>opened</code>,</li>
  103. <li><code>message</code>, when a <strong>message</strong> is received,</li>
  104. <li><code>binary-message</code>, when a <strong>binary</strong> message is
  105. received,</li>
  106. <li><code>ping</code>, when a <strong>ping</strong> is received,</li>
  107. <li><code>error</code>, when an <strong>error</strong> has been
  108. triggered,</li>
  109. <li><code>close</code>, when a connection is <strong>closing</strong>.</li>
  110. </ul>
  111. <p>For the <code>message</code> and <code>binary-message</code> listeners,
  112. there is only one associated datum: <code>message</code>, which contains
  113. without any surprise the received <code>message</code>.</p>
  114. <p>For the <code>ping</code> listener, we will also find the
  115. <code>message</code> datum. Notice that the pong is
  116. <strong>automatically</strong> sent before the listener is fired.</p>
  117. <p>For the <code>error</code> listener, we will find the
  118. <code>exception</code> datum, which contains an <strong>exception</strong>
  119. (not necessary <code>Hoa\Websocket\Exception\Exception</code>, this can be for
  120. example <code>Hoa\Socket\Exception</code>). The listener is fired after that
  121. the connection has been closed.</p>
  122. <p>The <code>close</code> listener has two data: <code>code</code> for the
  123. <strong>code</strong> and <code>reason</code> explains the
  124. <strong>reason</strong> of this closing with a short message. We will find
  125. standard closing codes through the <code>CLOSE_<em>*</em></code> constants on
  126. the <code>Hoa\Websocket\Connection</code> class. For example,
  127. <code>Hoa\Websocket\Connection::CLOSE_NORMAL</code> represents a normal
  128. connection closing, without error, whereas
  129. <code>Hoa\Websocket\Connection::CLOSE_MESSAGE_ERROR</code> represents a
  130. connection closing because of a malformed message. This listener is fired
  131. after that the connection has been closed.</p>
  132. <h3 id="Send_messages" for="main-toc">Send messages</h3>
  133. <p>Let's complete our example to, in the <code>message</code> listener,
  134. <strong>send back</strong> to the client all received messages in order to
  135. create an <strong>echo</strong>. We will use the
  136. <code>Hoa\Websocket\Connection::send</code> method. Once our listener is
  137. registered, we can start the server thanks to the
  138. <code>Hoa\Websocket\Connection::run</code> method. Thus:</p>
  139. <pre data-line="5"><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  140. $data = $bucket->getData();
  141. echo 'message: ', $data['message'], "\n";
  142. $bucket->getSource()->send($data['message']);
  143. return;
  144. });
  145. $server->run();</code></pre>
  146. <p>Now, let's test our server by creating a very simple HTML client:</p>
  147. <pre data-line="6,39"><code class="language-markup">&amp;lt;input type="text" id="input" placeholder="Message…" />
  148. &amp;lt;hr />
  149. &amp;lt;pre id="output">&amp;lt;/pre>
  150. &amp;lt;script>
  151. var host = 'ws://127.0.0.1:8889';
  152. var socket = null;
  153. var input = document.getElementById('input');
  154. var output = document.getElementById('output');
  155. var print = function (message) {
  156. var samp = document.createElement('samp');
  157. samp.innerHTML = message + '\n';
  158. output.appendChild(samp);
  159. return;
  160. };
  161. input.addEventListener('keyup', function (evt) {
  162. if (13 === evt.keyCode) {
  163. var msg = input.value;
  164. if (!msg) {
  165. return;
  166. }
  167. try {
  168. socket.send(msg);
  169. input.value = '';
  170. input.focus();
  171. } catch (e) {
  172. console.log(e);
  173. }
  174. return;
  175. }
  176. });
  177. try {
  178. socket = new WebSocket(host);
  179. socket.onopen = function () {
  180. print('connection is opened');
  181. input.focus();
  182. return;
  183. };
  184. socket.onmessage = function (msg) {
  185. print(msg.data);
  186. return;
  187. };
  188. socket.onclose = function () {
  189. print('connection is closed');
  190. return;
  191. };
  192. } catch (e) {
  193. console.log(e);
  194. }
  195. &amp;lt;/script></code></pre>
  196. <p>At line 6, we declare the address of the WebSocket server by using the
  197. <code>ws</code> protocol. At line 45, we use the
  198. <a href="https://developer.mozilla.org/docs/WebSockets/WebSockets_reference/WebSocket"><code>WebSocket</code>
  199. object</a> and we attach listeners, strongly similar to those of
  200. <code>Hoa\Websocket\Connection</code>!</p>
  201. <p>To test, we just have to start the server:</p>
  202. <pre><code class="language-shell">$ php Server.php</code></pre>
  203. <p>Then, open the client in your favorite browser. Each message sent to the
  204. server returns identically, we have an echo.</p>
  205. <h3 id="Broadcast_messages" for="main-toc">Broadcast messages</h3>
  206. <p>So far, the client speaks with the server and the server replies, but this
  207. is only a <strong>dialog</strong>. However, the server has all connections in
  208. memory. We are able to <strong>broadcast</strong> a message to all connected
  209. clients. So, we will use the <code>Hoa\Websocket\Connection::broadcast</code>
  210. method that will send a message to all other connected clients. Thus:</p>
  211. <pre data-line="5"><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  212. $data = $bucket->getData();
  213. echo 'message: ', $data['message'], "\n";
  214. $bucket->getSource()->broadcast($data['message']);
  215. return;
  216. });</code></pre>
  217. <p><em lang="fr">Et voilà !</em> Restart the server and open the client many
  218. times. Each message we send is broadcasted to <strong>all</strong> other
  219. clients! Our example is now an <strong>instant messaging</strong> tool.</p>
  220. <p>We have to understand that the <code>Hoa\Socket\Server</code> socket server
  221. works with <strong>nodes</strong>, it means an object that represents an
  222. opened connection. In this listener, to know the <strong>current</strong> node
  223. that has fired it, we have to call the
  224. <code>Hoa\Websocket\Connection::getConnection</code> method to get the socket
  225. server, and then, the <code>Hoa\Socket\Server::getCurrentNode</code> method.
  226. Similarly, we have the <code>Hoa\Socket\Server::getNodes</code> to get a list
  227. of all nodes. The <code>Hoa\Websocket\Connection::broadcast</code> method is
  228. in fact implemented in the <code>Hoa\Socket</code> library and hides this
  229. complexity. It is preferable to use this method for
  230. <strong>performance</strong> and compatibility reasons.</p>
  231. <h3 id="Closing" for="main-toc">Closing</h3>
  232. <p>To <strong>close</strong> a connection with a client, we use the
  233. <code>Hoa\Websocket\Connection::close</code> method. This latter is very
  234. similar to <code>Hoa\Websocket\Connection::send</code>. Its arguments are:</p>
  235. <ul>
  236. <li><code>code</code>: The closing <strong>code</strong>, see the
  237. <code>Hoa\Websocket\Connection::CLOSE_<em>*</em></code> constants
  238. (<code>CLOSE_NORMAL</code> by default),</li>
  239. <li><code>reason</code>: A short message explaining the
  240. <strong>reason</strong> of the closing (<code>null</code> by default),</li>
  241. <li><code>node</code>: The <strong>node</strong> that will close the
  242. connection (<code>null</code>, by default, indicates the current node).</li>
  243. </ul>
  244. <p>For example, when we will receive the message <code>I love you</code>, we
  245. will close the connection by explaining why, else we will only do an echo of
  246. the message:</p>
  247. <pre><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  248. $data = $bucket->getData();
  249. if ('I love you' === $data['message']) {
  250. $bucket->getSource()->close(
  251. Hoa\Websocket\Connection::CLOSE_NORMAL,
  252. 'Thank you but my heart is already taken, bye bye!'
  253. );
  254. return;
  255. }
  256. $bucket->getSource()->send($data['message']);
  257. return;
  258. });</code></pre>
  259. <p>We can modify our client in order to show the code and the reason of the
  260. closing:</p>
  261. <pre><code class="language-javascript"> socket.onclose = function (e) {
  262. print(
  263. 'connection is closed (' + e.code + ' ' +
  264. (e.reason || '—no reason—') + ')'
  265. );
  266. return;
  267. };</code></pre>
  268. <p>It is highly recommended to <strong>always</strong> use this method to
  269. close a connection instead of closing the TCP connection directly.</p>
  270. <h2 id="Message" for="main-toc">Message</h2>
  271. <p>There is two ways to send a message: Either in one piece if we have the
  272. message <strong>entirely</strong>, or in <strong>several</strong> pieces. Our
  273. message can also contain other things than text, for example
  274. <strong>binary</strong> data. In this case, we speak about a binary
  275. message.</p>
  276. <h3 id="Fragmentation" for="main-toc">Fragmentation</h3>
  277. <p>To send a message in one block, we use the
  278. <code>Hoa\Websocket\Connection::send</code> method as seen in previous
  279. sections:</p>
  280. <pre><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  281. $bucket->getSource()->send('foobar');
  282. return;
  283. });</code></pre>
  284. <p>In fact, this method has four arguments:</p>
  285. <ul>
  286. <li><code>message</code>: The <strong>message</strong>,</li>
  287. <li><code>node</code>: The <strong>node</strong> that will send the message
  288. (<code>null</code>, by default, indicates the current node),</li>
  289. <li><code>opcode</code>: The <strong>opcode</strong>, it means the type of
  290. the frame (<code>Hoa\Websocket\Connection::OPCODE_TEXT_FRAME</code> by
  291. default),</li>
  292. <li><code>fin</code>: Indicates whether the message is <strong>over</strong>
  293. or not (<code>true</code> by default).</li>
  294. </ul>
  295. <p>We will use all these arguments by trying to send a
  296. <strong>fragmented</strong> message.</p>
  297. <p>In our example, we have sent a message entirely, in one piece, which is the
  298. most usual way to do. If we send a very long message, we will also use this
  299. method. However, it is possible that we have the message piece by piece, and
  300. then, we are unable to send it entirely. For example, if a message, with an
  301. <strong>undetermined</strong> length, is read on a stream, and we would
  302. like to send it to a client, we won't wait to get the full message: We will
  303. send each piece directly to the client. In this case, we speak about
  304. fragmented messages.</p>
  305. <p>We will use the next opcodes: <code>OPCODE_TEXT_FRAME</code> for the
  306. <strong>first</strong> fragment, and then
  307. <code>OPCODE_CONTINUATION_FRAME</code> for the <strong>next ones</strong>.
  308. Each time, we will specify that the message is not <strong>over</strong>
  309. thanks to the <code>fin</code> argument which will be set to
  310. <code>false</code>, except for the last fragment where <code>fin</code> will
  311. be <code>true</code>.</p>
  312. <p>The final user behind the client won't receive fragmented messages, but one
  313. message in one block once the last fragment will be received. From the server
  314. side, this will avoid to overload the memory with “transit” data, and also to
  315. overload the network with a heavy message. We send data as soon as we get
  316. them, and it is part to the client to <strong>reconstitute</strong> the
  317. message. The server acts in the same way when it receives a fragment message.
  318. Between both fragments, the server is able to compute other tasks. So, it is
  319. more interesting to use fragments instead of bufferizing the message.</p>
  320. <p>Let's see an example. We will send the <code>foobarbaz</code> fragmented
  321. message in three pieces. We can imagine we read these data on a socket for
  322. example, and that the data are coming as soon as they are available. Thus:</p>
  323. <pre><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  324. $self = $bucket->getSource();
  325. $self->send(
  326. 'foo',
  327. null,
  328. Hoa\Websocket\Connection::OPCODE_TEXT_FRAME,
  329. false // not the end…
  330. );
  331. echo 'sent foo', "\n";
  332. sleep(1);
  333. $self->send(
  334. 'bar',
  335. null,
  336. Hoa\Websocket\Connection::OPCODE_CONTINUATION_FRAME,
  337. false // not the end…
  338. );
  339. echo 'sent bar', "\n";
  340. sleep(1);
  341. $self->send(
  342. 'baz',
  343. null,
  344. Hoa\Websocket\Connection::OPCODE_CONTINUATION_FRAME,
  345. true // the end!
  346. );
  347. echo 'sent baz, over', "\n";
  348. return;
  349. });</code></pre>
  350. <p>The <a href="http://php.net/sleep"><code>sleep</code></a> instructions
  351. allow to emulate a network latency or something equivalent. Each time the
  352. <code>send</code> method is called, data are <strong>effectively</strong> sent
  353. to the client, there is no buffer on the server side.</p>
  354. <h3 id="Encoding" for="main-toc">Encoding</h3>
  355. <p>All exchanged messages must be in the <strong>UTF-8</strong> format (see
  356. the <a href="https://tools.ietf.org/html/rfc3629">RFC3629</a>). If a message
  357. from the client is not consistent with this format,
  358. <code>Hoa\Websocket\Connection</code> will close the connection appropriately
  359. with the <code>Hoa\Websocket\Connection::CLOSE_MESSAGE_ERROR</code> code, we
  360. have nothing special to do. Consequently, all the received messages in our
  361. listeners respect this encoding.</p>
  362. <p>However, <code>Hoa\Websocket\Connection</code> verifies that messages
  363. <strong>going to</strong> the client are consistent with this format. If the
  364. encoding is not appropriated, then a
  365. <code>Hoa\Websocket\Exception\InvalidMessage</code> exception will be thrown,
  366. which will close the connection and fire the <code>error</code> listener if it
  367. is not captured earlier.</p>
  368. <h3 id="Binary" for="main-toc">Binary</h3>
  369. <p>It is also possible to send <strong>binary</strong> data, more compacted
  370. than textual data. We speak about binary messages. We will still use the
  371. <code>Hoa\Websocket\Connection::send</code> method but with the
  372. <code>OPCODE_BINARY_FRAME</code> opcode. This makes sense only in the
  373. <code>binary-message</code> listener, it means in a “binary communication”
  374. between the client and the server. We can imagine the client sending
  375. coordinates and the server giving others (for example for a board game):</p>
  376. <pre><code class="language-php">$server->on('binary-message', function (Hoa\Event\Bucket $bucket) {
  377. $data = $bucket->getData();
  378. $message = $data['message'];
  379. $point = [];
  380. list($point['x'], $point['y']) = array_values(unpack('nx/ny', $message));
  381. // compute a next point.
  382. $bucket->getSource()->send(
  383. pack('nn', $point['x'], $point['y']),
  384. null,
  385. Hoa\Websocket\Connection::OPCODE_BINARY_FRAME
  386. );
  387. return;
  388. });</code></pre>
  389. <p>The <a href="http://php.net/pack"><code>pack</code></a> and
  390. <a href="http://php.net/unpack"><code>unpack</code></a> functions are
  391. precious friends here.</p>
  392. <p>Notice that binary messages can also be <strong>fragmented</strong>. We
  393. have to use the <code>OPCODE_BINARY_FRAME</code> opcode instead of
  394. <code>OPCODE_TEXT_FRAME</code> and then continue with
  395. <code>OPCODE_CONTINUATION_FRAME</code> as we have learned.</p>
  396. <h2 id="Write_a_client" for="main-toc">Write a client</h2>
  397. <p>The <code>Hoa\Websocket\Client</code> class allows to write a client
  398. <strong>manipulating</strong> the WebSocket protocol. This class inherits from
  399. <code>Hoa\Websocket\Connection</code>. The <strong>communication</strong> is
  400. based on a socket client. We will use the <code>Hoa\Socket\Client</code> class
  401. (from <a href="@hack:chapter=Socket">the <code>Hoa\Socket</code> library</a>)
  402. to fill this role.</p>
  403. <p>Much as <code>Hoa\Websocket\Server</code> is able to communicate with
  404. clients supporting different versions of the WebSocket protocol,
  405. <code>Hoa\Websocket\Client</code> uses <strong>only</strong> the RFC6455
  406. protocol. It means that the client is only able to communicate with a server
  407. supporting the RFC6455 protocol.</p>
  408. <h3 id="Start_a_connection_with_the_server" for="main-toc">Start a connection
  409. with the server</h3>
  410. <p>Like the server, the client inherits from
  411. <code>Hoa\Websocket\Connection</code>. We find the same <code>send</code>,
  412. <code>close</code>, <code>run</code> and other methods, and also the same
  413. listeners. And like the server, listeners must be registered on the
  414. client.</p>
  415. <p>Thus, to start a client, we will write:</p>
  416. <pre><code class="language-php">$client = new Hoa\Websocket\Client(
  417. new Hoa\Socket\Client('ws://127.0.0.1:8889')
  418. );
  419. $client->on('message', function (Hoa\Event\Bucket $bucket) {
  420. $data = $bucket->getData();
  421. echo 'received message: ', $data['message'], "\n";
  422. return;
  423. });</code></pre>
  424. <p>The client can work in loop mode, like the server, with the
  425. <code>run</code> method. In this case, we will write:</p>
  426. <pre><code class="language-php">$client->run();</code></pre>
  427. <p>Or, for a sequential exchange, we have to manually call the
  428. <code>Hoa\Websocket\Client::connect</code> method:</p>
  429. <pre><code class="language-php">$client->connect();</code></pre>
  430. <p>If the server does not support the right protocol, a
  431. <code>Hoa\Websocket\Exception\BadProtocol</code> exception will be thrown.</p>
  432. <h3 id="Send_and_broadcast_messages" for="main-toc">Send and broadcast
  433. messages</h3>
  434. <p>To send a message, we will use the
  435. <code>Hoa\Websocket\Connection::send</code> method. Its behavior has been
  436. described previously for the server. It is identical.</p>
  437. <p><em>A contrario</em>, to receive a message, we will use the
  438. <code>Hoa\Websocket\Client::receive</code> method. The received messages from
  439. the server will fire listeners. Thus, we will send a message to the server,
  440. and then we will wait a response, twice:</p>
  441. <pre><code class="language-php">$client->send('foobar');
  442. $client->receive();
  443. $client->send('bazqux');
  444. $client->receive();
  445. $client->close();</code></pre>
  446. <p>The <code>Hoa\Websocket\Client::receive</code> method has no argument.</p>
  447. <h3 id="Note_about_the_handshake_and_the_host" for="main-toc">Note about the
  448. handshake and the host</h3>
  449. <p>In order the handshake to be complete, it is necessary to send a
  450. <code>Host</code> HTTP header, representing the name of the host. When the
  451. client is executed through an HTTP server, the host of the server will be used
  452. if available. Else, if it is not available, or if we execute the client in a
  453. command line for example, we have to specify a host with the
  454. <code>Hoa\Websocket\Client::setHost</code> method, before connecting the
  455. client (before the calls of <code>Hoa\Websocket\Connection::run</code> or
  456. <code>Hoa\Websocket\Client::connect</code>); thus:</p>
  457. <pre><code class="language-php">$client->setHost('hoa-project.net');
  458. $client->connect();
  459. // …</code></pre>
  460. <p>To know if the host is known, we can use the
  461. <code>Hoa\Websocket\Client::getHost</code> method. It will return
  462. <code>null</code> if the host is unknown. Or, during the handshake, an
  463. exception of type <code>Hoa\Websocket\Exception\Exception</code> will be
  464. thrown.</p>
  465. <h2 id="Customized_node" for="main-toc">Customized node</h2>
  466. <p>The <code>Hoa\Socket\Server</code> and <code>Hoa\Socket\Client</code>
  467. classes work with <strong>nodes</strong>: Objects representing an opened
  468. <strong>connection</strong>. The main class representing a node is
  469. <code>Hoa\Socket\Node</code>. The <code>Hoa\Websocket</code> library provides
  470. its own node: <code>Hoa\Websocket\Node</code>. We can <strong>extend</strong>
  471. this class in order to add and manipulate some <strong>information</strong>
  472. about a connection.</p>
  473. <p>For example, in the case of an instant messaging, we could store the
  474. pseudo of the client:</p>
  475. <pre><code class="language-php">class ChatNode extends Hoa\Websocket\Node
  476. {
  477. protected $_pseudo = null;
  478. public function setPseudo ($pseudo)
  479. {
  480. $old = $this->_pseudo;
  481. $this->_pseudo = $pseudo;
  482. return $old;
  483. }
  484. public function getPseudo()
  485. {
  486. return $this->_pseudo;
  487. }
  488. }</code></pre>
  489. <p>The <code>Hoa\Socket\Server::setNodeName</code> or
  490. <code>Hoa\Socket\Client::setNodeName</code> methods allow to specify what
  491. node class the server or the client will <strong>use</strong>:</p>
  492. <pre><code class="language-php">$server = new Hoa\Websocket\Server(
  493. new Hoa\Socket\Server('ws://127.0.0.1:8889')
  494. );
  495. $server->getConnection()->setNodeName('ChatNode');</code></pre>
  496. <p>Next, in our listeners for example, we will be able to use our
  497. <code>getPseudo</code> method:</p>
  498. <pre><code class="language-php">$server->on('message', function (Hoa\Event\Bucket $bucket) {
  499. $node = $bucket->getSource()->getConnection()->getCurrentNode();
  500. var_dump($node->getPseudo());
  501. // …
  502. });</code></pre>
  503. <p>If you set up a <strong>protocol</strong> by using WebSockets between
  504. your clients and your server, customized nodes will be very helpful to store
  505. extra recurrent information.</p>
  506. <h2 id="Conclusion" for="main-toc">Conclusion</h2>
  507. <p>The <code>Hoa\Websocket</code> library allows to create WebSocket
  508. <strong>servers</strong> and <strong>clients</strong> for more
  509. <strong>interactivity</strong> in your applications. The server is easily
  510. extensible thanks to the notion of <strong>node</strong> which eases the
  511. storage and manipulation of useful data to create its own protocol.</p>
  512. </yield>
  513. </overlay>