概述
在这个教程中,我们将学习如何使用Unix域套接字通道。我们将探讨一些理论基础知识、优缺点,并构建一个简单的Java客户端-服务器应用,使用Unix域套接字通道交换文本消息。我们还将研究如何利用Unix域套接字与数据库连接。
1. Unix域套接字通道
传统的进程间通信涉及基于TCP/IP的套接字,由IP地址和端口号定义,用于互联网或私有网络上的通信。相比之下,Unix域套接字仅限于同一物理主机上进程之间的通信。几十年来,它们一直是Unix操作系统的一部分,但最近已被添加到Microsoft Windows中,因此不再局限于Unix系统。
Unix域套接字通过文件系统路径名进行标识,看起来与其他文件名相似,例如*/folder/socket
或C:\folder\socket
。与TCP/IP连接相比,它们具有更快的设置时间、更高的数据传输速率,且接受远程连接时没有安全风险。然而,主要缺点是只限于单个物理主机的通信。
请注意,只要我们在共享卷上创建套接字,我们甚至可以在同一系统上的容器之间使用Unix域套接字进行通信。
2. 套接字配置
由于Unix域套接字基于文件系统路径名,首先我们需要为套接字文件定义一个路径,并将其转换为UnixDomainSocketAddress
:
Path socketPath = Path
.of(System.getProperty("user.home"))
.resolve("baeldung.socket");
UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(socketPath);
在示例中,我们在用户的主目录下创建名为baeldung.socket
的套接字文件。
需要记住的是,在每次关闭服务器后,我们需要删除套接字文件:
Files.deleteIfExists(socketPath);
不幸的是,它不会自动删除,我们无法再使用它进行后续连接。尝试使用相同的路径将导致异常,提示该地址已使用:
java.net.BindException: Address already in use
3. 接收消息
接下来,我们可以启动一个服务器,从套接字通道接收消息。
首先,我们需要创建一个具有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
方法负责将套接字通道的缓冲区转换为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);
}
需要注意的是,服务器必须先于客户端启动。如示例所示,它只能接受一个客户端连接。
4. 发送消息
发送消息比接收消息稍微简单一些。
我们只需要设置一个具有Unix协议的套接字通道,并将其连接到我们的套接字地址:
SocketChannel channel = SocketChannel
.open(StandardProtocolFamily.UNIX);
channel.connect(socketAddress);
现在我们可以准备一个文本消息:
String message = "Hello from Baeldung 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 Baeldung Unix domain socket article!
5. 连接到数据库
Unix域套接字可用于连接数据库。许多流行发行版,如MongoDB或PostgreSQL,都提供了默认配置,可以直接使用。
例如,MongoDB会在/tmp/mongodb-27017.sock
创建一个Unix域套接字,我们可以在创建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套接字。
在示例中,我们使用了来自junixsocket库的AFUNIXSocketFactory
类:
<dependency>
<groupId>com.kohlschutter.junixsocket</groupId>
<artifactId>junixsocket-core</artifactId>
<version>2.4.0</version>
</dependency>
6. 总结
在这篇教程中,我们了解了如何使用Unix域套接字通道。我们涵盖了使用Unix域套接字发送和接收消息的方法,以及如何使用Unix域套接字连接数据库。如往常一样,所有源代码可在GitHub上找到。