Сокеты: обнаружение доступности порта с помощью Java

123

Как программно определить доступность порта на данном компьютере с помощью Java?

т.е. по номеру порта определить, используется он уже или нет ?.

user54075
источник
2
Хотя этот вопрос касается того, как узнать, используется ли уже данный порт, вы, возможно, приземлились здесь, пытаясь найти способ получить номер бесплатного порта, который stackoverflow.com/questions/2675362/… (со ссылкой на мою суть .github.com / 3429822 ) покрывает лучше.
vorburger
С какой целью? Если вы просто хотите найти свободный сокет для прослушивания, просто укажите ноль. Любая техника сканирования подвержена проблемам с временным окном: порт может быть пустым, когда вы сканируете, и занятым, когда вы заходите, чтобы подать заявку.
Маркиз Лорн,

Ответы:

91

Это реализация из проекта Apache camel :

/**
 * Checks to see if a specific port is available.
 *
 * @param port the port to check for availability
 */
public static boolean available(int port) {
    if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) {
        throw new IllegalArgumentException("Invalid start port: " + port);
    }

    ServerSocket ss = null;
    DatagramSocket ds = null;
    try {
        ss = new ServerSocket(port);
        ss.setReuseAddress(true);
        ds = new DatagramSocket(port);
        ds.setReuseAddress(true);
        return true;
    } catch (IOException e) {
    } finally {
        if (ds != null) {
            ds.close();
        }

        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
                /* should not be thrown */
            }
        }
    }

    return false;
}

Они также проверяют DatagramSocket, чтобы проверить, доступен ли порт в UDP и TCP.

Надеюсь это поможет.

Дэвид Сантамария
источник
5
Хороший вариант этого, если у вас есть MINA, - это AvailablePortFinder.getNextAvailable (1024), который дает вам следующий непривилегированный порт.
Alain O'Dea
9
Однако это не улавливает все. Меня укусил запущенный nginx на TCP 8000, в то время как вышеупомянутая процедура сообщает 8000 как доступный. Понятия не имею, почему - я подозреваю, что nginx делает некоторые подлые вещи (это на OS X). Для меня обходной путь - это сделать описанное выше, а также открыть новый Socket ("localhost", порт), а затем вернуть false, если мы не получим исключения. Мысли?
Небольшая
2
Та же проблема в Windows при попытке определить, работает ли SMTP-сервер papercut. Решение, в котором вы открываете соединение с портом, а не пытаетесь привязаться к нему (как это было предложено комментарием Partly Clodys и ответом Ивана), кажется, работает более надежно.
stian
4
Интересный. Вызов setReuseAddress () совершенно бессмысленен после того, как сокет уже привязан, и это также полностью противоречит цели кода.
Marquis of Lorne
3
Этот код подвержен проблемам с временным окном. Если цель состоит в том, чтобы создать ServerSocketпрослушивающий какой-либо порт, это то, что он должен сделать, и вернуть файл ServerSocket. Создание его, а затем его закрытие и возврат дает нулевую гарантию того, что последующий ServerSocketможет быть создан с использованием этого порта. То же самое для DatagramSocket.
Маркиз Лорн
41

Для Java 7 вы можете использовать try-with-resource для более компактного кода:

private static boolean available(int port) {
    try (Socket ignored = new Socket("localhost", port)) {
        return false;
    } catch (IOException ignored) {
        return true;
    }
}
eivindw
источник
15
Это не проверяет, доступен ли порт. Он проверяет, находится ли он в состоянии LISTEN, доступен ли IP-адрес и т. Д.
Marquis of Lorne
1
Кроме того, этот тест довольно медленный (почти секунда на порт).
JMax
Разве он не должен возвращать false при обнаружении исключения IOException?
Baroudi Safwen
@BaroudiSafwen Это полностью зависит от фактического исключения. Ибо ConnectException: 'connection refused'да, он должен вернуть false. Для тайм-аутов ничего, что он мог бы вернуть, не будет действительным, поскольку фактический ответ неизвестен. Поэтому эта техника для этой цели бесполезна.
Marquis of Lorne
36

Похоже, что с Java 7 ответ Дэвида Сантамарии больше не работает надежно. Однако похоже, что вы все еще можете надежно использовать Socket для проверки соединения.

private static boolean available(int port) {
    System.out.println("--------------Testing port " + port);
    Socket s = null;
    try {
        s = new Socket("localhost", port);

        // If the code makes it this far without an exception it means
        // something is using the port and has responded.
        System.out.println("--------------Port " + port + " is not available");
        return false;
    } catch (IOException e) {
        System.out.println("--------------Port " + port + " is available");
        return true;
    } finally {
        if( s != null){
            try {
                s.close();
            } catch (IOException e) {
                throw new RuntimeException("You should handle this error." , e);
            }
        }
    }
}
TwentyMiles
источник
Я хочу проверить, доступен ли прокси-сервер для звонков от его имени.
Dejell 01
1
Ответ @ DavidSantamaria никогда не работал. Ничего общего с Java 7.
Marquis of Lorne
35

Если вас не слишком заботит производительность, вы всегда можете попробовать прослушивать порт с помощью класса ServerSocket . Если он вызывает исключение, скорее всего, он используется.

public static boolean isAvailable(int portNr) {
  boolean portFree;
  try (var ignored = new ServerSocket(portNr)) {
      portFree = true;
  } catch (IOException e) {
      portFree = false;
  }
  return portFree;
}

РЕДАКТИРОВАТЬ: Если все, что вы пытаетесь сделать, это выбрать свободный порт, new ServerSocket(0)вы найдете его для вас.

Спенсер Рупорт
источник
Используйте finally для очистки (попробуйте {...} catch {...} finally {if (socket! = Null) socket.close ();}
Хосам Али,
Вы также можете установить "portTaken = (socket == null);" в конце вместо того, чтобы делать это в улове.
Хосам Али,
1
Я не чувствовал, что это попадает в рамки заданного автором вопроса.
Спенсер Рупорт,
1
Есть ли способ сделать это без try / catch? Я бы не возражал против использования любого доступного порта для своих целей, но перебирать номера портов и пробовать / перебирать каждый, пока не найду свободный, немного расточительно. Нет ли где-нибудь метода 'native boolean available (int)', который просто проверяет?
Орен Шалев
27
new SeverSocket (0) автоматически выберет свободный порт.
Спенсер Рупорт,
10

Следующее решение основано на реализации SocketUtils Spring-core (лицензия Apache).

По сравнению с другими решениями Socket(...)его использование довольно быстрое (тестирование 1000 портов TCP менее чем за секунду):

public static boolean isTcpPortAvailable(int port) {
    try (ServerSocket serverSocket = new ServerSocket()) {
        // setReuseAddress(false) is required only on OSX, 
        // otherwise the code will not work correctly on that platform          
        serverSocket.setReuseAddress(false);
        serverSocket.bind(new InetSocketAddress(InetAddress.getByName("localhost"), port), 1);
        return true;
    } catch (Exception ex) {
        return false;
    }
}       
JMax
источник
3

Решения на основе сокетов try / catch могут не давать точных результатов (адрес сокета - «localhost», и в некоторых случаях порт может быть «занят» не интерфейсом обратной петли, и, по крайней мере, в Windows я видел, что этот тест не прошел, т.е. Прот ложно объявлен доступным).

Существует классная библиотека под названием SIGAR , следующий код может вас зацепить:

Sigar sigar = new Sigar();
int flags = NetFlags.CONN_TCP | NetFlags.CONN_SERVER | NetFlags.CONN_CLIENT;             NetConnection[] netConnectionList = sigar.getNetConnectionList(flags);
for (NetConnection netConnection : netConnectionList) {
   if ( netConnection.getLocalPort() == port )
        return false;
}
return true;
Шмиль Кот
источник
Я получаю: «no sigar-x86-winnt.dll in java.library.path» К сожалению, для этого требуется загрузка некоторой дополнительной dll, которая не очень эффективна в корпоративной среде (у меня нет контроля над системой CI). Так плохо .... Все равно спасибо.
uthomas
@uthomas Я думаю, у вас все еще есть контроль над пакетом развертывания приложения, если это так, вы всегда можете предоставить встроенные библиотеки DLL / SO Sigar рядом с вашими JAR и прагматично установить путь к библиотеке JAVA (с помощью простого взлома вы можете повлиять на него даже после приложение было запущено JVM).
Кот Шмиль
2

Очистка ответа, указанного Дэвидом Сантамария:

/**
 * Check to see if a port is available.
 *
 * @param port
 *            the port to check for availability.
 */
public static boolean isPortAvailable(int port) {
    try (var ss = new ServerSocket(port); var ds = new DatagramSocket(port)) {
        return true;
    } catch (IOException e) {
        return false;
    }
}

Это по-прежнему зависит от состояния гонки, указанного пользователем 207421 в комментариях к ответу Дэвида Сантамария (что-то может захватить порт после того, как этот метод закроет ServerSocketи DatagramSocketи вернется).

Люк Хатчисон
источник
1

В моем случае это помогло попытаться подключиться к порту - если служба уже присутствует, она ответит.

    try {
        log.debug("{}: Checking if port open by trying to connect as a client", portNumber);
        Socket sock = new Socket("localhost", portNumber);          
        sock.close();
        log.debug("{}: Someone responding on port - seems not open", portNumber);
        return false;
    } catch (Exception e) {         
        if (e.getMessage().contains("refused")) {
            return true;
    }
        log.error("Troubles checking if port is open", e);
        throw new RuntimeException(e);              
    }
Иван
источник
1
Не то, о чем просили.
Marquis of Lorne
0

В моем случае мне пришлось использовать класс DatagramSocket.

boolean isPortOccupied(int port) {
    DatagramSocket sock = null;
    try {
        sock = new DatagramSocket(port);
        sock.close();
        return false;
    } catch (BindException ignored) {
        return true;
    } catch (SocketException ex) {
        System.out.println(ex);
        return true;
    }
}

Не забудьте сначала импортировать

import java.net.DatagramSocket;
import java.net.BindException;
import java.net.SocketException;
kmchmk
источник
Это работает для портов UDP. Вопрос, похоже, касается TCP-портов.
Маркиз Лорн,
-2

Я пробовал что-то подобное, и у меня это отлично сработало

            Socket Skt;
            String host = "localhost";
            int i = 8983; // port no.

                 try {
                    System.out.println("Looking for "+ i);
                    Skt = new Socket(host, i);
                    System.out.println("There is a Server on port "
                    + i + " of " + host);
                 }
                 catch (UnknownHostException e) {
                    System.out.println("Exception occured"+ e);

                 }
                 catch (IOException e) {
                     System.out.println("port is not used");

                 }
Mayu Jadhv
источник
1
Не то, о чем просили.
Маркиз Лорн,
А также утечка сокета.
Маркиз Лорн