Перейти к основному содержимому

Работа с сетевыми интерфейсами в Java

· 6 мин. чтения

1. Обзор

В этой статье мы сосредоточимся на сетевых интерфейсах и на том, как получить к ним программный доступ в Java.

Проще говоря, сетевой интерфейс — это точка взаимосвязи между устройством и любым его сетевым соединением .

На повседневном языке мы называем их сетевыми интерфейсными картами (NIC), но они не обязательно должны быть аппаратными.

Например, популярный локальный IP-адрес 127.0.0.1 , который мы часто используем при тестировании веб-приложений и сетевых приложений, представляет собой петлевой интерфейс, который не является прямым аппаратным интерфейсом.

Конечно, системы часто имеют несколько активных сетевых подключений, таких как проводной Ethernet, WIFI, Bluetooth и т. д.

В Java основным API, который мы можем использовать для прямого взаимодействия с ними, является класс java.net.NetworkInterface . И так, чтобы поскорее приступить к работе, давайте импортируем полный пакет:

import java.net.*;

2. Зачем обращаться к сетевым интерфейсам?

Большинство Java-программ, вероятно, не будут взаимодействовать с ними напрямую; однако есть особые сценарии, когда нам действительно нужен такой низкоуровневый доступ.

Наиболее выдающимся из них является случай, когда в системе имеется несколько карт, и вы хотели бы иметь свободу выбора конкретного интерфейса для использования сокета с . В таком сценарии мы обычно знаем имя, но не обязательно IP-адрес.

Обычно, когда мы хотим установить сокетное соединение с определенным адресом сервера:

Socket socket = new Socket();
socket.connect(new InetSocketAddress(address, port));

Таким образом, система выберет подходящий локальный адрес, привяжется к нему и свяжется с сервером через его сетевой интерфейс. Однако такой подход не позволяет нам выбирать самостоятельно.

Здесь мы сделаем предположение; мы не знаем адрес, но мы знаем имя. Просто в демонстрационных целях предположим, что нам нужно соединение через петлевой интерфейс, по соглашению его имя lo , по крайней мере, в системах Linux и Windows, в OSX это lo0 :

NetworkInterface nif = NetworkInterface.getByName("lo");
Enumeration<InetAddress> nifAddresses = nif.getInetAddresses();

Socket socket = new Socket();
socket.bind(new InetSocketAddress(nifAddresses.nextElement(), 0));
socket.connect(new InetSocketAddress(address, port));

Поэтому мы сначала извлекаем сетевой интерфейс, подключенный к lo , извлекаем прикрепленные к нему адреса, создаем сокет, привязываем его к любому из перечисленных адресов, которые мы даже не знаем во время компиляции, а затем подключаемся.

Объект NetworkInterface содержит имя и набор назначенных ему IP-адресов. Таким образом, привязка к любому из этих адресов гарантирует связь через этот интерфейс.

На самом деле это ничего особенного не говорит об API. Мы знаем, что если мы хотим, чтобы наш локальный адрес был локальным, первого фрагмента было бы достаточно, если бы мы просто добавили код привязки.

Кроме того, нам никогда не придется выполнять все несколько шагов, поскольку у localhost есть один хорошо известный адрес, 127.0.0.1 , и мы можем легко привязать к нему сокет.

Однако в вашем случае lo мог бы представлять другие интерфейсы, такие как Bluetooth — net1 , беспроводная сеть — net0 или ethernet — eth0 . В таких случаях вы не будете знать IP-адрес во время компиляции.

3. Получение сетевых интерфейсов

В этом разделе мы рассмотрим другие доступные API для получения доступных интерфейсов. В предыдущем разделе мы видели только один из этих подходов; статический метод getByName () .

Стоит отметить, что у класса NetworkInterface нет общедоступных конструкторов, поэтому мы, конечно же, не можем создать новый экземпляр. Вместо этого мы собираемся использовать доступные API для его извлечения.

API, который мы рассмотрели до сих пор, используется для поиска сетевого интерфейса по указанному имени:

@Test
public void givenName_whenReturnsNetworkInterface_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");

assertNotNull(nif);
}

Он возвращает null , если для имени нет:

@Test
public void givenInExistentName_whenReturnsNull_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("inexistent_name");

assertNull(nif);
}

Второй API — это getByInetAddress() , он также требует, чтобы мы предоставили известный параметр, на этот раз мы можем указать IP-адрес:

@Test
public void givenIP_whenReturnsNetworkInterface_thenCorrect() {
byte[] ip = new byte[] { 127, 0, 0, 1 };

NetworkInterface nif = NetworkInterface.getByInetAddress(
InetAddress.getByAddress(ip));

assertNotNull(nif);
}

Или имя хозяина:

@Test
public void givenHostName_whenReturnsNetworkInterface_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByInetAddress(
InetAddress.getByName("localhost"));

assertNotNull(nif);
}

Или, если вы конкретно говорите о локальном хосте:

@Test
public void givenLocalHost_whenReturnsNetworkInterface_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByInetAddress(
InetAddress.getLocalHost());

assertNotNull(nif);
}

Другой альтернативой является явное использование интерфейса loopback:

@Test
public void givenLoopBack_whenReturnsNetworkInterface_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByInetAddress(
InetAddress.getLoopbackAddress());

assertNotNull(nif);
}

Третий подход, доступный только с Java 7, заключается в получении сетевого интерфейса по его индексу:

NetworkInterface nif = NetworkInterface.getByIndex(int index);

Последний подход предполагает использование API getNetworkInterfaces . Он возвращает перечисление всех доступных сетевых интерфейсов в системе. Нам нужно извлечь возвращенные объекты в цикле, стандартная идиома использует List :

Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();

for (NetworkInterface nif: Collections.list(nets)) {
//do something with the network interface
}

4. Параметры сетевого интерфейса

Есть много ценной информации, которую мы можем получить от одного после извлечения его объекта. Одним из наиболее полезных является список присвоенных ему IP-адресов .

Мы можем получить IP-адреса, используя два API. Первый API — это getInetAddresses() . Он возвращает перечисление экземпляров InetAddress , которые мы можем обрабатывать по своему усмотрению:

@Test
public void givenInterface_whenReturnsInetAddresses_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");
Enumeration<InetAddress> addressEnum = nif.getInetAddresses();
InetAddress address = addressEnum.nextElement();

assertEquals("127.0.0.1", address.getHostAddress());
}

Второй API — это getInterfaceAddresses() . Он возвращает список экземпляров InterfaceAddress , которые являются более мощными, чем экземпляры InetAddress . Например, кроме IP-адреса вас может интересовать широковещательный адрес:

@Test
public void givenInterface_whenReturnsInterfaceAddresses_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");
List<InterfaceAddress> addressEnum = nif.getInterfaceAddresses();
InterfaceAddress address = addressEnum.get(0);

InetAddress localAddress=address.getAddress();
InetAddress broadCastAddress = address.getBroadcast();

assertEquals("127.0.0.1", localAddress.getHostAddress());
assertEquals("127.255.255.255",broadCastAddress.getHostAddress());
}

Мы можем получить доступ к сетевым параметрам интерфейса помимо имени и назначенных ему IP-адресов. Чтобы проверить, работает ли он:

@Test
public void givenInterface_whenChecksIfUp_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");

assertTrue(nif.isUp());
}

Чтобы проверить, является ли это петлевым интерфейсом:

@Test
public void givenInterface_whenChecksIfLoopback_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");

assertTrue(nif.isLoopback());
}

Чтобы проверить, представляет ли он сетевое соединение точка-точка:

@Test
public void givenInterface_whenChecksIfPointToPoint_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");

assertFalse(nif.isPointToPoint());
}

Или, если это виртуальный интерфейс:

@Test
public void givenInterface_whenChecksIfVirtual_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");
assertFalse(nif.isVirtual());
}

Чтобы проверить, поддерживается ли многоадресная рассылка:

@Test
public void givenInterface_whenChecksMulticastSupport_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");

assertTrue(nif.supportsMulticast());
}

Или получить его физический адрес, обычно называемый MAC-адресом:

@Test
public void givenInterface_whenGetsMacAddress_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("lo");
byte[] bytes = nif.getHardwareAddress();

assertNotNull(bytes);
}

Другим параметром является Максимальная единица передачи, которая определяет максимальный размер пакета, который может быть передан через этот интерфейс:

@Test
public void givenInterface_whenGetsMTU_thenCorrect() {
NetworkInterface nif = NetworkInterface.getByName("net0");
int mtu = nif.getMTU();

assertEquals(1500, mtu);
}

5. Вывод

В этой статье мы показали сетевые интерфейсы, как получить к ним программный доступ и зачем нам нужен к ним доступ.

Полный исходный код и примеры, используемые в этой статье, доступны в проекте Github .