alles richtig.Das Problem passt nicht ganz zu dem Code. Das Problem kommt daher, dass der Server derzeit noch auf eine Textzeile vom Client wartet. Dieser Request läuft dann irgendwann in einen Timeout.
Der Client selbst öffnen einen ObjectInputStream und will bei der Initialisierung ein paar erste Bytes lesen. Diese kommen aber leider nicht (da der Server ja selbst noch wartet).
Das ist aber nicht das Verhalten des Codes, den Du gepostet hast
Generell ist es so, dass man sich auf eine "Kommunikationsart" festlegen sollte. Also entweder mann schickt nur Text oder man nutzt Object Streams oder man macht irgend was eigenes ...
Dann öffne ich gerne die Streams direkt, damit diese dann verwendbar sind. Da di Kommunikation blockierend ist, bekommt jeder Socket einen eigenen Thread. Wenn etwas kommt (beim Server), dann wird darauf reagiert.
Das habe ich mal kurz skizziert in folgendem Code. Das ist natürlich nicht komplett, denn der Server sollte alle seine Clients kennen und so. Aber das ignorieren wir mal einfach. Und da Du komplexe Dinge gerne per Object Stream versendest, basiert alles auf ObjectStream (Ein String ist ja auch ein Objekt - kann also versendet werden!)
Eine Nachricht, die empfangen wird auf Server Seite ist dann eine ReceivedMessage und bekommt - ähnlich wie man das von Events in Swing und Co kennt, auch den Sender mit:
Java:package mypackage.server; public class ReceivedMessage { private ClientSocketHandler handler; private Object message; public ClientSocketHandler getHandler() { return handler; } public Object getMessage() { return message; } public ReceivedMessage() { } public ReceivedMessage(ClientSocketHandler handler, Object message) { this.handler = handler; this.message = message; } }
Einen Client Socket will gehandhabt werden. Das ist dann einfach mal folgende Klasse geworden:
Java:package mypackage.server; import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.function.Consumer; public class ClientSocketHandler { private final Socket clientSocket; private final ObjectInputStream in; private final ObjectOutputStream out; private Consumer<ReceivedMessage> objectHandler; private volatile boolean running; public ClientSocketHandler(final Socket clientSocket) throws IOException { this.clientSocket = clientSocket; in = new ObjectInputStream(clientSocket.getInputStream()); out = new ObjectOutputStream(clientSocket.getOutputStream()); running = true; } public void start() { new Thread(this::receiveMessages).start(); } public void sendMessage(Object obj) throws IOException { out.writeObject(obj); } public void receiveMessages() { while (running) { try { Object obj = in.readObject(); if (objectHandler != null) objectHandler.accept(new ReceivedMessage(this, obj)); } catch (EOFException eofe) { System.out.println("Connection closed ..."); running = false; } catch (IOException ioe) { ioe.printStackTrace(); System.out.println("Could not read from socket, closing!"); running = false; } catch (ClassNotFoundException cnfe) { System.out.println("Message discarded, class not found: " + cnfe.getMessage()); } } safeClose(in); safeClose(out); safeClose(clientSocket); } private void safeClose(AutoCloseable closeable) { if (closeable == null) return; try { closeable.close(); } catch (Exception ex) {} } public void setObjectHandler(Consumer<ReceivedMessage> objectHandler) { this.objectHandler = objectHandler; } }
Ist schnell umschrieben: Bekommt einen Socket, öffnet darauf die Object Streams. Man kann eine Routine hinterlegen, die Nachrichten dann verarbeitet. Und man kann das natürlich noch starten.
Ein einfacher Server sieht dann so aus:
Also in erster Linie ein Thread, der auf neue Clients wartet, die Verbindungen annimmt und dann entsprechende Handler erstellt.Java:package mypackage.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server { private final int port; private boolean running; private ServerSocket serverSocket; public Server (final int port) { this.port = port; } public void open() { if (running) return; new Thread(this::acceptNewConnections).start(); } private void handleMessage(ReceivedMessage msg) { System.out.println(msg.getMessage()); try { msg.getHandler().sendMessage("Empfangen: " + msg.getMessage()); } catch (IOException ex) { System.out.println("Unable to send message back!"); ex.printStackTrace(); } } private void acceptNewConnections() { try { serverSocket = new ServerSocket(port); System.out.println("Waiting on Port " + port); running = true; } catch (IOException ex) { System.out.println("Unable to open port!"); ex.printStackTrace(); } while (running) { try { Socket client = serverSocket.accept(); System.out.println("Client connected ..."); ClientSocketHandler handler = new ClientSocketHandler(client); handler.setObjectHandler(this::handleMessage); handler.start(); // die einzelnen Handler werden in der Regel auch noch verwaltet! } catch (IOException ex) { System.out.println("Exception when accepting a new Client."); ex.printStackTrace(); } } } public static void main(String[] args) { Server server = new Server(4243); server.open(); } }
Der ObjectHandler (fällt mir gerade auf, dass das blöd ist. Sollte besser NewMessageHandler heissen) ist hier einfach minimal aufgebaut. Die nachricht wird ausgegeben und ein String zurück gesendet.
Mal einfach eine angepasste Klasse, die mit dem Server umgehen kann:
Java:package mypackage.client; import java.io.*; import java.net.*; import java.util.ArrayList; public class ClientDB { private InetSocketAddress address; ObjectOutputStream out; public void los() { try { address = new InetSocketAddress("127.0.0.1",4243); Socket s = new Socket(); s.connect(address, 1000); // Verbinde Dich mit dem Server, maximale Wartezeit: 3 Sekunden out = new ObjectOutputStream(s.getOutputStream()); out.writeObject("select * from leute"); // SQL-Befehl an Server absenden out.flush(); // Empfangen werden vom Server aber binäre Rohdaten, also wird ObjectInputStream benötigt: ObjectInputStream ois = new ObjectInputStream(s.getInputStream()); // Ausgabe der ArrayList mit dem ResultSet der Datenbank in einem String // nachdem die Rohdaten zunächst wieder in eine ArrayList von Typ String // zurückverwandelt wurden System.out.println("Ausgabe der Daten:"); Object msg = ois.readObject(); System.out.println(msg); // ois.close(); // s.close(); // writer.close(); } catch(IOException ex) {ex.printStackTrace();} catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * @param args the command line arguments */ public static void main(String[] args) { // Client starten: ClientDB client1 = new ClientDB(); client1.los(); } }
Am Client habe ich kaum was gemacht - da könnte man dann noch einiges optimieren.
Was man da z.B. machen könnte:
- Man erstellt einfach Nachrichten Klassen. Also von mir aus sowas wie eine SQLQuery Klasse.
- Der NewMessageHandler prüft dann: Ist die msg vom Typ SQLQuery? -> SQL Query ausführen.
So kann man dann viele Nachrichten-Klassen bauen, die dann behandelt werden.
Man könnte dazu sogar etwas bauen wie eine Map, die als Key den Klassennamen hat und als Value dann ein Consumer<> oder so.
Also ganz klar: Das war nur ein Anfang und man kann einiges mehr ausbauen!
Du erklärst aber nicht warum es lokal geht aber mit öffentlicher IP nicht.
Ich habe seinen Code mal mit öffentlicher und lokaler IP getestet ging beides.
Server und Client auf derselben Maschine, nur der Server blendet sich nach einer Verbindung.