#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
forLOBBY_LIST
рядом с другимиsetInterval()
s вне соединения и испускаю usingio.sockets.emit
. Спасибо за помощь, но проблема сохраняется :/4. Я не понимаю, о какой проблеме вы говорите. Что должно выполняться со скоростью 60cps? И какое отношение
angle
событие имеет ко всему этому?5. Итак, вы говорите, что клиент получает только
refresh
событие (отправленное из ЛОББИ обновления setInterval()) со скоростью 34 cps, пока сервер не получитangle
событие, а затем оно перейдет на 60 cps? Это то, что ты говоришь?