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 .