[SOLVED] SpiderMonkey Serialization Question

I’m beginning the adventure to learn and implement multiplayer into my game, and I noticed that registering the serializables works when I go against what the documentation says, and I get an error when I follow what the documentation says is correct:

https://jmonkeyengine.github.io/wiki/jme3/advanced/networking.html#creating-message-types

You must register each message type to the com.jme3.network.serializing.Serializer, in both server and client!

The documentation says to register messages and serialized classes with both the server and the client. However, doing so on the server causes this error when any other clients initially join the server and before I send any messages at all:

java.lang.RuntimeException: Error deserializing object, class ID:-53
	at com.jme3.network.base.MessageProtocol.createMessage(MessageProtocol.java:184)
	at com.jme3.network.base.MessageProtocol.addBuffer(MessageProtocol.java:160)
	at com.jme3.network.base.ConnectorAdapter.run(ConnectorAdapter.java:169)
Caused by: com.jme3.network.serializing.SerializerException: Class not found for buffer data.
	at com.jme3.network.serializing.Serializer.readClassAndObject(Serializer.java:405)
	at com.jme3.network.base.MessageProtocol.createMessage(MessageProtocol.java:180)
	... 2 more

When I do not register the serializables on the server (only on the clients), then everything works perfect: there is no error, and messages are sent and received properly.

Is the documentation outdated in this case, or have I possibly broken something with my code if my program is responding this way?

Thanks :slight_smile:

If you are using JME 3.1… nothing will work if you register only on the client. So maybe you are confused.

You only have to register on the server with the latest JME.

I’m running 3.1, I’ll double check that my observation is correct.

Here’s the condensed code from my main class on the client side

public static void main(String[] args) {
    app = new Main();
    app.start(JmeContext.Type.Display);
    
  }
  @Override
  public void simpleInitApp() {
    
    setPauseOnLostFocus(false);
    
    appController = new AppController(app, gameSettings, height, width);
    gameSettings.setAppController(appController); 
    
    appController.toMainMenu();

    //register messages with serializer 
    Serializer.registerClasses(TextMessage.class, PlayerJoinMessage.class, UpdateLobbyPlayersMessage.class, Player.class);  
  }

and here’s the server code, which does not contain any registration code. But if I run my project like this, I don’t get the error and things seem to work.

public class ServerMain extends SimpleApplication implements ConnectionListener{
    
    public Server server;
    
    public ArrayList connectedPlayers = new ArrayList();
    
    
  public static void main(String[] args) {
    
    ServerMain app = new ServerMain();
    app.start(JmeContext.Type.Headless); // headless type for servers!
  }
  
  public ServerMain(){
      start(JmeContext.Type.Headless);
      
  }

    @Override
    public void simpleInitApp() {
        try{
            server = Network.createServer(6143);
            server.start();
            server.addConnectionListener(this);
            
            server.addMessageListener(new ServerListener(), TextMessage.class);
            server.addMessageListener(new ServerListener(), PlayerJoinMessage.class);
            server.addMessageListener(new ServerListener(), UpdateLobbyPlayersMessage.class);
            
            SpellControlState serverSpellControlState = new SpellControlState();
            
        }
        catch(Exception e){
            e.printStackTrace();
        }
        
    }
    
    @Override
    public void simpleUpdate(float tpf){
        super.simpleUpdate(tpf);
    }
    
    @Override
    public void stop(){
        destroy();
        super.stop();
    }
   
     @Override
  public void destroy() {
      if(server.isRunning()){
        server.close();
        super.destroy();
      }
  }

    @Override
    public void connectionAdded(Server server, HostedConnection conn) {
        System.out.println(conn.getAddress());
    }

    @Override
    public void connectionRemoved(Server server, HostedConnection conn) {
        
    }
    public class ServerListener implements MessageListener<HostedConnection> {
        @Override
        public void messageReceived(HostedConnection source, Message message) {
           if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
           } 
//            else if (message instanceof TranslationMessage) {
//            // do something with the message
//                TranslationMessage translationMessage = (TranslationMessage) message;
//            
//            } 
//            else if (message instanceof SpellCastMessage) {
//                SpellCastMessage spellCastMessage = (SpellCastMessage) message;
//                spellCastMessage.getAgent().castSpell(spellCastMessage.getSpellIndex()); 
//            } 
           
            else if(message instanceof PlayerJoinMessage){
               PlayerJoinMessage joinMessage = (PlayerJoinMessage) message;
               Player joinedPlayer = joinMessage.getPlayer();
               source.setAttribute("player", joinedPlayer);
               connectedPlayers.add(joinedPlayer);
               server.broadcast(new UpdateLobbyPlayersMessage(connectedPlayers, joinedPlayer));
           }
        }
    }
    
}

here’s a short example sending two messages to put two players in the lobby, it appears to be functioning and throws no errors despite doing serialization registration on the client only

The only way this could remotely possibly work is in two cases:

  1. you are not really running the code you think you are
  2. the client and server are running in the same JVM and you get lucky.

Else, there is no way the server will know how to deserialize these messages. It’s just not possible.

Well, if the server is sending the messages to the client first then you might get lucky and they get registered in the right order. But you should definitely not ever count on that.

Register them on the server. Just the server. Not the client. Just the server. Only the server… don’t register them on the client.

Or, if you like to do the old fashioned way. Register them exactly in the same order with exactly the same utility method on both the client and server… and then make sure to remove the service that is normally doing that for you automatically.

Here is an example project that has a fully functioning network layer in client and server mode (thought is skips using the totally useless “headless” mode for the server).

Minimum connection setup:

A basic fully working “game”:

Same version using an entity system:

1 Like

I think it must be something along these lines, I just spent some time troubleshooting 3 scenarios. this time connecting between two separate computers with 3.1 as opposed to running two applications from the same computer.

  1. Registering on only the client does not work if I use two separate computers, but it’s the only way that works for me when I run two applications from the same computer.

  2. Registering on both the client and server does not work between two computers, and it also does not work running two application on the same computer. The host client that launched the server will connect to the server correctly, but the other clients can not connect due to the orginal serialization error.

  3. If I register on only the server, the server throws the original error when I call server.start();, and no clients can find or connect to the server as a result.

I’m wondering if my server startup code may be causing the serializbles to register more than once on the server and client in some scenarios, and as a result the initial host-client, the server, and the non-host clients all have non-identical serialization

Could the way I’m launching my server class titled “ServerMain” be causing the serializables that I register on the client to also register on the server? I’m declaring the ServerMain class as a new object from the main application rather than starting it as an indepenent application. When the player hits the “Host Server” button then I create a new object by callingServerMain serverMain = new ServerMain();

  public ServerMain(){
      start(JmeContext.Type.Headless);
  }

I also tried using the static main class method and attempted to start the server this way but couldn’t seem to get this to work. I’m thinking that I shouldn’t be starting my server in either of these ways maybe?
ServerMain.main(new String[0]);

public static void main(String[] args) {
     ServerMain app = new ServerMain();
     app.start(JmeContext.Type.Headless); // headless type for servers!
 }

I’ve been up later than I should and I’m probably too tired to continue troubleshooting this effectively, so I’m going to get to sleep and take a look at this again in the morning and double check the registration scenarios that seem to be throwing the errors when they shouldn’t.

Worst case scenario, I can at least keep moving forward using the odd, client-only registration that seems to work for two applications on the same device until I get this figured out.

Thanks for the help so far :slight_smile:

I don’t know. Something is screwy about your setup or there is information missing… or both. I can’t really comment.

There is a ton of info dumped to the log about what’s getting registered with the serializer and so on. This would be useful information along with the different errors you are seeing in the different scenarios… then at least you could see which class is failing.

And/or you can look at the completely working apps, see if they also work for you and then figure out what’s different.

1 Like

I think this is what @pspeed was describing:

Assuming you’re not forking the JVM process when you start the server, the server and client are running in the same JVM and sharing the same (static) serializer registration. This is the only way the setup you’ve described can possibly work. It’s quite straightforward to fix - just register your messages on the server before any connections are created, don’t touch anything on the client, and jME will take care of the rest.

P.S. For lots of reasons, I’d suggest forking the server to a different JVM so that it’s not sharing a JVM with a client.

1 Like

The wiki doc is out of date but is still only off slightly. I will get to it when I do Networking but I did spend 1 day with it recently and learned some things.

What I learned from that 1 day, take it for what its worth.

Initialize Messaging on server only and do it before starting server.

        try {
            server = Network.createServer(NetworkMessaging.PORT);
            NetworkMessaging.initialiseSerializables();
            server.start();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }

The server class,

package myserver;

import network.util.CreateGeoms;
import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.system.JmeContext;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import network.util.NetworkMessaging;
import network.util.NetworkMessaging.NetworkMessage;

public class ServerMain extends SimpleApplication {

    private Server server;
    private float count;
    private static final Logger LOG = Logger.getLogger(ServerMain.class.getName());

    public ServerMain() {
        count = 0;
    }
    
    public static void main(String[] args) {
        ServerMain app = new ServerMain();
        app.start(JmeContext.Type.Headless);
    }

    @Override
    public void simpleInitApp() {
        
        try {
            server = Network.createServer(NetworkMessaging.PORT);
            NetworkMessaging.initialiseSerializables();
            server.start();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
        
        Geometry geom = new CreateGeoms(this).createGeom(ColorRGBA.Blue);
        rootNode.attachChild(geom);
    }

    @Override
    public void simpleUpdate(float tpf) {
        count += tpf;
        if (count > 3f) {
            server.broadcast(new NetworkMessage("Welcome to the playground. " + tpf));
            count = 0;
        }
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
    
    @Override
    public void destroy() {
        server.close();
        super.destroy();
    }
}

The client and server need the same messaging classes. This means when you use separate client/server projects, you will need a shared jar/library that contains them.

Messaging class example,

package network.util;

import com.jme3.math.Vector3f;
import com.jme3.network.AbstractMessage;
import com.jme3.network.serializing.Serializable;
import com.jme3.network.serializing.Serializer;

public class NetworkMessaging {

    public static final int PORT = 6000;

    public static void initialiseSerializables() {
        Serializer.registerClass(NetworkMessage.class);
        Serializer.registerClass(PosAndLocMessage.class);
        Serializer.registerClass(PositionMessage.class);
    }

    @Serializable
    public static class NetworkMessage extends AbstractMessage {

        private String message;

        public NetworkMessage() {
        }

        public NetworkMessage(String message) {
            this.message = message;
        }

        /**
         * @return the message
         */
        public String getMessage() {
            return message;
        }
    }

    @Serializable
    public static class PosAndLocMessage extends AbstractMessage {

        private Vector3f position;
        private Vector3f direction;
        
        public PosAndLocMessage() {
        }

        public PosAndLocMessage(Vector3f position, Vector3f direction) {
            this.position = position;
            this.direction = direction;
        }

        /**
         * @return the position
         */
        public Vector3f getPosition() {
            return position;
        }

        /**
         * @return the direction
         */
        public Vector3f getDirection() {
            return direction;
        }
    }
    
    @Serializable
    public static class PositionMessage extends AbstractMessage {

        private Vector3f position;

        public PositionMessage() {
        }

        public PositionMessage(Vector3f position) {
            this.position = position;
        }

        /**
         * @return the position
         */
        public Vector3f getPosition() {
            return position;
        }
    }
}

An example of client startup using a shared jar/library that contains the message classes,

package myclient;

import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;
import com.jme3.network.Client;
import com.jme3.network.Network;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.system.JmeContext;
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import network.util.CreateGeoms;
import network.util.NetworkMessaging;

public class ClientMain extends SimpleApplication {

    private Client client;
    private static final Logger LOG = Logger.getLogger(ClientMain.class.getName());
    private ConcurrentLinkedQueue<String> messageQueue;
    
    public static void main(String[] args) {
        ClientMain app = new ClientMain();
        app.start(JmeContext.Type.Display);
    }

    @Override
    public void simpleInitApp() {
        
        try {
            client = Network.connectToServer("localhost", NetworkMessaging.PORT);
            client.start();
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, null, ex);
        }
        
        messageQueue = new ConcurrentLinkedQueue<>();
        client.addMessageListener(new NetworkMessageListener(messageQueue));
        Geometry geom = new CreateGeoms(this).createGeom(ColorRGBA.Blue);
        rootNode.attachChild(geom);
        
    }

    @Override
    public void simpleUpdate(float tpf) {
        String message = messageQueue.poll();
        
        if (message != null) {
            this.fpsText.setText(message);
        } 
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
    
    @Override
    public void destroy() {
        client.close();
        super.destroy();
    }

}

an example of a Client message listener,

package myclient;

import com.jme3.network.Client;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import java.util.concurrent.ConcurrentLinkedQueue;
import network.util.NetworkMessaging.NetworkMessage;

public class NetworkMessageListener implements MessageListener<Client>  {

    private final ConcurrentLinkedQueue<String> messageQueue;

    public NetworkMessageListener(ConcurrentLinkedQueue<String> messageQueue) {
        this.messageQueue = messageQueue;
    }
    
    @Override
    public void messageReceived(Client source, Message m) {
        
        if (m instanceof NetworkMessage) {
            NetworkMessage message = (NetworkMessage) m;
            messageQueue.add(message.getMessage());
        }
    }
    
}

Last,
see these videos
https://jmonkeyengine.github.io/wiki/jme3/advanced/networking_video_tutorials.html
starting from
•Video: Introduction to SpiderMonkey (Part 1/2)

Although dated, I was able to get a working SpiderMonkey server/client up and running with 0 networking experience and outdated wiki pages.

If anyone sees problems with this code please say so because this is what I will be using to update the wiki page.

Hope this helps in some way…

1 Like

How do I go about doing this? I split the server and client into two separate projects like the example @mitm posted which still doesn’t seem to fix my issue, and I also did some googling and couldn’t seem to find a full-proof way to fork a second jvm from my first application, unless I’m missing something. I also watched part 1 and 2 of the spidermonkey videos and looked through @pspeed’s examples as well but couldn’t seem to grasp the proper way to start the server application to ensure it runs in it’s own JVM

I’ve been trying more troubleshooting and my results are still inconsistent with all the working examples. Which leads me to think I need to first fix the way I’m starting the server so that I’m not getting inconsistent results when clients connect between two devices, compared to connecting two clients on the same device.

Thanks for the responses.

How are you starting the server from the client now?

I’m assuming you’re doing something like:

new Thread(new RunnableThatLaunchesMyServer)).start();

If you’re doing anything remotely like that, the server will always run in the same JVM with the client. To get around that you need to use Runtime.exec() or the ProcessBuilder/Process classes to launch a separate JVM process with the right main class parameter passed to launch your server.

2 Likes

Oh gosh I just noticed I never posted that part of my code in my original post :open_mouth: This class has a constructor for joining a server, and a constructor for hosting the server which initializes the ServerMain application as a new object within the first application (I’m thinking this is the root of my problem)

 public ClientHandler(AppController ac){
        appController = ac;
        if(!serverRunning){
            serverRunning = true;
        //    Networking.ServerMain.main(new String[0]);   
            serverMain = new ServerMain();
            try{
                client = Network.connectToServer("localhost", 6143);
                client.start();
                registerListeners();
            }
           catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    
    //constructor to connect to designated server
    public ClientHandler(AppController ac, String address){
        appController = ac;
        try{
           client = Network.connectToServer(address, 6143);
           client.start();
           registerListeners();
        }
        catch(Exception e){
           e.printStackTrace();
        }

    }

I saw some info about Process Builder when I was googling and was referncing this thread https://stackoverflow.com/questions/14704272/run-java-command-in-different-jvm

If I use the process builder, does that mean I just need to compile my server into an updated .jar every time I need to test something, or is there a way to run the .java files with the process builder before they’re compiled?

I also recall reading something about RunTime.exec(), which said the second JVM will have the same memory allocations as the primary application and can lead to memory crashes, unless I’m mistaken

Yep. That’s launching your server main as part of your client’s JVM, so all static variables accessible to the client are also accessible to the server. Serializer registrations are statically stored… and that’s the only reason this works at all. I’d suggest using either Runtime.exec() or ProcessBuilder to launch the server in its own JVM (i.e., run the “java” process with the name of your server’s main class as an argument). That’s really only strictly necessary if you want the server to keep running after the hosting player has disconnected (otherwise, if the host crashes then everyone else gets booted from the game - if the server is running in a separate process then the host can re-join and everyone is happy). At very least, however, move message registration to the server classes in a spot where messages will register before any messages get sent from server->client (or vice-versa). If you just register messages on the server once, SpiderMonkey will magically take care of the rest for you.

Edit: P.S. - if you do fork the server, make sure there’s a way for it to automatically exit when sensible (i.e., all players have disconnected, or whatever the right case is for your game). Users won’t like it if they have to hunt down and kill unneeded server instances in Task Manager that your game launched and never cleaned up.

1 Like

I’m running into trouble getting the Process Builder or the RunTime.exec() working now, I’ve unfortunately never used either method in the past.

Using RunTime.exec() cannot reference a .java file otherwise i get this error saying it’s not a valid win32 application, so I assume if I want to use this method I must compile and reference “ServerMain.jar” instead when using this method?

   try {
     Runtime.getRuntime().exec("C:/Users/ryan/Desktop/JM/TAF/src/Networking/ServerMain.java");

Java.io.IOException: Cannot run program "C:/Users/ryan/Desktop/JM/TAF/src/Networking/ServerMain.java": CreateProcess error=193, %1 is not a valid Win32 application

.

try {
    ProcessBuilder pb = new ProcessBuilder("java.exe","-server","bin","C:/Users/ryan/Desktop/JM/TAF/src/Networking/ServerMain.java");
    Process p = pb.start();
} catch (IOException ex) {
    Logger.getLogger(ClientHandler.class.getName()).log(Level.SEVERE, null, ex);
    
}

If I try the process builder it doesn’t work and the only error that gets thrown or logged is this connection refused error when clients try to connect

java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.<init>(Socket.java:434)
	at java.net.Socket.<init>(Socket.java:244)
	at com.jme3.network.kernel.tcp.SocketConnector.<init>(SocketConnector.java:65)
	at com.jme3.network.Network.connectToServer(Network.java:165)
	at com.jme3.network.Network.connectToServer(Network.java:122)
	at Networking.ClientHandler.<init>(ClientHandler.java:55)
	at CoreAppClasses.MenuInterfaces.MultiplayerLobbyScreen.hostMatch(MultiplayerLobbyScreen.java:75)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

It would be preferable if I can manage to launch the server by referencing a .java file as opposed to the .jar, if that’s even possible. I really appreciate the help :slight_smile:

Edit: I referenced this https://stackoverflow.com/questions/10093290/run-a-java-file-using-processbuilder for my attempt at running a .java file using the process builder, but I have no clue if I’m inputting the correct parameters :frowning:

Note: I run my servers and clients in the same process all the time… but you also have to test them separately and I know what I’m doing.

The suggestion to fork the process just forces you to be “clean”… which in your case is probably a good idea.

But ultimately, you really need to figure out what about your setup is messed up so that actual remote clients are unable to connect. And that has nothing to do with forking or not… it’s just that you will get the same problems instantly if you fork rather than having to run a separate client.

1 Like

In that case I’m going to try and narrow this down to a small test case with my current code and see if I can find anything different.

Edit: before I posted this and created a test case I tried one last thing with my main code. I added this boolean in my Networking.Utilities and now things are working.

public class NetUtilities {
    public static final int port = 6143;

    private static boolean registered = false;

    public static void initiliazeSerializables(){
      if(!registered){
        Serializer.registerClasses(TextMessage.class, PlayerJoinMessage.class, UpdateLobbyPlayersMessage.class, Player.class); 
        registered = true;
    }
   }
 }

Now things work when I register on the server and the client side. If I’m running 3.1 still, is it alright that I register on both sides?

1 Like

I should have tested my findings between two computers before saying things worked, now I’m just getting this connection error anytime a non-local client tries to join.

java.net.ConnectException: Connection refused: connect
	at java.net.DualStackPlainSocketImpl.connect0(Native Method)
	at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.<init>(Socket.java:434)
	at java.net.Socket.<init>(Socket.java:244)
	at com.jme3.network.kernel.tcp.SocketConnector.<init>(SocketConnector.java:65)
	at com.jme3.network.Network.connectToServer(Network.java:165)
	at com.jme3.network.Network.connectToServer(Network.java:122)
	at Networking.ClientHandler.<init>(ClientHandler.java:70)
	at CoreAppClasses.MenuInterfaces.MultiplayerLobbyScreen.joinFriends(MultiplayerLobbyScreen.java:85)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Is it possible this could have to do with port forwarding or my network settings? I also get this error when I try to connect two clients to the same device using my real IP address as opposed to using 127.0.0.1

Networking is particularly nasty because even if your code is 100% perfect there’s still a lot that can go wrong. Connection refused can be caused by lots of things - wrong IP, firewall/other security software, etc. If all clients are on the same LAN it isn’t port fowarding - that’s used to forward traffic from a world-facing port and your router’s public IP to a machine on your LAN when NAT is active. Check port numbers and make sure that no security software is blocking the connection.

1 Like

Unfortunately my router is in a room where my family is asleep right now, so I can’t check the login information to snoop around my routers settings, or to fix the port forwarding in order to allow non-LAN connections eventually.

However I did just find a working solution that properly connects two clients from this computer, as well as two clients from two seperate computers if I use my ipv4 address instead of my public IP address :grin: This rings a bell from a long time ago when I played around with hosting minecraft servers. Although I’ve forgotten a lot, I recall troubleshooting an issue like this where my brother had to use that address to connect from his computer to my server since we were on the same network.

http://www.minecraftforum.net/forums/support/server-support/1878676-cant-connect-to-my-own-server-from-external-ip

Now I just have to try to have someone who is not on my network connect using my public IP address once I get a chance to configure the port forwarding properly, and hopefully that will work.

For now, I’ve at least got the connection between two computers on the same network functioning with the serialization registration method being called on both the server and client.

Thanks so much for all the help!

1 Like

Awesome! Glad to hear you got it working.

1 Like

This is an enigma, granted, but note that operating systems treat how you get local host differently. Excuse the iPod autocorrect. The way you initialise the server will matter. Bound to an ip or a pseudonym, what ip you connect to, internal or outbound ip …

1 Like