我的服务器表现得很奇怪 -- setInterval
下的更新 Update LOBBIES
设置为 @ 1000/60ms 或 60cps,我只得到稳定的 34cps。但是当从 socket.emit('angle')
侧使用 CLIENT
时,它以 60cps 平稳运行,直到 socket.emit('angle')
停止,然后返回到 34cps。已经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 && (event.keyCode === 87 || event.keyCode == 38)){//UP
controlReady.up = false;
socket.emit('controls',{inputId:'up',state:true});
}
if(controlReady.down && (event.keyCode == 83 || event.keyCode == 40)){//DOWN
controlReady.down = false;
socket.emit('controls',{inputId:'down',state:true});
}
if(controlReady.left && (event.keyCode == 65 || event.keyCode == 37)){//LEFT
controlReady.left = false;
socket.emit('controls',{inputId:'left',state:true});
}
if(controlReady.right && (event.keyCode == 68 || event.keyCode == 39)){//RIGHT
controlReady.right = false;
socket.emit('controls',{inputId:'right',state:true});
}
if(controlReady.Q && event.keyCode == 81){//Q
controlReady.Q = false;
socket.emit('controls',{inputId:'Q',state:true});
}
if(controlReady.E && event.keyCode == 69){//E
controlReady.E = false;
socket.emit('controls',{inputId:'E',state:true});
}
if(controlReady.R && event.keyCode == 82){//R
controlReady.R = false;
socket.emit('controls',{inputId:'R',state:true});
}
if(controlReady.SHIFT && event.keyCode == 16){//SHIFT
controlReady.SHIFT = false;
socket.emit('controls',{inputId:'shift',state:true});
}
if(controlReady.SPACE && event.keyCode == 32){//SPACE
controlReady.SPACE = false;
socket.emit('controls',{inputId:'space',state:true});
}
});
document.addEventListener('keyup', (event) => {
if(!controlReady.up && (event.keyCode === 87 || event.keyCode == 38)){//UP
controlReady.up = true;
socket.emit('controls',{inputId:'up',state:false});
}
if(!controlReady.down && (event.keyCode == 83 || event.keyCode == 40)){//DOWN
controlReady.down = true;
socket.emit('controls',{inputId:'down',state:false});
}
if(!controlReady.left && (event.keyCode == 65 || event.keyCode == 37)){//LEFT
controlReady.left = true;
socket.emit('controls',{inputId:'left',state:false});
}
if(!controlReady.right && (event.keyCode == 68 || event.keyCode == 39)){//RIGHT
controlReady.right = true;
socket.emit('controls',{inputId:'right',state:false});
}
if(!controlReady.Q && event.keyCode == 81){//Q
controlReady.Q = true;
socket.emit('controls',{inputId:'Q',state:false});
}
if(!controlReady.E && event.keyCode == 69){//E
controlReady.E = true;
socket.emit('controls',{inputId:'E',state:false});
}
if(!controlReady.R && event.keyCode == 82){//R
controlReady.R = true;
socket.emit('controls',{inputId:'R',state:false});
}
if(!controlReady.SHIFT && event.keyCode == 16){//false
controlReady.SHIFT = true;
socket.emit('controls',{inputId:'shift',state:false});
}
if(!controlReady.SPACE && 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});
}
}
}
带有 Redis 的 Node.js 服务器代码
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 != ' ' && data != ' ' && 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++;
});
}