Многопоточный сервер Java * иногда * генерирует исключение SocketException (Socket закрыто) в методе ServerSocket.accept () - PullRequest
3 голосов
/ 12 марта 2011

Я искал эту проблему часами и часами, и я не могу найти достойного решения или объяснения, почему возникает это исключение (java.net.SocketException: Socket closed).Мой последний подход - спросить вас, ребята.

Я создал простое приложение сервер-клиент для тестирования (хотя «настоящее» приложение использует ту же логику), см. Ниже.

Если я повторно вызову один и тот же тестовый пример (например, с помощью параметра аннотации invocationcount TestNG или с помощью простого цикла for), в какой-то момент будет java.net.SocketException: Сокет закрыт .

Тестовый пример, приведенный ниже, в основном просто запускает сервер (открывает сокет сервера), ждет несколько миллисекунд и затем снова закрывает сокет.Закрытие сокета сервера включает в себя открытие сокета, чтобы сервер возвращался из метода ServerSocket.accept () (см. Server # shutdown ()).

Хотя это может быть проблема многопоточности с кодом сразу послеServerSocket.accept () - строка.Так что я временно окружил его синхронизированным блоком - тоже не помогло.

У вас есть идеи, почему выбрасывается это исключение?

Best, Крис

Server.java выглядит следующим образом:

package 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("Starting up the server failed - aborting", e);
    }

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

            if (!isShuttingDown) {
                new Thread(new EchoRequestHandler(socket)).start();
            } else {
                logger.info("Server is going to shutdown");
                break;
            }
        } catch (IOException e) {
            logger.error("Error occured while waiting for new connections, stopping server", 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 {
                /*
                 * since the server socket is still waiting in it's accept()
                 * method, just closing the server socket would cause an
                 * exception to be thrown. By quickly opening a socket
                 * (connection) to the server socket and immediately closing
                 * it again, the server socket's accept method will return
                 * and since the isShuttingDown flag is then false, the
                 * socket will be closed.
                 */
                new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()).close();

                serverSocket.close();
            } catch (IOException e) {
                logger.error("Closing the server socket has failed - aborting now.", e);
                throw e;
            }
        }
    } else {
        throw new IOException("Server socket is already closed which should not be the case.");
    }
}
}

Тестовый класс выполняет следующие действия:

package 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);
    }
}

}

Трассировка стека выглядит какследует:

ERROR 2011-03-13 16:14:23,537 [Thread-6] multithreading.Server: Error occured while waiting for new connections, stopping server
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)

Ответы [ 2 ]

5 голосов
/ 13 марта 2011

J.N. правильно относительно того, как вы должны справляться с закрытием.

Что касается того, почему это не работает, я думаю, что дело в том, что код вашего сервера читает isShuttingDown без синхронизации. Я не понимаю, почему изменение значения должно быть сразу видно в потоке сервера. Так что вполне может пойти на следующий раунд.

Так как J.N. сказал: иметь дело с исключением на сервере при принятии. Если вы хотите знать, является ли исключение , вероятно, , вызванным вашим кодом, выполняющим close в сокете, держите ваш isShuttingDown рядом, убедитесь, что вы получили к нему безопасный доступ. (Либо блок synchronized (this) {}, либо запишите очень короткий синхронизированный метод доступа.)

В этом конкретном случае я думаю, что достаточно сделать isShuttingDown volatile , как подробно описано в этой статье developerWorks Теория и практика Java: Управление волатильностью . Но будьте осторожны с этим, это не волшебная пуля.

4 голосов
/ 13 марта 2011

Как указано в этой ссылке javadoc , открытие сокета для закрытия другого является ошибкой.Вызов close на вашем сокете должен сам отменить «accept», выдав исключение, которое вы можете перехватить и проигнорировать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...