JEP 380 Unix Domain Socket Channels

Contexte

Les sockets Unix sont un moyen de communication inter-processus. Ils se basent sur le système de fichiers pour leurs fonctionnements. (Et oui, tout est fichier dans l’univers Unix). Plusieurs processus peuvent ainsi communiquer ensemble sans passer par la pile réseau. Cela est pris en charge par le noyau et c’est plus performant, même si on le compare avec l’utilisation de l’interface locale.

Nous comparons avec le réseau local car la limitation est que les processus doivent être sur la même machine.

Cependant, dans le monde des conteneurs, beaucoup de processus tournent localement sur la machine. Moyennant l’accès à un système de fichiers partagés, ils pourraient ainsi utiliser ce type de socket afin d' améliorer les performances.

astuce De plus, étant un fichier, cela permet de gérer les droits avec les permissions Unix comme n’importe quel fichier.

Pour information, il existe déjà des librairies afin de réaliser cette opération. Par exemple, le projet junixsocket, mais c’est une librairie utilisant JNI.

L’objectif est de pouvoir en bénéfier avec un simple JDK sans dépendance.

avertissement Windows 10 et Windows Server 2019 supportent aussi les sockets Unix.

Illustration

Pour illuster l’usage de ce type de socket, nous allons prendre l’exemple de PostgreSQL.

Sur une base de données PostgreSQL, nous pouvons se connecter localement au serveur de base de données sans utiliser le réseau. Par exemple, si nous utilisons la commande psql -l pour lister les bases présentes. Nous pouvons obtenir par exemple le mesage suivant :

> psql -l
psql: could not connect to server: No such file or directory
	Is the server running locally and accepting
	connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

Si maintenant, nous prenons un utilisateur qui a le droit sur le fichier, cela va fonctionner.

> psql -l
                                List of databases
    Name     |    Owner     | Encoding | Collate | Ctype |   Access privileges
-------------+--------------+----------+---------+-------+-----------------------
 postgres    | postgres     | UTF8     | C       | C     |
 template0   | postgres     | UTF8     | C       | C     | =c/postgres          +
             |              |          |         |       | postgres=CTc/postgres
 template1   | postgres     | UTF8     | C       | C     | =c/postgres          +
             |              |          |         |       | postgres=CTc/postgres

De même, si l’emplacement du fichier n’est pas celui par défaut, nous pouvons utiliser la variable PGHOST pour définir le chemin. Il faudra toujours que l’utilisateur possède les droits sur le fichier.

export PGHOST=/var/run/postgresql/srv

Cela va permettre d’accorder des privilièges particuliers si nous sommes connectés à la machine en SSH. Ainsi, nous allons pouvoir au contraire réduire les accès au minimum côté réseau.

Socket Channel (classique)

Revenons à Java et notamment au code associé à cette évolution. Mais avant, pour mieux comprendre la différence, regardons le code pour la création d’une socket classique, nous avons le code suivant coté serveur :

ServerSocketChannel ssc = ServerSocketChannel.open(/*StandardProtocolFamily.INET*/);
ssc.bind(new InetSocketAddress("127.0.0.1", 8085));
while ( true ) {
  SocketChannel socketChannel = ssc.accept();
  // Utilisation du SocketChannel sc
  ...
}

Coté client, nous avons le code suivant :

SocketChannel sc = SocketChannel.open(/*StandardProtocolFamily.INET*/);
sc.connect(new InetSocketAddress("127.0.0.1", 8085));
// Utilisation du SocketChannel sc
...

avertissement La méthode SocketChannel.open() et ServerSocketChannel peuvent prendre un paramètre : ProtocolFamily. Avant le JDK 16, nous avons le choix entre StandardProtocolFamily.INET et StandardProtocolFamily.INET6 en fonction si nous souhaitons utiliser IPv4 ou IPv6. Sans argument, la JVM choisit le protocole en fonction de la présence ou non du support d’IPv6

Nous pouvons utiliser :

  • L’api Stream (InputStream / OutputStream) de la socket avec la méthode SocketChannel.getSocket()`.

  • L’api ByteByffer

Socket Channel (unix domain)

Coté socket Unix, cela n’est pas plus compliqué. Nous avons une nouvelle famille de protocole StandardProtocolFamily.UNIX et nous avons une classe qui représente l’adresse correspondante, soit un simple fichier.

ServerSocketChannel ssc = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
ssc.bind(UnixDomainSocketAddress.of("/tmp/mon-sock"));
while ( true ) {
  SocketChannel sc = ssc.accept();
  // Utilisation du SocketChannel sc
  ...
}

Coté client, nous avons le code suivant :

SocketChannel sc = SocketChannel.open(StandardProtocolFamily.UNIX);
sc.connect(UnixDomainSocketAddress.of("/tmp/mon-sock"));
// Utilisation du SocketChannel sc
...

Seul, l’API ByteBuffer est disponible.

avertissement La méthode SocketChannel.getSocket() n’est pas implémentée.