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

Сокет домена Unix в Java 16

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

1. Введение

В этом руководстве мы собираемся узнать о каналах сокетов домена Unix .

Мы рассмотрим некоторые теоретические основы, плюсы и минусы и создадим простое клиент-серверное приложение Java, которое использует каналы сокетов домена Unix для обмена текстовыми сообщениями.

Мы также рассмотрим, как использовать сокеты домена Unix для подключения к базе данных.

2. Каналы сокетов домена Unix

Традиционное взаимодействие между процессами включает сокеты TCP/IP , определяемые IP-адресом и номером порта. Они используются для сетевой связи в Интернете или частных сетях.

Сокеты домена Unix , с другой стороны, ограничены только для связи между процессами на одном физическом хосте. Они были функцией операционных систем Unix на протяжении десятилетий, но недавно были добавлены в Microsoft Windows. Таким образом, они больше не ограничиваются системами Unix.

Сокеты домена Unix адресуются по именам путей файловой системы , которые выглядят так же, как и другие имена файлов, например, /folder/socket или C:\folder\socket . По сравнению с соединениями TCP/IP они имеют более быстрое время установки, больший объем передаваемых данных и отсутствие угроз безопасности при приеме удаленных соединений. С другой стороны, самым большим недостатком является ограничение только одним физическим хостом.

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

3. Конфигурация сокета

Как мы узнали ранее, сокеты домена Unix основаны на именах путей файловой системы, поэтому во-первых, нам нужно определить путь для нашего файла сокета и преобразовать его в UnixDomainSocketAddress :

Path socketPath = Path
.of(System.getProperty("user.home"))
.resolve("foreach.socket");
UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(socketPath);

В нашем примере мы создаем сокет в домашнем каталоге пользователя в файле foreach.socket .

Одна вещь, которую нам нужно иметь в виду, это удаление файла сокета после каждого выключения нашего сервера:

Files.deleteIfExists(socketPath);

К сожалению, он не будет удален автоматически, и мы не сможем повторно использовать его для дальнейших подключений. Любая попытка повторного использования одного и того же пути приведет к исключению, говорящему, что этот адрес уже используется:

java.net.BindException: Address already in use

4. Получение сообщений

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

Во-первых, мы должны создать канал сокета сервера с протоколом Unix:

ServerSocketChannel serverChannel = ServerSocketChannel
.open(StandardProtocolFamily.UNIX);

Далее нам нужно связать его с адресом сокета, который мы создали ранее:

serverChannel.bind(socketAddress);

Теперь мы можем дождаться первого подключения клиента:

SocketChannel channel = serverChannel.accept();

Когда клиент подключается, сообщения будут поступать в байтовый буфер. Чтобы прочитать эти сообщения, нам нужно построить бесконечный цикл, который будет обрабатывать ввод и выводить каждое сообщение на консоль :

while (true) {
readSocketMessage(channel)
.ifPresent(message -> System.out.printf("[Client message] %s", message));
Thread.sleep(100);
}

В приведенном выше примере метод readSocketMessage отвечает за преобразование буфера канала сокета в строку:

private Optional<String> readSocketMessage(SocketChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer);
if (bytesRead < 0)
return Optional.empty();

byte[] bytes = new byte[bytesRead];
buffer.flip();
buffer.get(bytes);
String message = new String(bytes);
return Optional.of(message);
}

Нам нужно помнить, что сервер должен запускаться раньше клиента. Как и в нашем примере, он может принимать только одно клиентское соединение.

5. Отправка сообщений

Отправлять сообщения немного проще, чем их получать.

Единственное, что нам нужно настроить, это канал сокета с протоколом Unix и подключить его к нашему адресу сокета:

SocketChannel channel = SocketChannel
.open(StandardProtocolFamily.UNIX);
channel.connect(socketAddress);

Теперь мы можем подготовить текстовое сообщение:

String message = "Hello from ForEach Unix domain socket article";

преобразовать его в байтовый буфер:

ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.clear();
buffer.put(message.getBytes());
buffer.flip();

и записываем все данные в наш сокет:

while (buffer.hasRemaining()) {
channel.write(buffer);
}

Наконец, в журналах сервера появится следующий вывод:

[Client message] Hello from ForEach Unix domain socket article!

6. Подключение к базе данных

Сокеты домена Unix можно использовать для подключения к базе данных. Многие популярные дистрибутивы, такие как MongoDB или PostgreSQL, поставляются с готовой к использованию конфигурацией по умолчанию.

MongoDB, например, создает сокет домена Unix в /tmp/mongodb-27017.sock , который мы можем использовать непосредственно в конфигурации MongoClient :

MongoClient mongoClient = new MongoClient("/tmp/mongodb-27017.sock");

Одним из требований является добавление зависимости jnr.unixsocket в наш проект:

<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-unixsocket</artifactId>
<version>0.38.13</version>
</dependency>

С другой стороны, PostgreSQL дает нам возможность использовать доменные сокеты Unix со стандартом JDBC . Поэтому нам просто нужно указать дополнительный параметр socketFactory при создании соединения:

String dbUrl = "jdbc:postgresql://databaseName?socketFactory=org.newsclub.net.unix.
AFUNIXSocketFactory$FactoryArg&socketFactoryArg=/var/run/postgresql/.s.PGSQL.5432";
Connection connection = DriverManager
.getConnection(dbUrl, "dbUsername", "dbPassword")

Параметр socketFactory должен указывать на класс, который расширяет java.net.SocketFactory . Этот класс будет отвечать за создание доменных сокетов Unix вместо TCP/IP.

В нашем примере мы использовали класс AFUNIXSocketFactory из библиотеки junixsocket :

<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-core</artifactId>
<version>2.4.0</version>
</dependency>

7. Резюме

В этом руководстве мы узнали, как использовать каналы сокетов домена Unix. Мы рассмотрели отправку и получение сообщений с помощью сокетов домена Unix и узнали, как использовать сокеты домена Unix для подключения к базе данных. Как всегда, весь исходный код доступен на GitHub .