Hey folks,
I’ve been developing on multiplayer game called Drag’n Slay on at least 4 different locations and it’s been a pain in the ass to change the ip address on the backend-server web and unity android client and unity editor client everytime manually so they will connect to the socket.io (tcp) and diagram (udp) server.
So had an idea. What if I could let the clients connect to my already payed webserver and get the fixed but changing ip.
I figured it would require a couple of steps to do so:
1) load ftp credentials from a git-ignored file
2) figuring out the lan and wan ip address
3) creating a php script that respond a json object with the accessable ip addresses to the server
4) uploading the php script to my always accessable webserver
5) loading the json file on the clients and connect to the server by using the dynamic ip address
1) [node] reading credentials
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | var fs = require('fs'); var StringDecoder = require('string_decoder').StringDecoder; var decoder = new StringDecoder('utf8'); var jsftp = require("jsftp"); fs.readFile( 'D:/sandbox/' + 'credentials.txt', function (err, data) { if (err) { throw err; } var pwd = JSON.parse(decoder.write(data)); ftp = new jsftp({ host: pwd.host, port: pwd.port, // defaults to 21 user: pwd.user, // defaults to "anonymous" pass: pwd.pass // defaults to "@anonymous" }); // upload current ip address as json for clients to get uploadFileByFtp(getIpAsJsonResponsePhp(), "/server.php", function() { // upload current ip address for server admin panel uploadFileByFtp(getServerAdminUrlPhp(), "/admin.php"); }); }); |
the credentials.txt:
1 2 3 4 5 6 | { "host": "serverftp.address", "port": "21", "user": "fancyadminname", "pass": "fancyftppassword" } |
2. [node] getting the ip address is quite easy with os.networkInterfaces()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var os=require('os'); var getIpAddress = function() { var ifaces = os.networkInterfaces(); var ips = {}; for (var dev in ifaces) { var alias=0; ifaces[dev].forEach(function(details){ if (details.family=='IPv4') { // console.log(dev+(alias?':'+alias:''),details.address); ips[dev+(alias?':'+alias:'')] = details.address; ++alias; } }); } ips['network_interface'] = server.get('network_interface'); // note: default set to server.set('network_interface', 'Wi-Fi'); return ips; }; |
3. [node] dynamically creating the script that will respond the ip addresses respectively Important: In order to allow cross domain loading of the json object set Access-Control-Allow-Origin: *
1 2 3 4 5 6 7 8 | var getIpAsJsonResponsePhp = function() { return "<?php header('Content-Type: application/json; charset=utf8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Max-Age: 3628800'); header('Access-Control-Allow-Methods: GET"+/*, POST, PUT, DELETE*/"'); echo '" + php_json_encode(JSON.stringify(getIpAddress())) + "'; ?>"; }; // (optional) the native php json_encode will escape / to \/ var php_json_encode = function(json) { return json.replace('/g', '\/'); }; |
I also made a short link to my node.js webserver default website:
1 2 3 | var getServerAdminUrlPhp = function() { return "<?php header('HTTP/1.1 301 Moved Permanently'); header('Location: http://"+getIpAddress()['Wi-Fi']+":"+server.get('tcp_port')+"/admin'); exit(); ?>"; }; |
4. [node] uploading the ip address to the server by using jsftp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | var uploadFileByFtp = function(filedata, targetUrl, callback) { var buffer = new Buffer(filedata, "utf-8"); ftp.on('progress', function(progress) { console.log("progress:",progress); }); ftp.put(buffer, targetUrl, function(err) { if(err) console.log(err); console.log("File successfully uploaded: " + targetUrl, decoder.write(buffer, "utf-8")); if(callback) callback(); }); }; |
5. [web-client] and finally using jquery connect to node.js socket.io with the dynamic ip
1 2 3 4 5 6 7 8 9 10 11 12 | $( document ).ready(function() { var connect = function(serverJson) { var socket = io.connect('http://' + serverJson[serverJson['network_interface']] + ':1337/'); // doing stuff with your connection }; $.getJSON("http://kibotu.net/server.php", function(json) { connect(json); }); }); |
[android client] one way to receive the url on your android client is by using an async task; grab the ip whenever you need it (e.g. in your onCreate-activity method)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final AsycnCallBack<String> callback = new AsyncTaskCallback<String>() { @Override public void callback(final String... ips) { ip = ips[0]; Toast.makeText(ChatClient.this, "Found Server: " + ip, Toast.LENGTH_SHORT).show(); // do stuff with your ip (e.g. connect to server) SocketClient.connect(ip, port); } }); new AsyncTask<String, Integer, JSONObject>(){ @Override protected void onPreExecute() { Logger.v(TAG, "Fetching ip"); } @Override protected JSONObject doInBackground(@NotNull final String... jsonUrls) { return JsonParser.readJson(jsonUrls[0]); } @Override protected void onProgressUpdate(final Integer... progress) { publishProgress(Integer.valueOf(1)); } @Override protected void onPostExecute(@Nullable final JSONObject json) { try { callback.callback(json.getString(json.getString("network_interface"))); } catch (final JSONException e) { e.printStackTrace(); } } }.execute("http://fancy.url/fancyserverip.php"); // you can test with http://ip.jsontest.com/ } |
convinient call back method:
1 2 3 | public interface AsyncTaskCallback<T> { public void callback(T...objects); } |
and you you’ll need a json parser
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | final public class JsonParser { private final static String TAG = JsonParser.class.getSimpleName(); // utility class private JsonParser() { } /** * reads json by url * * @param url * @return JSONObject */ @Nullable static public JSONObject readJson(final @NotNull String url) { StringBuilder builder = new StringBuilder(); HttpClient client = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); JSONObject finalResult = null; try { HttpResponse response = client.execute(httpGet); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); String line; while ((line = reader.readLine()) != null) { builder.append(line).append("\n"); } finalResult = new JSONObject(new JSONTokener(builder.toString())); } else { Logger.e(TAG, "Failed to download status file."); } } catch (JSONException e) { Logger.e(TAG, e.getMessage()); } catch (ClientProtocolException e) { Logger.e(TAG, e.getMessage()); } catch (IOException e) { Logger.e(TAG, e.getMessage()); } return finalResult; } } |