Generic Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we're going to learn about Unix domain socket channels.

We'll cover some theoretical basics, pros, cons, and build a simple Java client-server application that uses Unix domain socket channels to exchange text messages.

We'll also take a look at how to use Unix domain sockets for connecting with a database.

2. Unix Domain Socket Channels

Traditional inter-process communication involves TCP/IP sockets defined by IP address and port number. They're used for network communications on the internet or private networks.

Unix domain sockets, on the other hand, are limited only for communication between processes on the same physical host. They have been a feature of Unix operating systems for decades but have been added recently to Microsoft Windows. As such, they are no longer limited to Unix systems.

Unix domain sockets are addressed by filesystem path names that look much the same as other filenames, for example, /folder/socket or C:\folder\socket. Compared to the TCP/IP connections, they have faster setup time, higher data through output, and no security risks on accepting remote connections. The biggest drawback, on the other hand, is the limitation to just a single physical host.

Note that we can even use Unix domain sockets for communication between containers on the same system as long as we create the sockets on a shared volume.

3. Socket Configuration

As we learned previously, Unix domain sockets are based on filesystem path names, so firstly, we'll need to define a path for our socket file and transform it into UnixDomainSocketAddress:

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

In our example, we create the socket in the user's home directory under the baeldung.socket file.

One thing we need to have in mind is deleting the socket file after each shut down of our server:

Files.deleteIfExists(socketPath);

Unfortunately, it won't get deleted automatically, and we won't be able to reuse it for further connections. Any attempt to reuse the same path will end up with an exception saying that this address is already in use:

java.net.BindException: Address already in use

4. Receiving Messages

The next thing we can do is start a server that will receive messages from the socket channel.

Firstly, we should create a server socket channel with a Unix protocol:

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

Further, we need to bind it with the socket address we've created previously:

serverChannel.bind(socketAddress);

Now we can wait for the first client connection:

SocketChannel channel = serverChannel.accept();

When the client connects, the messages will come in a byte buffer. To read these messages, we'll need to build an infinite loop that will handle the input and print every message to the console:

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

In the above example, the method readSocketMessage is responsible for transforming the socket channel buffer into a String:

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);
}

We need to remember that the server needs to start before the client. As in our example, it can accept just a single client connection.

5. Sending Messages

Sending messages is a bit simpler than receiving them.

The only thing we need to set up is a socket channel with Unix protocol and connect it to our socket address:

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

Now we can prepare a text message:

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

transform it into a byte buffer:

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

and write the whole data to our socket:

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

Finally, the following output will pop out in the Server logs:

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

6. Connecting to a Database

Unix domain sockets can be used to connect with a database. Many popular distributions like MongoDB or PostgreSQL come with a default configuration that is ready for use.

MongoDB, for example, creates a Unix domain socket at /tmp/mongodb-27017.sock that we can use directly in the MongoClient configuration:

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

One requirement is to add the jnr.unixsocket dependency to our project:

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

On the other hand, PostgreSQL gives us the possibility to use Unix domain sockets with the JDBC standard. Therefore, we simply need to provide an additional socketFactory parameter while creating the connection:

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")

The socketFactory parameter should point to a class that extends java.net.SocketFactory. This class will be responsible for creating Unix domain sockets instead of TCP/IP ones.

In our example, we've used AFUNIXSocketFactory class from the junixsocket library:

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

7. Summary

In this tutorial, we've learned how to use Unix domain socket channels. We've covered both sending and receiving messages with Unix domain sockets, and we've learned how to use Unix domain sockets to connect with a database. As always, all the source code is available over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
Comments are closed on this article!