Add server files
This commit is contained in:
362
WebsocketNetworkServer.js
Normal file
362
WebsocketNetworkServer.js
Normal file
@ -0,0 +1,362 @@
|
||||
"use strict";
|
||||
var ws = require('ws');
|
||||
var inet = require('./INetwork');
|
||||
var WebsocketNetworkServer = (function () {
|
||||
function WebsocketNetworkServer() {
|
||||
this.mPool = {};
|
||||
}
|
||||
WebsocketNetworkServer.log = function (msg) {
|
||||
console.log("(" + new Date().toISOString() + ")" + msg);
|
||||
};
|
||||
WebsocketNetworkServer.prototype.onConnection = function (socket, appname) {
|
||||
//it would be possible to enforce the client to send a certain introduction first
|
||||
//to determine to which pool we add it -> for now only one pool is supported
|
||||
this.mPool[appname].add(socket);
|
||||
};
|
||||
//
|
||||
WebsocketNetworkServer.prototype.addSocketServer = function (websocketServer, appConfig) {
|
||||
var _this = this;
|
||||
if (this.mPool[appConfig.name] == null) {
|
||||
this.mPool[appConfig.name] = new PeerPool(appConfig);
|
||||
}
|
||||
var name = appConfig.name;
|
||||
websocketServer.on('connection', function (socket) { _this.onConnection(socket, name); });
|
||||
};
|
||||
return WebsocketNetworkServer;
|
||||
}());
|
||||
exports.WebsocketNetworkServer = WebsocketNetworkServer;
|
||||
;
|
||||
//Pool of client connects that are allowed to communicate to each other
|
||||
var PeerPool = (function () {
|
||||
function PeerPool(config) {
|
||||
this.mConnections = new Array();
|
||||
this.mServers = {};
|
||||
this.mAddressSharing = false;
|
||||
this.maxAddressLength = 256;
|
||||
this.mAppConfig = config;
|
||||
if (this.mAppConfig.address_sharing) {
|
||||
this.mAddressSharing = this.mAppConfig.address_sharing;
|
||||
}
|
||||
}
|
||||
PeerPool.prototype.hasAddressSharing = function () {
|
||||
return this.mAddressSharing;
|
||||
};
|
||||
//add a new connection based on this websocket
|
||||
PeerPool.prototype.add = function (socket) {
|
||||
this.mConnections.push(new SignalingPeer(this, socket));
|
||||
};
|
||||
//Returns the SignalingClientConnection that opened a server using the given address
|
||||
//or null if address not in use
|
||||
PeerPool.prototype.getServerConnection = function (address) {
|
||||
return this.mServers[address];
|
||||
};
|
||||
//Tests if the address is available for use.
|
||||
//returns true in the following cases
|
||||
//the address is longer than the maxAddressLength and the server the address is not yet in use or address sharing is active
|
||||
PeerPool.prototype.isAddressAvailable = function (address) {
|
||||
if (address.length <= this.maxAddressLength // only allow addresses shorter than maxAddressLength
|
||||
&& (this.mServers[address] == null || this.mAddressSharing)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
//Adds the server. No checking is performed here! logic should be solely in the connection class
|
||||
PeerPool.prototype.addServer = function (client, address) {
|
||||
if (this.mServers[address] == null) {
|
||||
this.mServers[address] = new Array();
|
||||
}
|
||||
this.mServers[address].push(client);
|
||||
};
|
||||
//Removes an address from the server. No checks performed
|
||||
PeerPool.prototype.removeServer = function (client, address) {
|
||||
//supports address sharing. remove the client from the server list that share the address
|
||||
var index = this.mServers[address].indexOf(client);
|
||||
if (index != -1) {
|
||||
this.mServers[address].splice(index, 1);
|
||||
}
|
||||
//delete the whole list if the last one left
|
||||
if (this.mServers[address].length == 0) {
|
||||
delete this.mServers[address];
|
||||
}
|
||||
};
|
||||
//Removes a given connection from the pool
|
||||
PeerPool.prototype.removeConnection = function (client) {
|
||||
var index = this.mConnections.indexOf(client);
|
||||
if (index != -1) {
|
||||
this.mConnections.splice(index, 1);
|
||||
}
|
||||
else {
|
||||
console.warn("Tried to remove unknown SignalingClientConnection. Bug?" + client);
|
||||
}
|
||||
};
|
||||
PeerPool.prototype.count = function () {
|
||||
return this.mConnections.length;
|
||||
};
|
||||
return PeerPool;
|
||||
}());
|
||||
var SignalingConnectionState;
|
||||
(function (SignalingConnectionState) {
|
||||
SignalingConnectionState[SignalingConnectionState["Uninitialized"] = 0] = "Uninitialized";
|
||||
SignalingConnectionState[SignalingConnectionState["Connecting"] = 1] = "Connecting";
|
||||
SignalingConnectionState[SignalingConnectionState["Connected"] = 2] = "Connected";
|
||||
SignalingConnectionState[SignalingConnectionState["Disconnecting"] = 3] = "Disconnecting";
|
||||
SignalingConnectionState[SignalingConnectionState["Disconnected"] = 4] = "Disconnected"; //means the instance is destroyed and unusable
|
||||
})(SignalingConnectionState || (SignalingConnectionState = {}));
|
||||
;
|
||||
///note: all methods starting with "internal" might leave the system in an inconsistent state
|
||||
///e.g. peerA is connected to peerB means peerB is connected to peerA but internalRemoveConnection
|
||||
///could cause peerA being disconnected from peerB but peerB still thinking to be connected to peerA!!!
|
||||
var SignalingPeer = (function () {
|
||||
function SignalingPeer(pool, socket) {
|
||||
var _this = this;
|
||||
this.mState = SignalingConnectionState.Uninitialized;
|
||||
this.mConnections = {};
|
||||
//C# version uses short so 16384 is 50% of the positive numbers (maybe might make sense to change to ushort or int)
|
||||
this.mNextIncomingConnectionId = new inet.ConnectionId(16384);
|
||||
this.mConInfo = "[con info missing]";
|
||||
this.mConnectionPool = pool;
|
||||
this.mSocket = socket;
|
||||
//(this.mSocket as any).maxPayload = 16;
|
||||
this.mState = SignalingConnectionState.Connecting;
|
||||
this.mConInfo = this.mSocket.upgradeReq.connection.remoteAddress + ":" + this.mSocket.upgradeReq.connection.remotePort;
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + " connected ");
|
||||
socket.on('message', function (message, flags) {
|
||||
_this.onMessage(message, flags);
|
||||
});
|
||||
socket.on('error', function (error) {
|
||||
console.error(error);
|
||||
});
|
||||
socket.on('close', function (code, message) { _this.onClose(code, message); });
|
||||
socket.on('pong', function (data, flags) {
|
||||
WebsocketNetworkServer.log("[" + _this.mConInfo + "]" + "INC: pong ");
|
||||
});
|
||||
this.mState = SignalingConnectionState.Connected;
|
||||
this.mPingInterval = setInterval(function () { _this.doPing(); }, 30000);
|
||||
}
|
||||
SignalingPeer.prototype.doPing = function () {
|
||||
if (this.mState == SignalingConnectionState.Connected && this.mSocket.readyState == ws.OPEN) {
|
||||
this.mSocket.ping();
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "OUT: ping");
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.evtToString = function (evt) {
|
||||
var output = "[";
|
||||
output += "NetEventType: (";
|
||||
output += inet.NetEventType[evt.Type];
|
||||
output += "), id: (";
|
||||
output += evt.ConnectionId.id;
|
||||
if (evt.Info != null) {
|
||||
output += "), Data: (";
|
||||
output += evt.Info;
|
||||
}
|
||||
else if (evt.MessageData != null) {
|
||||
var chars = new Uint16Array(evt.MessageData.buffer, evt.MessageData.byteOffset, evt.MessageData.byteLength / 2);
|
||||
output += "), Data: (";
|
||||
var binaryString = "";
|
||||
for (var i = 0; i < chars.length; i++) {
|
||||
binaryString += String.fromCharCode(chars[i]);
|
||||
}
|
||||
output += binaryString;
|
||||
}
|
||||
output += ")]";
|
||||
return output;
|
||||
};
|
||||
SignalingPeer.prototype.onMessage = function (message, flags) {
|
||||
try {
|
||||
//unlike browsers ws will give a Uint8Array
|
||||
var evt = inet.NetworkEvent.fromByteArray(message);
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "INC: " + this.evtToString(evt));
|
||||
this.handleIncomingEvent(evt);
|
||||
}
|
||||
catch (err) {
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "Invalid message received: " + message + " \n Error: " + err);
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.sendToClient = function (evt) {
|
||||
//this method is also called during cleanup after a disconnect
|
||||
//check first if we are still connected
|
||||
//bugfix: apprently 2 sockets can be closed at exactly the same time without
|
||||
//onclosed being called immediately -> socket has to be checked if open
|
||||
if (this.mState == SignalingConnectionState.Connected
|
||||
&& this.mSocket.readyState == this.mSocket.OPEN) {
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "OUT: " + this.evtToString(evt));
|
||||
var msg = inet.NetworkEvent.toByteArray(evt);
|
||||
this.mSocket.send(msg);
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.onClose = function (code, error) {
|
||||
this.mState = SignalingConnectionState.Disconnecting;
|
||||
this.Cleanup();
|
||||
};
|
||||
//so far only used if the socket was disconnected
|
||||
SignalingPeer.prototype.Cleanup = function () {
|
||||
if (this.mPingInterval != null) {
|
||||
clearInterval(this.mPingInterval);
|
||||
}
|
||||
this.mConnectionPool.removeConnection(this);
|
||||
WebsocketNetworkServer.log("[" + this.mConInfo + "]" + "disconnected "
|
||||
+ " " + this.mConnectionPool.count()
|
||||
+ " connections left.");
|
||||
//disconnect all connections
|
||||
var test = this.mConnections; //workaround for not having a proper dictionary yet...
|
||||
for (var v in this.mConnections) {
|
||||
if (this.mConnections.hasOwnProperty(v))
|
||||
this.disconnect(new inet.ConnectionId(+v));
|
||||
}
|
||||
//make sure the server address is freed
|
||||
if (this.mServerAddress != null) {
|
||||
this.stopServer();
|
||||
}
|
||||
this.mState = SignalingConnectionState.Disconnected;
|
||||
};
|
||||
SignalingPeer.prototype.handleIncomingEvent = function (evt) {
|
||||
//update internal state based on the event
|
||||
if (evt.Type == inet.NetEventType.NewConnection) {
|
||||
//client wants to connect to another client
|
||||
var address = evt.Info;
|
||||
//the id this connection should be addressed with
|
||||
var newConnectionId = evt.ConnectionId;
|
||||
this.connect(address, newConnectionId);
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.ConnectionFailed) {
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.Disconnected) {
|
||||
//peer tries to disconnect from another peer
|
||||
var otherPeerId = evt.ConnectionId;
|
||||
this.disconnect(otherPeerId);
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.ServerInitialized) {
|
||||
this.startServer(evt.Info);
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.ServerInitFailed) {
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.ServerClosed) {
|
||||
//stop server request
|
||||
this.stopServer();
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.ReliableMessageReceived) {
|
||||
this.sendData(evt.ConnectionId, evt.MessageData, true);
|
||||
}
|
||||
else if (evt.Type == inet.NetEventType.UnreliableMessageReceived) {
|
||||
this.sendData(evt.ConnectionId, evt.MessageData, false);
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.internalAddIncomingPeer = function (peer) {
|
||||
//another peer connected to this (while allowing incoming connections)
|
||||
//store the reference
|
||||
var id = this.nextConnectionId();
|
||||
this.mConnections[id.id] = peer;
|
||||
//event to this (the other peer gets the event via addOutgoing
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.NewConnection, id, null));
|
||||
};
|
||||
SignalingPeer.prototype.internalAddOutgoingPeer = function (peer, id) {
|
||||
//this peer successfully connected to another peer. id was generated on the
|
||||
//client side
|
||||
this.mConnections[id.id] = peer;
|
||||
//event to this (the other peer gets the event via addOutgoing
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.NewConnection, id, null));
|
||||
};
|
||||
SignalingPeer.prototype.internalRemovePeer = function (id) {
|
||||
delete this.mConnections[id.id];
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.Disconnected, id, null));
|
||||
};
|
||||
//test this. might cause problems
|
||||
//the number is converted to string trough java script but we need get back the number
|
||||
//for creating the connection id
|
||||
SignalingPeer.prototype.findPeerConnectionId = function (otherPeer) {
|
||||
for (var peer in this.mConnections) {
|
||||
if (this.mConnections[peer] === otherPeer) {
|
||||
return new inet.ConnectionId(+peer);
|
||||
}
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.nextConnectionId = function () {
|
||||
var result = this.mNextIncomingConnectionId;
|
||||
this.mNextIncomingConnectionId = new inet.ConnectionId(this.mNextIncomingConnectionId.id + 1);
|
||||
return result;
|
||||
};
|
||||
//public methods (not really needed but can be used for testing or server side deubgging)
|
||||
//this peer initializes a connection to a certain address. The connection id is set by the client
|
||||
//to allow tracking of the connection attempt
|
||||
SignalingPeer.prototype.connect = function (address, newConnectionId) {
|
||||
var serverConnections = this.mConnectionPool.getServerConnection(address);
|
||||
//
|
||||
if (serverConnections != null && serverConnections.length == 1) {
|
||||
//inform the server connection about the new peer
|
||||
//events will be send by these methods
|
||||
//shared addresses -> connect to everyone listening
|
||||
serverConnections[0].internalAddIncomingPeer(this);
|
||||
this.internalAddOutgoingPeer(serverConnections[0], newConnectionId);
|
||||
}
|
||||
else {
|
||||
//if address is not in use or it is in multi join mode -> connection fails
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ConnectionFailed, newConnectionId, null));
|
||||
}
|
||||
};
|
||||
//join connection happens if another user joins a multi address. it will connect to every address
|
||||
//listening to that room
|
||||
SignalingPeer.prototype.connectJoin = function (address) {
|
||||
var serverConnections = this.mConnectionPool.getServerConnection(address);
|
||||
//in join mode every connection is incoming as everyone listens together
|
||||
if (serverConnections != null) {
|
||||
for (var _i = 0, serverConnections_1 = serverConnections; _i < serverConnections_1.length; _i++) {
|
||||
var v = serverConnections_1[_i];
|
||||
if (v != this) {
|
||||
v.internalAddIncomingPeer(this);
|
||||
this.internalAddIncomingPeer(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.disconnect = function (connectionId) {
|
||||
var otherPeer = this.mConnections[connectionId.id];
|
||||
if (otherPeer != null) {
|
||||
var idOfOther = otherPeer.findPeerConnectionId(this);
|
||||
//find the connection id the other peer uses to talk to this one
|
||||
this.internalRemovePeer(connectionId);
|
||||
otherPeer.internalRemovePeer(idOfOther);
|
||||
}
|
||||
else {
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.startServer = function (address) {
|
||||
//what to do if it is already a server?
|
||||
if (this.mServerAddress != null)
|
||||
this.stopServer();
|
||||
if (this.mConnectionPool.isAddressAvailable(address)) {
|
||||
this.mServerAddress = address;
|
||||
this.mConnectionPool.addServer(this, address);
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerInitialized, inet.ConnectionId.INVALID, address));
|
||||
if (this.mConnectionPool.hasAddressSharing()) {
|
||||
//address sharing is active. connect to every endpoint already listening on this address
|
||||
this.connectJoin(address);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerInitFailed, inet.ConnectionId.INVALID, address));
|
||||
}
|
||||
};
|
||||
SignalingPeer.prototype.stopServer = function () {
|
||||
if (this.mServerAddress != null) {
|
||||
this.mConnectionPool.removeServer(this, this.mServerAddress);
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ServerClosed, inet.ConnectionId.INVALID, null));
|
||||
this.mServerAddress = null;
|
||||
}
|
||||
//do nothing if it wasnt a server
|
||||
};
|
||||
//delivers the message to the local peer
|
||||
SignalingPeer.prototype.forwardMessage = function (senderPeer, msg, reliable) {
|
||||
var id = this.findPeerConnectionId(senderPeer);
|
||||
if (reliable)
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.ReliableMessageReceived, id, msg));
|
||||
else
|
||||
this.sendToClient(new inet.NetworkEvent(inet.NetEventType.UnreliableMessageReceived, id, msg));
|
||||
};
|
||||
SignalingPeer.prototype.sendData = function (id, msg, reliable) {
|
||||
var peer = this.mConnections[id.id];
|
||||
if (peer != null)
|
||||
peer.forwardMessage(this, msg, reliable);
|
||||
};
|
||||
return SignalingPeer;
|
||||
}());
|
||||
//# sourceMappingURL=WebsocketNetworkServer.js.map
|
Reference in New Issue
Block a user