Lua + nginxに基づいたレイヤーnodejsなしのphp + websocketの高速プール

nginx + lua



簡単に説明すると、nginxはwebsocketに弾丸を付ける方法を知らず、リクエストごとにphpが機能します。 Webソケットを開いたままにして、データが到着したらphpに接続し(同じfastcgiを介して)、応答を送信するレイヤーが必要です。



更新:これは、nodejsに比べても、はるかに遅いphpソリューションに関するものではありません。



判明したように、このトピックは新しいものではなく、ソースコードは2014年から継続していますが、それでも、ここで説明するトリックに関する情報は非常に少ないものです。 「 websockets php 」をグーグルで検索できます。 このトピックは、見つかった実装例(2つ、より正確には)が機能しないという事実によってさらに悪化しています。



ここで、私は内部のどこかで、何であるかを知っていました。 私は長い間、このミドルウェアをNginx内に入れて、異なるかなり遅いphpライブラリ( 12 )を使用せず、シングルスレッドのnodejsをバイパスしたくないと思っていました。 そして、私は多くの(そして可能な限り)Webソケットが欲しいので、レイヤーの追加コストはより少なくなりました。 そのため、nodejsを使用して多数のマシンを作成しないように(将来、通常は高負荷でこれを行います)、Nginxが提供する一部の拡張機能をlua + restyの形式で使用します。 Nginx + luaはnginx-extrasパッケージからインストールすることも、自分でビルドすることもできます。 Restyからは、 websocketのみが必要です。 libディレクトリの内容をパスのどこかにダウンロードしてドロップします( / home / username / lib / lua / libがありますが、良い方法では/ usr / local / share / luaにあります )。



デフォルトでは、nginx + websocketsは次のように機能します。



  1. クライアントはnginxに接続します
  2. Nginxはアップストリームへのプロキシ/ websocketを提供する別のサーバー(nodejs + sockets.ioに基づくミドルサーバーなど)でプロキシストリームを開きます。
  3. Middle Serverサーバーは、epollなどのイベントリスナーにソケット接続をスローし、データを待機します。
  4. データを受信すると、Middle Serverサーバーは、次にphpでFastcgi接続を開き、待機して回答を取得します。 ソケットに送信します。 データを待っているソケットを再び返します。
  5. 特別なWebSocketクロージャーフレームが届くまで、円で囲みます。


リソースのオーバーヘッドとこのソリューションのシングルスレッドを除き、すべてが単純です。



提案されたスキームでは、MiddleServerはnginx内のミドルウェアに変わります。 さらに、Fastcgiの期待はなく、すべての作業は同じepollによって行われ、nginxはオープンソケットを信頼しますが、一方で、nginx'aスレッドは他のことを行うことができます。 このスキームにより、ストリームに散在する多数のWebソケットを同時に操作できます。



ここでは、他のホスティング設定なしで、タスクに関連する単純化されたコードのみを提供します。 私はすべての見出しを不必要なものとして修正しようとしませんでした。



lua_package_path "/home/username/lib/lua/lib/?.lua;;"; server { # ,     ,     nginx location ~ ^/ws/?(.*)$ { default_type 'plain/text'; #        -   ,     content_by_lua_file /home/username/www/wsexample.local/ws.lua; } #   ,     php #    POST ,    json payload location ~ ^/lua_fastcgi_connection(/?.*)$ { internal; #     nginx fastcgi_pass_request_body on; fastcgi_pass_request_headers off; # never never use it for lua handler #include snippets/fastcgi-php.conf; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD "POST"; # $request_method; fastcgi_param CONTENT_TYPE "application/x-www-form-urlencoded"; # $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param DOCUMENT_URI "$1"; #  $document_uri fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; fastcgi_param SCRIPT_FILENAME "$document_root/mywebsockethandler.php"; fastcgi_param SCRIPT_NAME "/mywebsockethandler.php"; fastcgi_param REQUEST_URI "$1"; #      .      lua   -   php . fastcgi_pass unix:/var/run/php/php7.1-fpm.sock; fastcgi_keep_conn on; }
      
      





そして、ws.luaコード:



  local server = require "resty.websocket.server" local wb, err = server:new{ -- timeout = 5000, -- in milliseconds --     max_payload_len = 65535, } if not wb then ngx.log(ngx.ERR, "failed to new websocket: ", err) return ngx.exit(444) end while true do local data, typ, err = wb:recv_frame() if wb.fatal then return elseif not data then ngx.log(ngx.DEBUG, "Sending Websocket ping") wb:send_ping() elseif typ == "close" then -- send a close frame back: local bytes, err = wb:send_close(1000, "enough, enough!") if not bytes then ngx.log(ngx.ERR, "failed to send the close frame: ", err) return end local code = err ngx.log(ngx.INFO, "closing with status code ", code, " and message ", data) break; elseif typ == "ping" then -- send a pong frame back: local bytes, err = wb:send_pong(data) if not bytes then ngx.log(ngx.ERR, "failed to send frame: ", err) return end elseif typ == "pong" then -- just discard the incoming pong frame elseif data then --      uri,  json payload   body local res = ngx.location.capture("/lua_fastcgi_connection"..ngx.var.request_uri,{method=ngx.HTTP_POST,body=data}) if wb == nil then ngx.log(ngx.ERR, "WebSocket instaince is NIL"); return ngx.exit(444) end wb:send_text(res.body) else ngx.log(ngx.INFO, "received a frame of type ", typ, " and payload ", data) end end
      
      





他に何ができますか? 速度を測定し、nodejsと比較します:)そして、lua内のRedis、MySQL、Postgresでリクエストを行うことができます...



既知の問題:Webソケット上のデータパケットの最大サイズは65 KBです。 必要に応じて、フレームにブレークを追加できます。 プロトコルは複雑ではありません。



テストhtml(ws.html):



ここにHTML
 <!DOCTYPE> <html> <head> <meta charset="utf-8" /> <script type="text/javascript"> "use strict"; let socket; function tryWebSocket() { socket = new WebSocket("ws://try6.local/ws/"); socket.onopen = function() { console.log(" ."); }; socket.onclose = function(event) { if (event.wasClean) { console.log('  '); } else { console.log(' '); // , ""   } console.log(': ' + event.code + ' : ' + event.reason); }; socket.onmessage = function(event) { console.log("  " + event.data); }; socket.onerror = function(error) { console.log(" " + error.message); }; } function tryWSSend(event) { let msg = document.getElementById('msg'); socket.send(msg.value); event.stopPropagation(); event.preventDefault(); return false; } function closeWebSocket(event) { socket.close(); } </script> </head> <body onLoad="tryWebSocket(event);return false;"> <form onsubmit="tryWSSend(event); return false;"> <button onclick="tryWebSocket(event); return false;">Try WebSocket</button> <fieldset> Message: <input value="Test message 4444" type="text" size="10" id="msg"/><input type="submit"/> </fieldset> <fieldset> <button onclick="closeWebSocket(event); return false;">Close Websocket</button><br/> </fieldset> </form> </body> </html>
      
      







テストphp(mywebsockethandler.php):



ここにPHP
 <?php header("Content-Type: application/json; charset=utf-8"); echo json_encode(["status"=>"ok","response"=>"php websocket json @ ".time(), "payload"=>[$_REQUEST,$_SERVER]]); exit;
      
      







LuaにFastCGIを使用するには、 別の Resty拡張機能をインストールします。



All Articles