Socket

Система предоставляет канал для информирования пользователей о событиях в режиме реального времени через параллельное соединение пользователя с сервером посредством web-socket-a (если версия браузера не позволяет их использование, то автоматически будут использоваться другие механизмы, например, long-polling). С помощью данной функциональности, реализованы: общение между пользователями (модуль chat), система публикации сообщений (модуль livefeed), система мониторинга работы расчетчика (модуль calclog) и многие другие.

В качестве дополнительного инструмента реализованного на базе socket-а является Progress bar. При разработке системы, нужно принимать в расчет, что некоторые запросы, которые пользователь отправляет на сервер требуют продолжительного времени исполнения, например печать свода документов. В этом случае, при отправке ajax запроса, если клиент не получает ответ от браузера в течении определенного промежутка времени (обычно 2-х минут), браузер отправляет повторный запрос. Чтобы избежать такого поведения клиентского кода, система запускает задачу, инициализирует прогресс бар и отправляет клиенту в качестве ответа уникальный id, по которому в дальнейшем отправляется информация через socket о прогрессе работы.

Перейдем к описанию программной реализации:

Серверный код

В качестве основной библиотеки используется nodejs библиотек socket.io.

Код на стороне сервера находится в файле src/socket.js. Основные методы предоставляемые модулем на стороне сервера:

emitEventAll = function(eventName,data) // оповещение всех пользователей о каком то событии
emitTo = function(CodeUser,EventName,data) // оповещение конкретного пользователя о событии

Кроме того, при присоединении или разрыве соединения с пользователем модуль инициирует события:

Events.emit("userconnected",socket)
Events.emit("userdisconnected",socket)

Рассмотрим пример использования событий. Код из модуля login. Появлению этого куска кода в системе способствовало то, что при закрытии браузера Chrome сессия по умолчанию не завершается (это можно устранить в настройках браузера, поставив нужную галочку). В связи с этим, на форме входа в систему возникла галочка "Чужой компьютер", при включении которой в системе запускается свой механизм автоматического выхода пользователя из системы через 5 секунд после обрыва всех socket соединений.

var SocketManager =  require(__base+'src/socket.js');

var AlienDeviceGuard = (new function(){
    var self = this;

    self.DisconnectTimeouts = {};

    self.UserConnected = function(socket){
        var CodeUser = socket.request.session.user.CodeUser;
        if (self.DisconnectTimeouts[CodeUser]){
            clearTimeout(self.DisconnectTimeouts[CodeUser]);
        }
    }

    self.UserDisconnected = function(socket){
        var CodeUser = socket.request.session.user.CodeUser;
        if (socket.request.session.alienDevice){
            if (socket.request.session && socket.request.session.user && !_.isEmpty(CodeUser)){
                self.DisconnectTimeouts[CodeUser] = setTimeout(function(req){
                    return function(){
                        req.logout();
                        req.session.destroy();
                    }
                }(socket.request),5000);
            }        
        }
    }

    SocketManager.Events.on("userconnected",self.UserConnected);
    SocketManager.Events.on("userdisconnected",self.UserDisconnected);

    return self;
})

Клиентский код

Код на стороне клиента находится в файле modules/socket/index.js

Работа с socket-ом на стороне клиента происходит по следующему алгоритму:

  1. Регистрируется событие модуля в виде название события + обработчик
  2. Запускается прослушивание события
  3. Останавливается прослушивание события

На словах это выглядит так: сначала модуль регистрирует событие, затем, обычно, при входе на страницу модуля запускается прослушивание, при уходе со страницы модуля, прослушивание останавливается. Ниже приведен пример из клиентского кода модуля livefeed:

 self.Init = function(done){
        self.LoadLiveFeed();
        MSocket.RegisterEvent("livefeedupdate",self.UpdateFeed);
        MSocket.Start ("livefeedupdate");
        MSite.Events.on("scroll-bottom",self.LoadMore);
        return done();
}

Модуль при загрузке передаёт информацию socket-у о том, что события "livefeedupdate" будут обрабатываться aeyrwbtq UpdateFeed. И запускает прослушивание. На стороне сервера, при изменении сообщений событие рассылается всем пользователям, например при удалении события:

router.delete("/feed/:id", HP.TaskAccess("IsFeedWriter"), function(req,res,next){
    var FeedModel = mongoose.model("lfmessage"), id = req.params.id;
    FeedModel.remove({_id:id}).exec(function(err){
        SocketManager.emitEventAll("livefeedupdate",{id:id,type:'delete'});
        return res.json({});
    })
})

Пример работы с progressbar-ом

Все модули обладают этим функционалом. Код реализации запроса от клиента к серверу:

self.ProgressMessage     = ko.observable("");
self.ProgressCurrentLine = ko.observable(0);
self.ProgressLines       = ko.observable(0);
self.Progress            = ko.observable(0);

self.ProgressBarOn       = ko.observable(false);

self.OnProgress = function(data){
    self.ProgressLines(data.Lines);
    self.ProgressCurrentLine(data.Line);
    self.Progress(data.Progress);
    self.ProgressMessage(data.Message);
}

self._request = function(method,urlpart,data,done){
        self.Error(null); self.IsLoading(true);
        $.ajax({
            url:self.base+urlpart,
            method:method,
            data:data,
            success:function(data){
                self.IsLoading(false);
                if (data.err) {
                    return self.Error(data.err);   
                }
                if (data.progressbar){
                    self.IsLoading(true);
                    self.ProgressMessage(""); 
                    self.ProgressCurrentLine(0); 
                    self.Progress(0);
                    self.ProgressCurrentLine(0);
                    self.ProgressBarOn(true);
                    ProgressBar.Create(data.progressbar,self.OnProgress, function(){
                        self.IsLoading(false); self.ProgressBarOn(false); done();
                    });
                } else {
                    return done && done(data);
                }
            },
            error:function(data){
                self.IsLoading(false);
            }
        })
    }

На стороне клиента в том месте, где необходимо расположить прогресс бар, нужно сделать вызов шаблона

<!-- ko template:'progress_bar' --><!-- /ko -->

Сам шаблон выглядит следующим образом:

<script id="progress_bar" type="text/html">
    <div class='progressBar' data-bind='if:$data.ProgressBarOn()'>
        <div class="progress">
            <div class="progress-bar" data-bind='{
                    css:{
                        "progress-bar-success":$data.ProgressCurrentLine()==1,
                        "progress-bar-warning":$data.ProgressCurrentLine()==2,
                        "progress-bar-danger":$data.ProgressCurrentLine()==3
                    },
                    attr:{
                        "data-percent":$data.Progress()+"%",
                        "style":"width:"+$data.Progress()+"%"
                    }
                }'>
            </div>
        </div>
        <div class="progress-lines" data-bind='text:$data.ProgressCurrentLine()+"/"+$data.ProgressLines()'> </div>
        <div class="progress-message" data-bind='text:$data.ProgressMessage'></div>
    </div>
</script>

На стороне сервера работа прогресс бара:

var Progress = require(__base+"src/progressbar.js");

var UProgress = new Progress(CodeUser);

UProgress.Init(4,"Синхронизация данных"); // инициализация с количеством шагов

UProgress.StartLine(ListLength,"Синхронизация проектов"); // запуск шага
UProgress.Step("Обработан "+P.NameProject); // инкремент с дополнительной информацией

Progress.Step("Обработан "+P.NameProject)

Вид работы прогресс бара:

results matching ""

    No results matching ""