app.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. const dgram = require('dgram');
  2. const server = dgram.createSocket('udp4');
  3. var net = require('net');
  4. var events = require('events');
  5. var uuid = require('uuid');
  6. var _ = require('lodash');
  7. var messageEventEmitter = new events.EventEmitter();
  8. var connections = [];
  9. var serverList = [];
  10. var MASTER_SERVER_PORT = 41234;
  11. // -------------------------------------------------------------------
  12. //
  13. // Atomic Master Server
  14. //
  15. //
  16. // This server is designed to work with the MasterServerClient
  17. // in the Atomic Game Engine to allow multiplayer servers to register
  18. // themselves, in order to be discovered by multiplayer clients.
  19. //
  20. // It also handles NAT punchthrough in case the server is behind
  21. // a NAT firewall.
  22. //
  23. // -------------------------------------------------------------------
  24. // -------------------------------------------------------------------
  25. // UDP Server
  26. //
  27. // We use a UDP Socket to listen for messages from both clients
  28. // and servers. This way we can identify their external UDP ports
  29. // assigned by any NAT firewall they might be behind.
  30. //
  31. // We do not send any UDP packets back. We acknowledge that we
  32. // received the UDP port by sending a message back on the TCP socket.
  33. //
  34. // -------------------------------------------------------------------
  35. function writeMessageToSocket(sock, msgObj) {
  36. var msg = JSON.stringify(msgObj);
  37. var len = msg.length;
  38. sock.write(len.toString());
  39. sock.write(':');
  40. sock.write(msg);
  41. }
  42. function handleServerUDPMessage(rinfo, msgObj) {
  43. console.log('Processing UDP message: ' + JSON.stringify(msgObj));
  44. if (msgObj.cmd === 'registerUDPPort') {
  45. var connectionId = msgObj.id;
  46. var connectionObj = _.find(connections, { connectionId: connectionId });
  47. if (!connectionObj) {
  48. console.error('No server found for id: '+connectionId);
  49. return;
  50. }
  51. // Save the UDP port
  52. connectionObj.externalUDPPort = rinfo.port;
  53. // Send the success message
  54. var udpPortMessage = {
  55. cmd: 'connectUDPSuccess'
  56. };
  57. writeMessageToSocket(connectionObj.tcpSocket, udpPortMessage);
  58. console.log('Got udp port:' + connectionObj.externalUDPPort);
  59. } else {
  60. console.log('Unable to process message: ' + msg)
  61. }
  62. }
  63. // Set up UDP
  64. server.on('error', function(err) {
  65. console.log("server error: "+ err.stack);
  66. server.close();
  67. });
  68. server.on('message', function (msg, rinfo) {
  69. console.log('Received %d bytes from %s:%d\n', msg.length, rinfo.address, rinfo.port);
  70. try {
  71. var msgObj = JSON.parse(msg);
  72. handleServerUDPMessage(rinfo, msgObj);
  73. } catch (err) {
  74. console.error(err);
  75. console.error(err.stack);
  76. console.log('Unable to parse JSON from UDP: ' + message)
  77. }
  78. });
  79. server.on('listening', function () {
  80. var address = server.address();
  81. console.log("udp server listening on "+address.address + ":" + address.port);
  82. });
  83. server.bind(MASTER_SERVER_PORT);
  84. // -------------------------------------------------------------------
  85. // TCP Server
  86. //
  87. // We use a TCP connection to handle requests from clients and from
  88. // servers. If the server TCP connection dies, we remove the server
  89. // from our list.
  90. //
  91. // Messages are received in a simplified 'netstring' format, where
  92. // the message length is followed by a colon, and then the actual
  93. // message.
  94. //
  95. // Example: "13:Hello, World!"
  96. //
  97. // Messages may not be complete until multiple data calls have
  98. // been made. Once we have a complete message, we fire the 'msg'
  99. // event on an event emitter and handle the message.
  100. // -------------------------------------------------------------------
  101. function handleServerTCPMessage(socket, msgObj) {
  102. console.log('Processing TCP message: ' + JSON.stringify(msgObj));
  103. if (msgObj.cmd === 'connectRequest') {
  104. var connectionId = uuid.v4();
  105. var connectionObj = {
  106. connectionId: connectionId,
  107. internalIP: msgObj.internalIP,
  108. internalPort: msgObj.internalPort,
  109. externalIP: socket.remoteAddress,
  110. externalTCPPort: socket.remotePort,
  111. tcpSocket: socket
  112. };
  113. connections.push(connectionObj);
  114. // Send the uuid back to the game server
  115. var registerSuccessMessage = {
  116. cmd: 'connectTCPSuccess',
  117. id: connectionId
  118. };
  119. writeMessageToSocket(socket, registerSuccessMessage);
  120. console.log('Registered connection from IP:' + connectionObj.externalIP);
  121. } else if (msgObj.cmd === 'getServerList' ) {
  122. var servers = _.map(serverList, function (item) {
  123. return _.pick(item, ['connectionId', 'internalIP', 'internalPort', 'externalIP', 'externalUDPPort', 'serverName' ]);
  124. })
  125. var response = {
  126. cmd: 'serverList',
  127. servers: JSON.stringify(servers)
  128. }
  129. writeMessageToSocket(socket, response);
  130. } else if (msgObj.cmd === 'registerServer' ) {
  131. var connectionInfo = _.find(connections, { connectionId: msgObj.id });
  132. if (!connectionInfo) {
  133. console.error("No server found: " + msgObj.id);
  134. }
  135. var serverInfo = _.clone(connectionInfo);
  136. serverInfo.serverName = msgObj.serverName;
  137. serverInfo.internalIP = msgObj.internalIP;
  138. serverInfo.internalPort = msgObj.internalPort;
  139. serverList.push(serverInfo);
  140. console.log('Registered server: ' + serverInfo.serverName);
  141. } else if (msgObj.cmd === 'requestIntroduction' ) {
  142. var clientInfo = _.find(connections, { connectionId: msgObj.id });
  143. if (!clientInfo) {
  144. return;
  145. console.error("No client found: " + msgObj.id);
  146. }
  147. var serverInfo = _.find(connections, { connectionId: msgObj.serverId });
  148. if (!serverInfo) {
  149. console.error("No client found: " + msgObj.id);
  150. return;
  151. }
  152. // Send introduction request to server
  153. var response = {
  154. cmd: 'sendPacketToClient',
  155. clientId: clientInfo.connectionId,
  156. clientIP: clientInfo.externalIP,
  157. clientPort: clientInfo.externalUDPPort
  158. }
  159. // Send this to the server
  160. writeMessageToSocket(serverInfo.tcpSocket, response);
  161. } else {
  162. console.log('Unable to process message: ' + msg)
  163. }
  164. }
  165. messageEventEmitter.addListener('msg', function(socket, message) {
  166. try {
  167. var msgObj = JSON.parse(message);
  168. handleServerTCPMessage(socket, msgObj);
  169. } catch (err) {
  170. console.error(err);
  171. console.error(err.stack);
  172. console.log('Unable to parse JSON: ' + message)
  173. }
  174. });
  175. function onDisconnect(sock) {
  176. }
  177. console.log('Setting up tcp');
  178. var tcpServer = net.createServer();
  179. tcpServer.listen(MASTER_SERVER_PORT,'0.0.0.0');
  180. tcpServer.on('connection', function(sock) {
  181. console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort);
  182. // Set keep alive true so we can detect when a client or server has died
  183. sock.setKeepAlive(true);
  184. var readingLength = true;
  185. var messageLengthStr = '';
  186. var bytesRemainingInMasterServerMessage = 0;
  187. var messageStr = '';
  188. // Clean up on disconnect
  189. sock.on('end', function() {
  190. });
  191. sock.on('data', function (_data) {
  192. var data = _data.toString();
  193. var n=0;
  194. var totalBytes = data.length;
  195. while (n < totalBytes) {
  196. var c = data[n++];
  197. // Are we still reading in the length?
  198. if (readingLength) {
  199. if (c == ':') {
  200. bytesRemainingInMasterServerMessage = Number(messageLengthStr);
  201. readingLength = false;
  202. }
  203. else {
  204. messageLengthStr+=c;
  205. }
  206. }
  207. else {
  208. // Are we reading in the string?
  209. messageStr+=c;
  210. bytesRemainingInMasterServerMessage--;
  211. // Did we hit the end of the string?
  212. if (bytesRemainingInMasterServerMessage==0) {
  213. messageEventEmitter.emit('msg', sock, messageStr);
  214. readingLength = true;
  215. messageLengthStr='';
  216. messageStr='';
  217. }
  218. }
  219. }
  220. });
  221. sock.on('error', function(error) {
  222. console.log('Got TCP error: '+error);
  223. });
  224. });