📜 ⬆️ ⬇️

Realtime on your resource in a few minutes

During the development of the game, we were faced with the need to ensure maximum real-time data exchange between users, which led to experiments with various comet libraries.
The first bike was built on dklab realplexor, which, as it was once again attempted to use it, as expected, let us down. Maybe our hands are crooked, but unfortunately we didn’t manage to get events without delays of 5-10-15 seconds.
Dancing with a tambourine lasted a long time, with the result that we decided to stop at nginx_http_push_module , and the time spent was still worth it.

The main problems that have arisen when working with this module:


Among the shortcomings, it is possible to single out only the impossibility of sending messages to several channels at the same time, at the level of the library itself, which is in realplexor, as well as the lack of storing several events coming to the channel in a short period of time (reconnect time).

A little jaggle helped find the JS function, which, at first glance, solved the problem:
')
function Listener(url, successCallback, failureCallback) { var scope = this; var etag = 0, lastModified = 0; var launched = false; var failure = false; this.successTimeout = 0; this.failureTimeout = 5000; var getTimeout = function () { return failure ? this.failureTimeout : this.successTimeout; }; var listen = function () { $.ajax(scope.ajaxOptions); } var beforeSend = function (jqXHR) { jqXHR.setRequestHeader("If-None-Match", etag); jqXHR.setRequestHeader("If-Modified-Since", lastModified); }; var complete = function (jqXHR) { var timeout = getTimeout(); etag = jqXHR.getResponseHeader('Etag'); lastModified = jqXHR.getResponseHeader('Last-Modified'); var timeout = jqXHR.statusText == 'success' ? scope.successTimeout : scope.failureTimeout; if (timeout > 0) { setTimeout(listen, timeout); } else { listen(); } }; this.ajaxOptions = { url : url, type : 'GET', async : true, error : failureCallback, success : successCallback, dataType : 'json', complete : complete, beforeSend : beforeSend, timeout: 1000 * 60 * 60 * 24 }; this.start = function (timeout) { if (!launched) { if (typeof(timeout) == 'undefined' || timeout == 0) { listen(); } else { setTimeout(listen, timeout); } launched = true; } }; } 


A few dances with tambourines made it clear that she has her own skeletons in the closet:


The first problem was resolved quite simply, replacing
var etag = 0, lastModified = 0;
on
var etag = 0:
lastModified = LOAD_TIME;

where LOAD_TIME was set by a constant in the index, as
var LOAD_TIME = "<?=date('r');?>";
The second problem, I hoped, would be solved just as easily by replacing
timeout: 1000 * 60 * 60 * 24
on
timeout: 1000 * 20
creating a forced reconnect to nginx every X seconds (in our case, the optimal interval was 20 seconds).
But when he started testing, he got even more collisions than before - after the reconstructions the hellish flood of events began. Thank you console, for clarifying the situation. The problem was that when the check did a reconnect, the complete handler continued to update the data on the headers, so the following request went off with If-None-Match and If-Modified-Since null.

In the end, got the most stable function:

 <script> var LOAD_TIME = "<?=date('r');?>"; /* * !        ,      ,   index.php,     JS ,    . */ function Listener(url, successCallback, failureCallback) { var scope = this; var etag = 0; var lastModified = LOAD_TIME; var launched = false; var failure = false; this.successTimeout = 1000; this.failureTimeout = 0; var getTimeout = function () { return failure ? this.failureTimeout : this.successTimeout; }; var listen = function () { $.ajax(scope.ajaxOptions); } var beforeSend = function (jqXHR) { jqXHR.setRequestHeader("If-None-Match", etag); jqXHR.setRequestHeader("If-Modified-Since", lastModified); cw("BEFORE None-Match : "+etag); cw("BEFORE Modified : "+lastModified); }; var complete = function (jqXHR) { var timeout = getTimeout(); if (jqXHR.getResponseHeader('Etag') != null && jqXHR.getResponseHeader('Last-Modified') != null) { etag = jqXHR.getResponseHeader('Etag'); lastModified = jqXHR.getResponseHeader('Last-Modified'); } var timeout = jqXHR.statusText == 'success' ? scope.successTimeout : scope.failureTimeout; if (timeout > 0) { setTimeout(listen, timeout); } else { listen(); } }; this.ajaxOptions = { url : url, type : 'GET', async : true, error : failureCallback, success : successCallback, dataType : 'json', complete : complete, beforeSend : beforeSend, timeout: 1000 * 20 }; this.start = function (timeout) { if (!launched) { if (typeof(timeout) == 'undefined' || timeout == 0) { listen(); } else { setTimeout(listen, timeout); } launched = true; } }; this.start(); } </script> 


Calling an event handler looks like this:

 <script> $(document).ready(function() { Listener('/listener?cid=chanel_1', onSuccess, onError); }); /* *onSuccess  onError -   ( )    ,        . */ function onSuccess (data) { if (data) { $("#messages").prepend(data.time + " | " +data.msg); } else { console.log("EMPTY DATA"); } } function onError () { alert("ERROR"); } </script> 


Since in the examples on the module site there is no code that could send a message to several channels at once, we decided to write our own function.
Implementation in php:

 <? function push ($cids, $text) { /* * $cids - ID ,  ,     - ID  * $text - ,    */ $c = curl_init(); $url = 'http://server_name/publisher?cid='; $message = array( 'time' => time(), 'msg' => $text ); curl_setopt($c, CURLOPT_RETURNTRANSFER, true); curl_setopt($c, CURLOPT_POST, true); if (is_array($cids)) { foreach ($cids as $v) { curl_setopt($c, CURLOPT_URL, $url.$v); curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($message)); $r = curl_exec($c); } } else { curl_setopt($c, CURLOPT_URL, $url.$cids); curl_setopt($c, CURLOPT_POSTFIELDS, json_encode($message)); $r = curl_exec($c); } curl_close($c); } // push (1, ""); // push (array(1, 2, 3), ""); // push (array('chanel_1' => 1, 'chanel_2' => 'user_100500', 'chanel_3' => 'global'), ""); 


With the installation of Dklab Realplexor, no problems arose and full documentation is available on the website (http://dklab.ru/lib/dklab_realplexor) of the developer.
However, with nginx_http_push_module, everything is not so simple, to be honest, on CentOS, the finished package including this module could not be found, so everything was collected from the sources.

So let's start:

Create a daddy where we will download all the necessary elements.
 mkdir ~/nginx_push; cd ~/nginx_push; 


Download the latest sources from the githab:
 git clone git://github.com/slact/nginx_http_push_module.git 


We download the latest version of nginx, in our case it is nginx-1.1.15
 wget http://nginx.org/download/nginx-1.1.15.tar.gz; 


Downloading additional required libraries:
 wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.21.tar.gz; wget http://zlib.net/zlib-1.2.6.tar.gz; 


Unpack archives:
 tar -zxf nginx-1.1.15.tar.gz; tar -zxf pcre-8.21.tar.gz tar -zxf zlib-1.2.6.tar.gz 


Go to the source folder of nginx
 cd nginx-1.1.15; 


And configure nginx

 ./configure --prefix=/usr --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_gzip_static_module --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/tmp/nginx/client/ --http-proxy-temp-path=/tmp/nginx/proxy/ --http-fastcgi-temp-path=/tmp/nginx/fcgi --with-pcre=../pcre-8.21 --with-zlib=../zlib-1.2.6 --with-http_perl_module --with-http_stub_status_module –add-module=../nginx_http_push_module/ 


Then

 make make install 


now create the file /etc/init.d/nginx
 nano /etc/init.d/nginx; 


and put there such a script:

 #!/bin/sh ### BEGIN INIT INFO # Provides: nginx # Required-Start: $local_fs $remote_fs $network $syslog # Required-Stop: $local_fs $remote_fs $network $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts the nginx web server # Description: starts nginx using start-stop-daemon ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/sbin/nginx NAME=nginx DESC=nginx # Include nginx defaults if available if [ -f /etc/default/nginx ]; then . /etc/default/nginx fi test -x $DAEMON || exit 0 set -e . /lib/lsb/init-functions test_nginx_config() { if $DAEMON -t $DAEMON_OPTS >/dev/null 2>&1; then return 0 else $DAEMON -t $DAEMON_OPTS return $? fi } case "$1" in start) echo -n "Starting $DESC: " test_nginx_config # Check if the ULIMIT is set in /etc/default/nginx if [ -n "$ULIMIT" ]; then # Set the ulimits ulimit $ULIMIT fi start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON -- $DAEMON_OPTS || true echo "$NAME." ;; stop) echo -n "Stopping $DESC: " start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON || true echo "$NAME." ;; restart|force-reload) echo -n "Restarting $DESC: " start-stop-daemon --stop --quiet --pidfile \ /var/run/$NAME.pid --exec $DAEMON || true sleep 1 test_nginx_config start-stop-daemon --start --quiet --pidfile \ /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS || true echo "$NAME." ;; reload) echo -n "Reloading $DESC configuration: " test_nginx_config start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/$NAME.pid \ --exec $DAEMON || true echo "$NAME." ;; configtest|testconfig) echo -n "Testing $DESC configuration: " if test_nginx_config; then echo "$NAME." else exit $? fi ;; status) status_of_proc -p /var/run/$NAME.pid "$DAEMON" nginx && exit 0 || exit $? ;; *) echo "Usage: $NAME {start|stop|restart|reload|force-reload|status|configtest}" >&2 exit 1 ;; esac exit 0 


And now
 chkconfig --add nginx chkconfig --level 345 nginx on 


Everything. At this point, the nginx installation is complete, and you can start the nginx service.
 service nginx start 


Now let's go to the settings:

/ publisher - is used to write to the channel and should be available only to your server, otherwise anyone can write to it.
 location /publisher { //     ,     ?cid= set $push_channel_id $arg_cid; push_publisher; push_message_timeout 1m; //    push_message_buffer_length 20000; //   allow 127.0.0.1; deny all; } 


/ listener - is available to everyone and serves to distribute messages to channel subscribers.
 location /listener { //     ,     ?cid= set $push_channel_id $arg_cid; push_subscriber; push_subscriber_concurrency broadcast; default_type text/plain; } 


On this setting everything. The solution is good for everyone, but we have difficulty setting up crossdomain-ajax, and in the module, in the main branch, there is no jsonp support at the moment. BUT, a fork was found with this functionality (https://github.com/Kronuz/nginx_http_push_module). Its installation does not differ from the original.

To enable jsonp support in the location / listener section, add
 //     /?callback= push_channel_jsonp_callback $arg_callback; 


Now, if the data is transferred to the callback parameter, the module will wrap the data in jsonp.

There is no separate demo, but registered users in VK can see the work on a live example in our game - vk.com/app2814545
Fights were built on this technology (in fact, they demanded this rialtime), casinos, reports about receiving / spending resources and many other events.

Source: https://habr.com/ru/post/139339/


All Articles