3 Stimmen

Java Mehrfaden-Server wirft *manchmal* SocketException (Socket geschlossen) in der ServerSocket.accept() Methode

Ich habe dieses Problem seit Stunden untersucht und kann keine gute Lösung oder Erklärung dafür finden, warum diese Ausnahme (java.net.SocketException: Socket closed) geworfen wird. Mein letzter Ansatz ist jetzt, euch zu fragen.

Ich habe eine einfache Server-Client-App für Testzwecke erstellt (die "echte" Anwendung verwendet jedoch die gleiche Logik), siehe unten.

Wenn ich den gleichen Testfall wiederholt aufrufe (zum Beispiel durch den invocationcount-Parameter von TestNG oder durch eine einfache for-Schleife), wird es irgendwann zu einer java.net.SocketException: Socket closed kommen.

Der untenstehende Testfall startet im Grunde genommen den Server (öffnet den Server-Socket), wartet ein paar Millisekunden und schließt dann den Socket wieder. Das Schließen des Server-Sockets beinhaltet das Öffnen eines Sockets, damit der Server aus der ServerSocket.accept() Methode zurückkehren wird (siehe Server#shutdown()).

Ich dachte, es könnte ein Multithreading-Problem mit dem Code direkt nach der ServerSocket.accept()-Zeile sein. Also habe ich es vorübergehend mit einem synchronisierten Block umgeben - hat auch nicht geholfen.

Habt ihr eine Idee, warum diese Ausnahme geworfen wird?

Beste Grüße, Chris

Server.java sieht folgendermaßen aus:

paket multithreading; 
import java.io.IOException; 
import java.net.ServerSocket; 
import java.net.Socket; 
import org.apache.log4j.Logger; 

public class Server {

private ServerSocket serverSocket;
private boolean isShuttingDown;
private final static Logger logger = Logger.getLogger(Server.class);

public void start() throws Exception { 
    try { 
        serverSocket = new ServerSocket(5000); 
        isShuttingDown = false; 
    } catch (Exception e) { 
        throw new RuntimeException("Der Start des Servers ist fehlgeschlagen - Abbruch", e); 
    } 

    while (true) { 
        try { 
            Socket socket = serverSocket.accept(); 

            if (!isShuttingDown) { 
                new Thread(new EchoRequestHandler(socket)).start(); 
            } else { 
                logger.info("Server wird heruntergefahren"); 
                break; 
            } 
        } catch (IOException e) { 
            logger.error("Fehler beim Warten auf neue Verbindungen aufgetreten, Server wird gestoppt", e); 
            throw e; 
        } 
    } 
} 

public synchronized boolean isRunning() { 
    if (serverSocket != null && serverSocket.isBound() && !serverSocket.isClosed() && !isShuttingDown) { 
        return true; 
    } 
    return false; 
} 

public synchronized void shutdown() throws IOException { 
    if (isRunning()) { 
        isShuttingDown = true; 
        if (serverSocket != null && !serverSocket.isClosed()) { 
            try { 
                /* 
                 * Da der Server-Socket immer noch in seiner accept() 
                 * Methode wartet, würde das einfach Schließen des 
                 * Server-Sockets eine Ausnahme auslösen. Indem man 
                 * schnell einen Socket (Verbindung) zum Server-Socket 
                 * öffnet und sofort wieder schließt, wird die accept-Methode 
                 * des Server-Sockets zurückgegeben und da das 
                 * isShuttingDown-Flag dann falsch ist, wird der 
                 * Socket geschlossen. 
                 */ 
                new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()).close(); 

                serverSocket.close(); 
            } catch (IOException e) { 
                logger.error("Schließen des Server-Sockets ist fehlgeschlagen - Abbruch jetzt.", e); 
                throw e; 
            } 
        } 
    } else { 
        throw new IOException("Server-Socket ist bereits geschlossen, was nicht der Fall sein sollte."); 
    } 
} 
}

Die Testklasse macht folgendes:

paket multithreading;

import java.io.IOException;

import org.testng.annotations.Test;

public class Testing {

// @Test(invocationCount=10, skipFailedInvocations=true)
@Test
public void loadTest() throws InterruptedException, IOException {
    for (int i = 0; i < 10; i++) {
        final Server s = new Server();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s.start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }).start();
        Thread.sleep(500);
        gracefullyShutdownServer(s);
        Thread.sleep(1000);
    }
}

private void gracefullyShutdownServer(final Server server) throws InterruptedException {
    try {
        server.shutdown();
        while (server.isRunning()) {
            Thread.sleep(500);
        }
    } catch (IOException e) {
        System.err.println(e);
    }
}

}

Die Stacktrace sieht wie folgt aus:

ERROR 2011-03-13 16:14:23,537 [Thread-6] multithreading.Server: Fehler beim Warten auf neue Verbindungen aufgetreten, Server wird gestoppt
java.net.SocketException: Socket closed
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:390)
at java.net.ServerSocket.implAccept(ServerSocket.java:453)
at java.net.ServerSocket.accept(ServerSocket.java:421)
at multithreading.Server.start(Server.java:26)
at multithreading.Testing$1.run(Testing.java:18)
at java.lang.Thread.run(Thread.java:680)
java.net.SocketException: Socket closed
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:390)
at java.net.ServerSocket.implAccept(ServerSocket.java:453)
at java.net.ServerSocket.accept(ServerSocket.java:421)
at multithreading.Server.start(Server.java:26)
at multithreading.Testing$1.run(Testing.java:18)
at java.lang.Thread.run(Thread.java:680)

5voto

Mat Punkte 195740

J.N. hat recht, wie du mit dem Schließen umgehen solltest.

Was das Warum betrifft, warum es nicht funktioniert, glaube ich, dass das Problem darin liegt, dass dein Server-Code isShuttingDown ohne Synchronisierung liest. Ich sehe keinen Grund, warum die Änderung des Werts sofort für den Server-Thread sichtbar sein sollte. Es könnte also gut sein, dass es zu einem weiteren Durchlauf kommt.

Also, wie J.N. gesagt hat: behandele die Ausnahme im Server, wenn du akzeptierst. Wenn du wissen möchtest, ob die Ausnahme wahrscheinlich durch deinen Code beim Schließen des Sockets ausgelöst wird, behalte dein isShuttingDown bei und stelle sicher, dass du sicher darauf zugreifst. (Entweder ein synchronized (this) {} Block oder schreibe einen sehr kurzen synchronisierten Zugriff.)

In diesem speziellen Fall denke ich, dass es ausreicht, isShuttingDown volatile zu machen, wie in diesem developerWorks-Artikel detailliert beschrieben Java-Theorie und Praxis: Verwalten von Volatilität. Aber sei vorsichtig damit, es ist kein Allheilmittel.

4voto

J.N. Punkte 7865

Wie in diesem javadoc Link angegeben, ist es ein Fehler, einen Socket zu öffnen, um einen anderen zu schließen. Das Aufrufen von close auf Ihrem Socket sollte das "accept" selbst abbrechen, indem es eine Ausnahme wirft, die Sie sicher fangen und ignorieren können.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X