1. Introduction
1.绪论
In this tutorial, we’re going to learn about Unix domain socket channels.
在本教程中,我们将学习Unix域socket通道。
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.
我们将介绍一些理论基础知识、优点、缺点,并建立一个简单的Java客户-服务器应用程序,使用Unix域套接字通道来交换文本信息。
We’ll also take a look at how to use Unix domain sockets for connecting with a database.
我们还将看一下如何使用Unix域套接字与数据库连接。
2.
2.
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.
传统的进程间通信涉及TCP/IP套接字,由IP地址和端口号定义。它们被用于互联网或私人网络上的网络通信。
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域套接字,只限于同一物理主机上的进程之间的通信。几十年来,它们一直是Unix操作系统的一个特征,但最近被添加到Microsoft Windows中。因此,它们不再局限于Unix系统。
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.
Unix域套接字是通过filesystem路径名来寻址的,看起来与其他文件名基本相同,例如,/folder/socket或C:\folder\socket。与TCP/IP连接相比,它们有更快的设置时间,更高的数据通过输出,并且在接受远程连接时没有安全风险。另一方面,最大的缺点是只限制在一个物理主机上。
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.
请注意,只要我们在共享卷上创建套接字,我们甚至可以使用Unix域套接字在同一系统的containers之间进行通信。
3. Socket Configuration
3.插座配置
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:
正如我们之前学到的,Unix域套接字是基于文件系统路径名的,所以首先我们需要为我们的套接字文件定义一个路径,并将其转化为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.
在我们的例子中,我们在用户的主目录下的baeldung.socket文件中创建套接字。
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
4.接收信息
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:
首先,我们应该创建一个具有Unix协议的服务器套接字通道。
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:
在上面的例子中,方法readSocketMessage负责将套接字通道buffer转换为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
5.发送信息
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:
我们唯一需要设置的是一个具有Unix协议的套接字通道,并将其连接到我们的套接字地址。
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
6.连接到一个数据库
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.
Unix 域套接字可以用来与数据库连接。许多流行的发行版,如MongoDB或PostgreSQL,都有一个可以使用的默认配置。
MongoDB, for example, creates a Unix domain socket at /tmp/mongodb-27017.sock that we can use directly in the MongoClient configuration:
例如,MongoDB在/tmp/mongodb-27017.sock处创建了一个Unix域套接字,我们可以在MongoClient配置中直接使用。
MongoClient mongoClient = new MongoClient("/tmp/mongodb-27017.sock");
One requirement is to add the jnr.unixsocket dependency to our project:
一个要求是将jnr.unixsocket依赖性添加到我们的项目。
<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:
另一方面,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")
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.
socketFactory参数应指向一个扩展java.net.SocketFactory的类。这个类将负责创建Unix域套接字,而不是TCP/IP的套接字。
In our example, we’ve used AFUNIXSocketFactory class from the junixsocket library:
在我们的例子中,我们使用了AFUNIXSocketFactory类,来自junixsocket库。
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-core</artifactId>
<version>2.4.0</version>
</dependency>
7. Summary
7.摘要
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.
在本教程中,我们已经学会了如何使用Unix域套接字通道。我们已经涵盖了用Unix域套接字发送和接收消息,并且我们已经学会了如何使用Unix域套接字来连接数据库。像往常一样,所有的源代码都可以在GitHub上找到。