multithreaded multiport socketListener beendet TCP-Port-Listening

Bitte aktiviere JavaScript!
Hi,

ich bastle gerade an einem multithreaded SocketListener sowohl für eine Anzahl UDP- als auch TCP-Ports. Die Anwendung läuft auf einem Debian-Linux. Verwendet werden soll sie als Server-Gegenstück zu einem Java-Client der bei mehreren hundert Nutzern läuft. D.h. es kann durchaus mal vorkommen, dass 2 oder mehr Nutzer gleichzeitig den Listener ansprechen.

Allerdings habe ich ein Problem bereits mit einzelnen Requests: die TDP-Ports schließen sich einfach wieder nach einiger Zeit (immer unterschiedlich). Ich sehe irgendwie keinen Grund dafür. Vlt. kann mir hier jemand dazu einen Tipp geben? UDP geht btw. einwandfrei.

Java:
package Main;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.logging.FileHandler;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

/**
* Multi-threaded multisocket-listener which will act as server-side
* application to check if users have specific ports open
* in their network/firewall.
*
* If a client connects on one of the ports,
* the application sends a simple 1 and closes the connection.
*
*/
public class socketListener {

    Socket cs;
    private String logfile = "";
    private ServerSocket ss;
    private ArrayList<Integer> tcpPorts =  new ArrayList<Integer>();
    private ArrayList<Integer> udpPorts =  new ArrayList<Integer>();
    private InputStream inputStream;
    private String result = "";
    private Logger logger;
   
    socketListener(Socket cs){
        this.cs = cs;
    }
   
    /**
     * Main function for this app.
     * The arguments will not be used.
     *
     * @param args
     * @throws InterruptedException
     * @throws IOException
     */
    public static void main(String args[]) throws InterruptedException, IOException{
        System.setProperty("java.net.preferIPv4Stack" , "true");
        new socketListener();
    }
   
    /**
     * Start the listener.
     */
    public socketListener() {
        try {
            // read config.properties-file
            this.getPropValues();
            // if logfile is set use it
            if( this.logfile.length() > 0 ) {
                // create a logger
                logger = Logger.getLogger("log");
                // create a filehandler
                FileHandler fh = new FileHandler(this.logfile);
                // set the filehandler as destination where the file will be written
                logger.addHandler(fh);
                // create and set a format for the logfile
                SimpleFormatter formatter = new SimpleFormatter(); 
                fh.setFormatter(formatter); 
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
       
        // add all tcp-ports to the array
        for( int p = 2300; p <= 2400; p++ ) {
            tcpPorts.add(p);
        }
        tcpPorts.add(47624);
       
        // add all udp-ports to the array
        for( int p = 2300; p <= 2400; p++ ) {
            udpPorts.add(p);
        }
        udpPorts.add(1900);
       
        try {
            // start the tcp-port-listener
            tcpPortListener();
            // start the udp-port-listener
            udpPortListener();
        } catch (InterruptedException | IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
       
    }
   
    /**
     * UDP-Port-listener for the given list of ports.
     * Sends the received message back to sender.
     *
     * @throws InterruptedException
     * @throws IOException
     */
    private void udpPortListener() throws InterruptedException, IOException {
        // loop through the ports and open a listener for each
        for( int udpPort: udpPorts ) {
            // wait 100ms before adding the next port-listener as it may be to fast
            Thread.sleep(100);
            // make it runnable as we need to listen to many ports at the same time
            Runnable runnable = new Runnable(){
                public void run(){
                    DatagramSocket serverSocket = null;
                    try {
                        // open port
                        serverSocket = new DatagramSocket(udpPort);
                        debug("Server Listening on udp-port " + udpPort);
                        byte[] receiveData = new byte[1024];
                        byte[] sendData = new byte[1024];
                       
                        // wait if we got a message
                        while(true)
                        {
                            // get the message we received
                            DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                            serverSocket.receive(receivePacket);
                            String sentence = new String( receivePacket.getData());
                            debug("RECEIVED on udp port " + udpPort + ": " + sentence);
                           
                            // get the ip-address of the caller
                            InetAddress IPAddress = receivePacket.getAddress();
                           
                            // get the port where the caller sent from
                            int port = receivePacket.getPort();
                           
                            // format the message
                            String capitalizedSentence = sentence.toUpperCase();
                           
                            // get the message-bytes
                            sendData = capitalizedSentence.getBytes();
                           
                            // send the package back to given ip and port
                            DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, port);
                            serverSocket.send(sendPacket);
                        }
                    } catch (NumberFormatException | SocketException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    finally {
                        // quit the listener on the port
                        if( serverSocket instanceof DatagramSocket ) {
                            serverSocket.close();
                        }
                    }
                }
            };
            Thread thread = new Thread(runnable);
            thread.setName("udpPort" + udpPort + "Thread");
            thread.start();
        }
    }
   
    /**
     * TCP-Port-listener for the given list of ports.
     * Sends a "1" back to sender and close the connection after it.
     *
     * @throws InterruptedException
     */
    private void tcpPortListener() throws InterruptedException {
        // loop through the ports and open a listener for each
        for( int port: tcpPorts ) {
            // wait 500ms before adding the next port-listener as it may be to fast
            Thread.sleep(500);
            // make it runnable as we need to listen to many ports at the same time
            Runnable runnable = new Runnable(){
                public void run(){
                    try {
                        // take the port
                        ss = new ServerSocket(port);
                        debug("Server is now listening on tcp-port " + port);
                       
                        // wait for a connection
                        while(true){
                            // new connection from a client
                            Socket newClient = ss.accept();
                           
                            // debug-output
                            debug(newClient.getInetAddress() + " has connected on tcp port " + port);
                           
                            try {
                                // send a simple "1" back to the client to proof that the connection was successfully
                                PrintWriter out = new PrintWriter(newClient.getOutputStream(), true);
                                out.println("1");
                            } finally {
                                // then close the connection to the client
                                newClient.close();
                            }
                        }
                    }
                    catch(IOException e) {
                        System.out.println("tcp-Port " + port + " already taken (maybe other programm?)");
                    }
                    finally {
                        // quit the listener on the port
                        try {
                            debug("Server Listening on tcp port " + port + " ended");
                            ss.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Thread thread = new Thread(runnable);
            thread.setName("tcpPort" + port + "Thread");
            thread.start();
        }
    }
   
    /**
     * Output the given debug-string on the console.
     *
     * @param text
     * @throws IOException
     */
    private void debug( String text ) {
        if( this.logger != null ) {
            this.logger.info(text);
        }
        // output to console
        System.out.println(text);
    }
   
    /**
     * Read data from config-file.
     *
     * @return
     * @throws IOException
     */
    public String getPropValues() throws IOException {
        
        try {
            Properties prop = new Properties();
            String propFileName = "config.properties";

            inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);

            if (inputStream != null) {
                prop.load(inputStream);
            } else {
                throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
            }
            // get a single property from file
            logfile = prop.getProperty("logfile");
        } catch (Exception e) {
            debug("Exception: " + e);
        } finally {
            if( inputStream != null ) {
                inputStream.close();
            }
        }
        return result;
    }
   
}
 
Zuletzt bearbeitet von einem Moderator:
Oh, da habe ich dich falsch verstanden. Korrekt, Socket.close() schließt nur die aktuelle Verbindung von dem Client Socket. Es sollte nicht das ServerSocket selbst schließen.
 
Ja, ich denke auch, dass das Problem sein könnte, dass Du die Variable ständig überschreibst und damit der Socket nicht mehr referenziert wird und dann wohl vom GC behandelt wird.

Generell sehe ich aber auch gewisse Probleme mit dem Design: Die vielen Threads sind in meinen Augen ungünstig, auch wenn die meiste immer schlafen sollten. Du hast halt sehr viele Context Switche dadurch und das kostet auch Performance. Besser ist es daher, weniger Threads und dafür eine async Klassen aus java.nio zu nehmen. Dann hast Du halt in erster Linie einen Thread, der die SocketChannel prüft, ob etwas anliegt um das dann zu behandeln.
 
@mrBrown Danke für den Tipp. Hab ich gleich mal angepasst. Der Abschnitt sieht nun so aus:

Code:
Runnable runnable = new Runnable(){
                                public void run(){
                                        ServerSocket ss = null;
                                    try {
                                        // take the port
                                        ss = new ServerSocket(port);
                                        debug("Server is now listening on tcp-port " + port);

                                        // wait for a connection
                                            while(true){
                                                // new connection from a client
                                                Socket newClient = ss.accept();

                                                // keep socket alive
                                                newClient.setKeepAlive(true);

                                                // debug-output
                                                debug(newClient.getInetAddress() + " has connected on tcp port " + port);

                                                try {
                                                        // send a simple "1" back to the client to proof that the connection was successfully
                                                PrintWriter out = new PrintWriter(newClient.getOutputStream(), true);
                                                out.println("1");
}
                                                catch (SocketTimeoutException e) {
                                                        ss = new ServerSocket(port);
                                                        debug("Server has reconnected for listening on tcp-port " + port);
                                            } finally {
                                                // then close the connection to the client
                                                newClient.close();
                                            }
                                            }
                                    }
                                    catch(IOException e) {
                                        System.out.println("tcp-Port " + port + " already taken (maybe other programm?)");
                                    }
                                    finally {
                                        // quit the listener on the port
                                    try {
                                        debug("Server Listening on tcp port " + port + " ended");
                                                        ss.close();
                                                } catch (IOException e) {
                                                        e.printStackTrace();
                                                }
                                }
                                }
                        };
Auch wenn ich es inhaltlich nachvollziehen kann ändert es nichts am Verhalten. Sobald ich einmal einen Port per TCP aufgerufen habe wird er beim 2. Request dann geschlossen. Interessant finde ich, dass ich genau das nun nachvollziehen konnte - vorher war ich von einem mir unbekannten Zeitfaktor ausgegangen (wie oben beschrieben).

Merkwürdig finde ich auch, dass - obwohl ich ein Logfile innerhalb der Anwendung über debug() mitlaufen lasse - es keinerlei Meldung "Server Listening on tcp port xy ended" gibt. Der Thread zum Port ist einfach weg ohne Meldung.

@kneitzel Um Performance geht es mir derzeit (noch) nicht. Ich will es einfach nur laufen haben ohne, dass der steuernde Dienst ständig neugestartet werden muss - und auch verstehen wieso der TCP-Socket geschlossen wird. Die Anwendung nutzt sehr wenig Ressourcen auf dem Server, weshalb ich mir da weniger Sorgen machen. Oder denkst Du das könnte etwas am TCP-Socket-Schließen ändern?
 
Ist Dir da evtl. beim Copy & Paste was schief gelaufen? Irgendwie scheint mir der Code nicht schlüssig. Die Timeout Exception umfasst nicht mehr das accept() und vor dem catch sehe ich nicht die schließende geschweifte Klammer ....

So dass Dein Code wirklich ist: ggf hat er nicht neu Übersetzt und die alte Version neu gestartet?

Ansonsten fällt mir da nichts auf. Und ob das ggf. Das Problem ist: reduziert mal die Anzahl der Threads/Ports ... wenn es an der Anzahl liegt und das System da irgendwelche Schmerzen hat, dann wird es mit weniger laufen. Aber derzeit halte ich die 202 Threads / Sockets nicht für so kritisch. Aber wozu sind so viele Ports notwendig? Warum lässt Du es nicht über weniger Ports laufen?
 
Hab nur den Abschnitt im Runnable in tcpPortListener() nochmal gepostet, passt schon so ;) Hab es auch testweise laufen lassen.
Der Code wurde in jedem Fall neu ausgeführt nach der Anpassung.

Die Ports brauche ich für meinen aktuellen Fall in genau dieser Anzahl. Wird nicht mehr werden, aber auch nicht weniger. Wenn es gut läuft würde ich das ggfs. noch dynamischer gestalten und anderen auch bereitstellen.

Hab inzwischen aber eine Idee was es ist:
D.h. ich müsste nach einer Verbindung durch einen Client das jeweilige Socket neu verbinden ..
 
Ich vermute das Problem langsam viel mehr im Client als im Server. Du schreibst:
die TDP-Ports schließen sich einfach wieder nach einiger Zeit (immer unterschiedlich)
Wie stellst du das genau fest? Per netstat? Also: Woher weißt du ganz genau, dass der TCP Port tatsächlich weg ist?
Der Thread zum Port ist einfach weg ohne Meldung.
Woran machst du das genau fest? Startest du mit Debugger und dann ist der Thread nicht mehr in der Liste der aktiv ausgeführten Threads gelistet?
 
Also nur damit Du da keine falschen Ideen bekommst: Da hat jemand in der Schleife ein ServerSocket erstellt und dann nach jedem Client schließt er den ServerSocket und öffnet ihn erneut. Das ist so natürlich Quatsch. Der ServerSocket ist und bleibt offen. Also ein einfaches Beispiel sieht z.B. wie folgt aus:

Server:
Java:
package socket;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(10000);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("New Client connected...");
                handleSocket(socket);
            }
        } catch (IOException ex) {
            System.err.println("Exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    private static void handleSocket(Socket socket) {
        try (PrintWriter writer = new PrintWriter(socket.getOutputStream(), true)) {
            writer.println("1");
            socket.close();
        } catch (IOException ex) {
            System.err.println("Exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}
Client:
Java:
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        try {
            while (true) {
                Socket socket = new Socket("localhost",10000);
                System.out.println("Connected to Server ...");
                handleSocket(socket);
            }
        } catch (IOException ex) {
            System.err.println("Exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    private static void handleSocket(Socket socket) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
            System.out.println("Read: " + reader.readLine());
            socket.close();
        } catch (IOException ex) {
            System.err.println("Exception: " + ex.getMessage());
            ex.printStackTrace();
        }
    }
}
Der ServerSocket bleibt immer geöffnet und neue Connections können angenommen werden.

Aber jede Verbindung wird direkt wieder geschlossen: Der Server senden einen Text (Die "1" mit Zeilenumbruch) und schließt die Verbindung dann. Der Client macht auch nichts groß anders: Er liest eine Zeile und schließt die Verbindung dann auch....

Aber wie man erkennen kann: Neue Verbindungen können da dann direkt angenommen werden, ohne dass der ServerSocket neu geöffnet werden muss.

Das Problem ist aber wirklich auf Serverseite? Dass da keine Verbindung angenommen wird? Oder ist das Problem auf Clientseite, weil du da mehr machen willst, als nur das einmal geschriebene zu lesen?
 
Danke nochmal für eure Antworten. Ich glaube ich habe oben im 2. Quellcode von mir doch einiges drinn gehabt was ich zum Testen auf dem Server noch hatte - und nur dort. Das passt natürlich nicht 100%ig zu dem was ich in der Entwicklungsumgebung hier lokal habe (und euch oben im 1. Posting gezeigt habe). Habe jetzt mit dem aktuellen Stand (siehe Quellcode 1. Posting + Anpassung wg. ss-Variable) nochmal genauer geschaut und getestet mit folgendem Ergebnis:

Unter Windows starte ich die Anwendung über eclipse. Mit
Code:
netstat -abno | find "2300"
lass ich mir dort anzeigen, ob der Port offen ist.

Mit
Code:
telnet localhost 2300
frag ich den Port von der Windows-cmd aus ab.

Ergebnis: unter Windows kann ich es so oft machen wie ich will. Der Port bleibt dauerhaft geöffnet und jeder Request wird an der Eclipse-Konsole vermerkt. Bei jedem Request an den Port kommt auch die "1" zurück.

Unter Linux starte ich die Anwendung als systemd-Dienst ("service socketListener start") über folgendes Shell-Script:

Code:
#!/bin/bash
cd /opt/socketListener
nohup java Main/socketListener </dev/null >/dev/null &
Mit
Code:
netstat -tulpen | grep 2300
prüfe ich, ob der Port offen ist.

Mit
Code:
telnet meinedomain.tld 2300
frage ich den Port von der Windows-cmd aus ab. Analog unter Linux (auch von einem anderen Server aus).

Ergebnis: egal von welchem System aus ich den Request an den Linux-Server sende, ich erhalte immer nur beim 1. Versuch nach dem Neustart die "1" zurück. Beim 2. Versuch erhalte ich keinerlei textliche Rückmeldung mehr. Scheinbar bleibt die Verbindung jedoch weiterhin offen (zu sehen wenn ich unter Windows nach dem 2. Request nochmal die Ports anzeigen lasse, benannt als "FIN_WARTEN_2") - die wird scheinbar nach einem Timout erst geschlossen.

Scheint also eher ein Linux-bezogenes Problem zu sein. Oder liegts eher an der Java-Version? Aktuell wird unter Linux diese genutzt:

openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-1~bpo8+1-b11)
OpenJDK 64-Bit Server VM (build 25.171-b11, mixed mode)
Unter Windows habe ich aktuell 1.8.0_191. Macht das so einen Unterschied?

Hinweis wg. der Frage zum Client: natürlich gibt es noch eine Client-seitige Anwendung, aber solange es nicht per telnet sauber geht, spielt die noch keine Rolle.
 
Kannst du den Code noch einmal genau zeigen, wie er ist? Ich arbeite ja auch unter Linux und da läuft das auch problemlos. Wobei ich das JDK 8 von azul nutze, aber das sollte keine Rolle spielen.

Evtl. baust du in den Thead noch mehr Debug Ausgabe ein, damit du siehst, was er alles macht. Kommt er zu dem accept Aufruf nach dem connect des Clients?

Und kommt er ggf zu dem catch Block, in dem du ss neu setzen willst? Wäre fatal, denn ss ist ja nicht geschlossen und damit der Port noch durch den ServerSocket gesperrt.
 
Danke für eure Unterstützung. Dank dem Hinweis von @kneitzel mehr Debugs einzubauen erkannte ich, dass auf dem Linux-Server doch nicht der aktuelle Stand lief. Hab das behoben - seither funktioniert der socketListener einwandfrei. Das eigentliche Problem war demnach die Variable ss vom ServerSocket, die wurde bisher wie von euch richtig gesagt, falsch initialisiert.

Ich lasse es mal noch von den Usern checken und würde euch das Ergebnis dann gerne bereitstellen.

Manchmal hilft ein Tag Abstand um seine eigenen Fehler zu erkennen ;)
 
Passende Stellenanzeigen aus deiner Region:

Neue Themen

Oben