PHPを利用したWebSocketのサーバ側の実装と、JSでリアルタイムチャットの実装を行います。WebSocketを利用する機能を作る前段階として、簡単なWebSocketアプリケーションを作ってみました
実行環境
- PHP7.2(RC2)
- Ratchet
- js
- jQuery
WebSocketのサーバー側の実装はPHPではなくもてよいのですが、メインのアプリケーションがPHPだとした場合、PHPで作れていると何かと便利なので、まずはPHPで実装してみました
参考にしたサイトはこちら
Webcoket for PHP
『Ratchet』を使ってPHPのwebsocketを試してみた
Ratchetを利用しています。基本的にサイトで紹介されている導入方法でPHP側は簡単に実装できます
1 2 3 4 5 6 7 8 9 10 |
{ "autoload": { "psr-0": { "MyApp": "src" } }, "require": { "cboden/ratchet": "^0.4.0" } } |
これで実行環境はできました。Chatを実行する機能を作ります。
この記事で紹介されているクラスを基にして、細かい修正をしただけです
1 |
$ composer update |
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 |
<?php namespace MyApp; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage (); } public function onOpen(ConnectionInterface $conn) { // Store the new connection to send messages to later $this->clients->attach ( $conn ); echo "New connection! ({$conn->resourceId})\n"; } public function onMessage(ConnectionInterface $from, $msg) { $numRecv = count ( $this->clients ) - 1; echo sprintf ( 'Connection %d sending message "%s" to %d other connection%s' . "\n", $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's' ); foreach ( $this->clients as $client ) { if ($from !== $client) { // The sender is not the receiver, send to each client connected $client->send ( $msg ); } } } public function onClose(ConnectionInterface $conn) { // The connection is closed, remove it, as we can no longer send it messages $this->clients->detach ( $conn ); echo "Connection {$conn->resourceId} has disconnected\n"; } public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error has occurred: {$e->getMessage()}\n"; $conn->close (); } } |
メインの処理を作ります
1 2 3 4 5 6 7 8 9 10 |
<?php use Ratchet\Server\IoServer; use MyApp\Chat; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; require dirname ( __DIR__ ) . '/vendor/autoload.php'; $server = IoServer::factory ( new HttpServer( new WsServer( new Chat () ) ), 8080 ); $server->run (); |
作成した「server.php」を実行します
1 |
$ php bin/server.php & |
正しく起動したサーバ側は完成です
次に、PHPのwebsocketに接続して、チャットを行う処理をブラウザ側の処理を作ります。
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 |
<!DOCTYPE html> <html lang="ja"> <meta charset="utf-8"> <title>Chat</title> <style> .container { width: 600px; } .box{ overflow:hidden; } .left { background-color: #D3D3D3; padding: 20px; margin: 5px; width: 300px; float:left; } .right { background-color: #ADFF2F; padding: 20px; margin: 5px; width: 300px; float:right; } </style> <script src="http://code.jquery.com/jquery-2.2.4.js"></script> <script> (function($){ var settings = {}; var methods = { init : function( options ) { settings = $.extend({ 'uri' : 'ws://localhost:8080', 'conn' : null, 'message' : '#message', 'display' : '#display' }, options); $(settings['message']).keypress( methods['checkEvent'] ); $(this).chat('connect'); }, checkEvent : function ( event ) { if (event && event.which == 13) { var message = $(settings['message']).val(); if (message && settings['conn']) { settings['conn'].send(message + ''); $(this).chat('drawText',message,'right'); $(settings['message']).val(''); } } }, connect : function () { if (settings['conn'] == null) { settings['conn'] = new WebSocket(settings['uri']); settings['conn'].onopen = methods['onOpen']; settings['conn'].onmessage = methods['onMessage']; settings['conn'].onclose = methods['onClose']; settings['conn'].onerror = methods['onError']; } }, onOpen : function ( event ) { $(this).chat('drawText','サーバに接続','left'); }, onMessage : function (event) { if (event && event.data) { $(this).chat('drawText',event.data,'left'); } }, onError : function(event) { $(this).chat('drawText','エラー発生!','left'); }, onClose : function(event) { $(this).chat('drawText','サーバと切断','left'); settings['conn'] = null; setTimeout(methods['connect'], 1000); }, drawText : function (message, align='left') { if ( align === 'left' ) { var inner = $('<div class="left"></div>').text(message); } else { var inner = $('<div class="right"></div>').text(message); } var box = $('<div class="box"></div>').html(inner); $('#chat').prepend(box); }, }; // end of methods $.fn.chat = function( method ) { if ( methods[method] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist' ); } } // end of function })( jQuery ); $(function() { $(this).chat({ 'uri':'ws://localhost:8080', 'message' : '#message', 'display' : '#chat' }); }); </script> </head> <body> <input type="text" id="message" size="50" /> <div id="chat" class="container"></div> </body> </html> |
JSで実装されている部分がメインの機能です。実行するとwebsocketにつながり、リアルタイムの通信が行われます。URLは「http://localhost:8080/index.html」などになります。複数のブラウザのタブで開くと動きがよくわかります
片方に、入力して投稿すると、他方のブラウザは再読み込みしなくても、自動的に表示されます。自分の投稿は「右側」、他のユーザの投稿は「左側」に表示されます。左側のブラウザに投稿すると、右側のブラウザにも自動的に投稿が反映されます
右側のブラウザに投稿しても、左側のブラウザに自動的に反映されます。リアルタイムですね
サーバ側の処理をPHPで実装した場合、速度、安定性、同時接続数など検討すべきことはありますが、PHPでもやろうと思えばできるということでした