Node.js Socket.io — Сервер setInterval работает на половинной скорости, полная скорость только при подключении.вызывается on

#javascript #node.js #socket.io

#javascript #node.js #socket.io

Вопрос:

Мой сервер ведет себя странно — обновление setInterval под Update LOBBIES установлено для запуска @ 1000/60 мс или 60cps, что я получаю только стабильные 34cps. Но когда socket.emit('angle') используется CLIENT сбоку, он работает плавно со скоростью 60 к / с до socket.emit('angle') остановки, затем возвращается обратно к 34 к / с. Прошло 3 дня, а я до сих пор понятия не имею. Похоже, что в коде нет проблем с дроссельной заслонкой, и, как ни странно, работает лучше, когда socket.on событие вызывается только для angle одного, а не для других. Не имеет никакого смысла! Есть идеи?

Клиент HTML / Javascript

 User.onConnect = (socket) => {  
    var lobbies = [];
    socket.on('updateLobbies', (data) => {
        $('#menu-screen-navigation-display-play ul').empty();
        $('#menu-screen-navigation-display-play ul').append(
            "<li style='position: fixed;top: calc(13vh); width: 47.5%; height: calc(5vh); left:26.25%;background-color: rgba(50,50,50,1);'>"  
            "   <table>"  
            "       <th style='width:10%'>"  
            "           <span></span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-lobby'>Lobby</span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-map'>Map</span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-gamemode'>Game Mode</span>"  
            "       </th>"  
            "       <th style='width:15%'>"  
            "           <span class='play-players'>Players</span>"  
            "       </th>"  
            "       <th style='width:15%'>"  
            "           <span></span>"  
            "       </th>"  
            "   </table>"  
            "</li>");
        $('#menu-screen-navigation-display-play ul').append(
            "<li style='position: fixed;bottom: calc(4vh); width: 47.5%; height: calc(5vh); left:26.25%;background-color: rgba(50,50,50,1);'>"  
            "   <table>"  
            "       <th style='width:10%'>"  
            "           <span></span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-lobby'></span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-map'></span>"  
            "       </th>"  
            "       <th style='width:20%'>"  
            "           <span class='play-gamemode'></span>"  
            "       </th>"  
            "       <th style='width:15%'>"  
            "           <span class='play-players'></span>"  
            "       </th>"  
            "       <th style='width:15%'>"  
            "           <span></span>"  
            "       </th>"  
            "   </table>"  
            "</li>");
        for(let i in data){
            lobbies[i] = data[i];
            
            switch(lobbies[i].mode){
                case 'Outbreak': {
                    lobbies[i].modeColor = 'green';
                    
                    break;
                }
                default: {
                    lobbies[i].modeColor = 'white';
                }
            }           
            switch(lobbies[i].map){
                case 'Urban Delight': {
                    lobbies[i].mapSrc = 'img/ui/map-icons/urbanDelight.png';
                    
                    break;
                }
                default: {
                    break;
                }
            }
            
            $('#menu-screen-navigation-display-play ul').append(
                "<li>"  
                "   <table>"  
                "       <th style='width:10%'>"  
                "           <img src='"   lobbies[i].mapSrc   "'/>"  
                "       </th>"  
                "       <th style='width:20%'>"  
                "           <span class='play-lobby'>"   lobbies[i].title   "</span>"  
                "       </th>"  
                "       <th style='width:20%'>"  
                "           <span class='play-map'>"   lobbies[i].map   "</span>"  
                "       </th>"  
                "       <th style='width:20%'>"  
                "           <span class='play-gamemode' style='color:" lobbies[i].modeColor "'>"   lobbies[i].mode   "</span>"  
                "       </th>"  
                "       <th style='width:15%'>"  
                "           <span class='play-players'>"   lobbies[i].online   " / "   lobbies[i].max   "</span>"  
                "       </th>"  
                "       <th style='width:15%'>"  
                "           <button id='"  lobbies[i].title   "' class='bubble'>Join</button>"  
                "       </th>"  
                "   </table>"  
                "</li>");
                
            $('#' lobbies[i].title).click(function(){
                playAudio(SFX['pop']);
                socket.emit('lobby.join', this.id);
                $('#menu-screen').hide();
                $('#menu-canvas').hide();
                $('#game-screen').show();
                MUSIC['menu'].pause();
                MUSIC['menu'].currentTime = 0;
                
                game(socket);
            });
        }
    });
    socket.on('gameID', (data) => {
        MAIN.id = data;
    });
    socket.on('sendClientChat', (data) => {
        if(data.user == 'server'){
            $("#chat-list").append(`<li><span class="chat-message" style="color:rgba(255,255,255,0.8);"> ${data.msg}</span></li>`);
        }
        else {
            $("#chat-list").append(`<li><span class="chat-message"><span style="color:red;">${data.user}:</span> ${chatFilter(data.msg)}</span></li>`);
        }
        $("#chat-list").scrollTop($("#chat-list").prop('scrollHeight'));
    });
    socket.on('lobbyinfo', (data) => {
        MAP = data;
    });
    socket.on('refresh', (data) => {
        PLAYER_LIST = data.players;
        for(var P in PLAYER_LIST){
            if(MAIN.id == PLAYER_LIST[P].id){
                MAIN.position = PLAYER_LIST[P].position;
                break;
            }
        }
        $('#serverCPS').text(data.server.cps);
    });
}



var controlReady = {
    up:         true,
    down:       true,
    left:       true,
    right:      true,
    Q:          true,
    E:          true,
    R:          true,
    SHIFT:      true,
    SPACE:      true,
    ENTER:      true,
    click:      true
};
function controls(mobile, socket){  
    if(mobile){
        $('#touchable-screen').show();
        document.getElementById("touchable-screen").addEventListener("touchstart", function(event){
            event.preventDefault();
            event.stopImmediatePropagation();
            
            emitMouseClick(true);
            emitMouseAngle(socket, event.touches[0]);
        });
        document.getElementById("touchable-screen").addEventListener("touchmove", function(event){
            event.preventDefault();
            event.stopImmediatePropagation();
            
            emitMouseAngle(socket, event.touches[0]);
        });
        document.getElementById("touchable-screen").addEventListener("touchend", function(event){
            event.preventDefault();
            event.stopImmediatePropagation();
            
            emitMouseClick(false);
            emitMouseAngle(socket, event.touches[0]);
        });
        
        var joystickRadius  = Math.round(window.innerHeight/100 * 5);
        
        document.getElementById("joystick-left").style.width    = Math.round(window.innerHeight/100 * 30)  "px";
        document.getElementById("joystick-left").style.height   = Math.round(window.innerHeight/100 * 30)  "px";
        document.getElementById("joystick-left").style.left     = Math.round(window.innerHeight/100 * 3)  "px";
        document.getElementById("joystick-left").style.bottom   = Math.round(window.innerHeight/100 * 3)  "px";
        
        document.getElementById("joystick-left-stick").style.width  = Math.round(window.innerHeight/100 * 20)  "px";
        document.getElementById("joystick-left-stick").style.height     = Math.round(window.innerHeight/100 * 20)  "px";
        document.getElementById("joystick-left-stick").style.right  = Math.round(window.innerHeight/100 * 5)  "px";
        document.getElementById("joystick-left-stick").style.bottom     = Math.round(window.innerHeight/100 * 2.5)  "px";
        
        let myStickLeft = new JoystickController("joystick-left-stick", joystickRadius, 8);
        
        document.getElementById("joystick-right").style.width   = Math.round(window.innerHeight/100 * 30)  "px";
        document.getElementById("joystick-right").style.height  = Math.round(window.innerHeight/100 * 30)  "px";
        document.getElementById("joystick-right").style.right   = Math.round(window.innerHeight/100 * 3)  "px";
        document.getElementById("joystick-right").style.bottom  = Math.round(window.innerHeight/100 * 3)  "px";
        
        document.getElementById("joystick-right-stick").style.width     = Math.round(window.innerHeight/100 * 20)  "px";
        document.getElementById("joystick-right-stick").style.height    = Math.round(window.innerHeight/100 * 20)  "px";
        document.getElementById("joystick-right-stick").style.right     = Math.round(window.innerHeight/100 * 5)  "px";
        document.getElementById("joystick-right-stick").style.bottom    = Math.round(window.innerHeight/100 * 2.5)  "px";
        
        let myStickRight = new JoystickController("joystick-right-stick", joystickRadius, 8);
        
        $('#joystick-left').show();
        $('#joystick-right').show();
        $('#chat-input').hide();
    }
    else{
        document.addEventListener('mousemove', function(event){
            emitMouseAngle(socket, event);
        });
        document.addEventListener('mousedown', function(event){
            emitMouseAngle(socket, event);
            emitMouseClick(true);
        });
        document.addEventListener('mouseup', function(event){
            emitMouseAngle(socket, event);
            emitMouseClick(false);
        });
    }
    document.addEventListener('keydown', (event) => {
        if(controlReady.up amp;amp; (event.keyCode === 87 || event.keyCode == 38)){//UP
            controlReady.up = false;
            socket.emit('controls',{inputId:'up',state:true});
        }
        if(controlReady.down amp;amp; (event.keyCode == 83 || event.keyCode == 40)){//DOWN
            controlReady.down = false;
            socket.emit('controls',{inputId:'down',state:true});
        }
        if(controlReady.left amp;amp; (event.keyCode == 65 || event.keyCode == 37)){//LEFT
            controlReady.left = false;
            socket.emit('controls',{inputId:'left',state:true});
        }
        if(controlReady.right amp;amp; (event.keyCode == 68 || event.keyCode == 39)){//RIGHT
            controlReady.right = false;
            socket.emit('controls',{inputId:'right',state:true});
        }
        
        if(controlReady.Q amp;amp; event.keyCode == 81){//Q
            controlReady.Q = false;
            socket.emit('controls',{inputId:'Q',state:true});
        }
        if(controlReady.E amp;amp; event.keyCode == 69){//E
            controlReady.E = false;
            socket.emit('controls',{inputId:'E',state:true});
        }
        if(controlReady.R amp;amp; event.keyCode == 82){//R
            controlReady.R = false;
            socket.emit('controls',{inputId:'R',state:true});
        }               
        if(controlReady.SHIFT amp;amp; event.keyCode == 16){//SHIFT
            controlReady.SHIFT = false;
            socket.emit('controls',{inputId:'shift',state:true});
        }
        if(controlReady.SPACE amp;amp; event.keyCode == 32){//SPACE
            controlReady.SPACE = false;
            socket.emit('controls',{inputId:'space',state:true});
        }
    });
    document.addEventListener('keyup', (event) => {
        if(!controlReady.up amp;amp; (event.keyCode === 87 || event.keyCode == 38)){//UP
            controlReady.up = true;
            socket.emit('controls',{inputId:'up',state:false});
        }
        if(!controlReady.down amp;amp; (event.keyCode == 83 || event.keyCode == 40)){//DOWN
            controlReady.down = true;
            socket.emit('controls',{inputId:'down',state:false});
        }
        if(!controlReady.left amp;amp; (event.keyCode == 65 || event.keyCode == 37)){//LEFT
            controlReady.left = true;
            socket.emit('controls',{inputId:'left',state:false});
        }
        if(!controlReady.right amp;amp; (event.keyCode == 68 || event.keyCode == 39)){//RIGHT
            controlReady.right = true;
            socket.emit('controls',{inputId:'right',state:false});
        }
        
        if(!controlReady.Q amp;amp; event.keyCode == 81){//Q
            controlReady.Q = true;
            socket.emit('controls',{inputId:'Q',state:false});
        }
        if(!controlReady.E amp;amp; event.keyCode == 69){//E
            controlReady.E = true;
            socket.emit('controls',{inputId:'E',state:false});
        }
        if(!controlReady.R amp;amp; event.keyCode == 82){//R
            controlReady.R = true;
            socket.emit('controls',{inputId:'R',state:false});
        }               
        if(!controlReady.SHIFT amp;amp; event.keyCode == 16){//false
            controlReady.SHIFT = true;
            socket.emit('controls',{inputId:'shift',state:false});
        }
        if(!controlReady.SPACE amp;amp; event.keyCode == 32){//SPACE
            controlReady.SPACE = true;
            socket.emit('controls',{inputId:'space',state:false});
        }
    });
    
    
    $('#chat-input').focus(() => {
        controlReady.ENTER = false;
    });
    $('#chat-input').blur(() => {
        controlReady.ENTER = true;
    });
    function emitMouseAngle(socket, event) {
        var mouse;
        if(settings.mobile){
            mouse = {
                x: Math.round(event.clientX * pixelDensityRatio),
                y: Math.round(event.clientY * pixelDensityRatio)
            };
        }
        else {
            mouse = {
                x: Math.round(event.x * pixelDensityRatio),
                y: Math.round(event.y * pixelDensityRatio)
            };
        };
        var angle = Math.floor(Math.atan2(mouse.y - center.y, mouse.x - center.x) * 100)/100;
        socket.emit('angle', angle);
    }
    function emitMouseClick(click){
        if(click){
            socket.emit('controls',{inputId:'click',state:true});
        }
        else{
            socket.emit('controls',{inputId:'click',state:false});
        }
    }
}
 

Node.js Код сервера с Redis

 const os            = require('os'),
      cluster       = require('cluster'),
      cores         = os.cpus();
var clusterCount = 0;

process.on('uncaughtException', function (exception) {
  console.log(exception); // to see your exception details in the console
  // if you are on production, maybe you can send the exception details to your
  // email as well ?
});

if(cluster.isMaster) {
    console.log(`Master ${process.pid} is running`);    
    // Fork workers
    for (let i = 0; i < cores.length; i  ) {
        cluster.fork();
    }
    cluster.on('exit', (worker, code, signal) => {
        console.log(`worker ${worker.process.pid} died`);
    });
}
else{
    const http          = require('http'),
          express       = require('express'),
          socketio      = require('socket.io'),
          process       = require('process'),
          config        = require('./config.js'),
          socketioRedis = require('socket.io-redis'),
          redis         = require('./redis.js');
    
    const settings = {
        cps: 60
    };
    
    var LOBBY_LIST = {};
    
    redis.subscriber.on('connect', () => {
        console.log(`worker ${process.pid} connected to redis(subscriber)`);
        
        redis.subscriber.subscribe('LOBBIES');
        
        redis.subscriber.on('message', (channel, data) => {
            var parsedData = JSON.parse(data);
            switch(channel){
                case 'LOBBIES': {
                    //console.log(`Lobby -- ${parsedData.title} | ${parsedData.map} | ${parsedData.mode} | ${parsedData.max}`);
                    LOBBY_LIST[parsedData.title] = parsedData;
                    break;
                }
                default: {
                    break;
                }
            }
        });
        redis.subscriber.on('users', (channel, data) => {
            console.log(`${data.id} connected to worker ${data.server}`);
        });
    });
    redis.publisher.on('connect', () => {
        console.log(`worker ${process.pid} connected to redis(publisher)`);
        
        var cpu = cores[clusterCount];
        var app = express();
        var port = process.env.PORT || process.argv[2] || 8080;
        var server = app.listen(port);
        var io = socketio(server);
        var User = {};
        var SOCKET_LIST = {};
        var PLAYER_LIST = {};
        
        io.adapter(socketioRedis({host: config.redis_host, port: config.redis_port}));
        io.on('connection', (socket) => {
            User.onConnect(socket);
        });
        User.onConnect = (socket) => {
            console.log(`${socket.id} connected to worker ${process.pid}`);
            var id = socket.id;
            var player = new Player(id);
            
            SOCKET_LIST[id] = socket;
            SOCKET_LIST[id].spamGuard = 0;
            
            var updateLobbies = setInterval(() => {
                socket.emit('updateLobbies', LOBBY_LIST);
            }, 3000);
            
            socket.emit('gameID', id);
            
            socket.on('serverPing', (ping) => {
                socket.emit('serverPong', ping);
            });
            socket.on('sendServerChat', (data) => {
                if(data != ' ' amp;amp; data != '  ' amp;amp; data != '   '){
                    if(SOCKET_LIST[socket.id].spamGuard < 3){
                        if(data.length < 100){
                            var chat = {
                                user: socket.id,
                                msg: data
                            };
                            io.sockets.emit('sendClientChat', chat);
                            SOCKET_LIST[socket.id].spamGuard  ;
                        }
                    }
                    else{
                        var chat = {
                            user: 'server',
                            msg: 'You're sending messages too fast!'
                        };
                        socket.emit('sendClientChat', chat);
                    }
                }
                else {
                    var chat = {
                        user: 'server',
                        msg: 'Can't send an empty message!'
                    };
                    socket.emit('sendClientChat', chat);
                }
            });
            socket.on('lobby.join', (lobby) => {
                var user = {
                    id: socket.id,
                    lobby: lobby
                };
                redis.publisher.publish('JOINROOM', JSON.stringify(user));
                
                socket.emit('lobbyinfo', map[LOBBY_LIST[lobby].map]);
            });
            socket.on('lobby.leave', () => {
                var user = {
                    id: socket.id,
                    lobby: lobby
                };
                redis.publisher.publish('LEAVEROOM', JSON.stringify(user));
            });
            socket.on('disconnect', () => {
                User.onDisconnect(socket);
            });
            socket.on('controls', (data) => {
                if(data.inputId === 'up'){//UP
                    player.controls.pressingUp = data.state;
                }
                else if(data.inputId === 'down'){//DOWN
                    player.controls.pressingDown = data.state;
                }
                else if(data.inputId === 'left'){//LEFT
                    player.controls.pressingLeft = data.state;
                }
                else if(data.inputId === 'right'){//RIGHT
                    player.controls.pressingRight = data.state;
                }
                else if(data.inputId === 'Q'){//Q
                    player.controls.pressingQ = data.state;
                }
                else if(data.inputId === 'E'){//E
                    player.controls.pressingE = data.state;
                }
                else if(data.inputId === 'R'){//R
                    player.controls.pressingR = data.state;
                }
                else if(data.inputId === 'shift'){//SHIFT
                    player.controls.pressingSHIFT = data.state;
                }
                else if(data.inputId === 'space'){//SPACE
                    player.controls.pressingSPACE = data.state;
                }
                
                if(data.inputId === 'click'){//Click
                    player.controls.leftClick = data.state;
                }
            });
            socket.on('angle', (data) => {
                player.angle = data;
            });
        };
        User.onDisconnect = (socket) => {
            console.log(`${socket.id} disconnected from worker ${process.pid}`);
            delete SOCKET_LIST[socket.id];
            delete PLAYER_LIST[socket.id];
            socket.disconnect();
        };
        
        var Player = function(id){
            var self = {
                id: id,
                username: 'player',
                server: '',
                position: {
                    x: 0,
                    y: 0
                },
                speed: 500,
                angle: degrees_to_radians(Math.floor(Math.random()*360)),
                controls: {
                    leftClick:      false,
                    pressingUp:     false,
                    pressingDown:   false,
                    pressingLeft:   false,
                    pressingRight:  false,
                    pressingQ:      false,
                    pressingE:      false,
                    pressingR:      false,
                    pressingSHIFT:  false,
                    pressingSPACE:  false
                }
            };
            self.updatePosition = function(){
                if(self.controls.pressingUp){
                    self.position.y -= self.speed / settings.cps;
                    if(self.position.y < 0){
                        self.position.y = 0;
                    }
                }
                if(self.controls.pressingDown){
                    self.position.y  = self.speed / settings.cps;
                    if(self.position.y > 9999){
                        self.position.y = 9999;
                    }
                }
                if(self.controls.pressingLeft){
                    self.position.x -= self.speed / settings.cps;
                    if(self.position.x < 0){
                        self.position.x = 0;
                    }
                }
                if(self.controls.pressingRight){
                    self.position.x  = self.speed / settings.cps;
                    if(self.position.x > 9999){
                        self.position.x = 9999;
                    }
                }
            };
            
            PLAYER_LIST[self.id] = self;
            return self;
        };
        Player.update = function(){     
            var pack = [];
            for(var i in PLAYER_LIST){
                var player = PLAYER_LIST[PLAYER_LIST[i].id];
                
                player.updatePosition();
                
                pack.push({
                    id: player.id,
                    position: player.position,
                    angle: player.angle
                });
            }
            return pack;
        };
        
        
        //Spam Guard
        setInterval(() => {
            for(var id in SOCKET_LIST){
                if(SOCKET_LIST[id].spamGuard > 0){
                    SOCKET_LIST[id].spamGuard--;
                }
            }
        }, 3000);
        //Update LOBBIES
        setInterval(() => {
            //for(var L in LOBBY_LIST){
            //  var lobby = LOBBY_LIST[L];
                checkCPS();
                
                var pack = {
                    players: Player.update(),
                    server: {
                        cps: cpsSave
                    }
                };
                
                for(var S in SOCKET_LIST){
                    var user = SOCKET_LIST[SOCKET_LIST[S].id];
                    
                    user.emit('refresh', pack);
                }
            //}
        }, 1000/settings.cps);
        
        var map = [
            'Urban Delight',
            'Graveyard',
            'Farmlands'
        ];
        for(var i in map){
            var mapName = map[i];
            
            map[mapName] = {
                tileSize: 100,
                matrix: [],
            };
            for(var j = 0; j < 100; j  ){
                map[mapName].matrix[j] = [];
                for(var k = 0; k < 100; k  ){
                    map[mapName].matrix[j][k] = 0;
                }
            }
        }       
        function degrees_to_radians(degrees){
            var pi = Math.PI;
            return degrees * (pi/180);
        }
        function radians_to_degrees(radians){
            var pi = Math.PI;
            return radians / (pi/180);
        }
        var cps = 0,
            cpsCheck = 0,
            cpsSave = 0;
        function checkCPS(){
            cps  ;
            var d = new Date().getSeconds();
            if(cpsCheck != d){
                cpsCheck = d;
                cpsSave = cps;
                cps = 0;
            }
        }
        console.log(`Worker ${process.pid} started on port: ${port} | ${cpu.model}`);   
        clusterCount  ;
    });
}
 

Комментарии:

1. Одна из проблем, которую я вижу, заключается в том, что вы создаете setInterval() таймер при подключении пользователя, но вы НИКОГДА не останавливаете этот таймер, поэтому при каждом новом подключении вы создаете еще один интервальный таймер, и даже после нескольких обновлений от одного пользователя (не говоря уже о том, что пользователей много) у вас будет кучавсе интервальные таймеры запущены. Фактически, этот таймер пытается продолжать работать socket.emit('updateLobbies', ...) даже спустя долгое время после socket отключения.

2. Вы должны либо совместно использовать один таймер для всех активно подключенных пользователей, либо clearInterval() для определенного таймера сокета каждый раз, когда сокет отключается.

3. @jfriend00 хороший глаз! Я поместил setInterva()l for LOBBY_LIST рядом с другими setInterval() s вне соединения и испускаю using io.sockets.emit . Спасибо за помощь, но проблема сохраняется :/

4. Я не понимаю, о какой проблеме вы говорите. Что должно выполняться со скоростью 60cps? И какое отношение angle событие имеет ко всему этому?

5. Итак, вы говорите, что клиент получает только refresh событие (отправленное из ЛОББИ обновления setInterval()) со скоростью 34 cps, пока сервер не получит angle событие, а затем оно перейдет на 60 cps? Это то, что ты говоришь?