Rust网络编程库socketpair的使用,实现进程间高效双向通信的Unix域套接字接口
Rust网络编程库socketpair的使用,实现进程间高效双向通信的Unix域套接字接口
这个crate封装了AF_UNIX
平台上的socketpair
系统调用,并在Windows上使用CreateNamedPipe
来模拟这个接口。
它提供了两种接口模式:
- "stream"模式:对应
SOCK_STREAM
和PIPE_TYPE_BYTE
,提供面向流的可靠双向通信 - "seqpacket"模式:对应
SOCK_SEQPACKET
和PIPE_TYPE_MESSAGE
,保留消息边界的数据传输
基础用法示例
// 创建一对已连接的socket
let (mut a, mut b) = socketpair_stream()?;
// 向一端写入数据
writeln!(a, "hello world")?;
// 从另一端读取数据
let mut buf = [0_u8; 4096];
let n = b.read(&mut buf)?;
assert_eq!(str::from_utf8(&buf[..n]).unwrap(), "hello world\n");
完整双向通信示例
use std::io::{Read, Write};
use socketpair::socketpair_stream;
fn main() -> std::io::Result<()> {
// 创建一对已连接的Unix域套接字
let (mut socket_a, mut socket_b) = socketpair_stream()?;
// 从socket_a发送数据
socket_a.write_all(b"Hello from socket_a")?;
// socket_b接收数据
let mut buf = [0; 1024];
let n = socket_b.read(&mut buf)?;
println!("socket_b received: {}",
std::str::from_utf8(&buf[..n]).unwrap());
// 反向发送数据
socket_b.write_all(b"Hello back from socket_b")?;
// socket_a接收回复
let n = socket_a.read(&mut buf)?;
println!("socket_a received: {}",
std::str::from_utf8(&buf[..n]).unwrap());
Ok(())
}
父子进程通信示例
use std::io::{Read, Write};
use std::os::unix::io::AsRawFd;
use socketpair::socketpair_stream;
use std::process::{Command, Stdio};
fn main() -> std::io::Result<()> {
// 创建套接字对
let (mut parent_socket, mut child_socket) = socketpair_stream()?;
// 创建子进程
let mut child = Command::new("/bin/bash")
.arg("-c")
.arg("read -r msg <&3; echo \"Child received: $msg\"; echo \"Hello parent\" >&3")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.fd(3, child_socket.as_raw_fd()) // 将socket传递给子进程作为文件描述符3
.spawn()?;
// 父进程发送消息
parent_socket.write_all(b"Hello child")?;
// 父进程接收子进程回复
let mut buf = [0; 1024];
let n = parent_socket.read(&mut buf)?;
println!("Parent received: {}",
std::str::from_utf8(&buf[..n]).unwrap());
// 等待子进程结束
child.wait()?;
Ok(())
}
注意事项
- 目前对async-std和tokio的支持暂时禁用,直到这些crate包含所需的I/O安全特性实现
- Windows平台使用命名管道模拟Unix域套接字行为
- 在多进程场景中,需要注意文件描述符的传递和继承
这个crate为Rust提供了跨平台的高效进程间通信解决方案,特别适合需要低延迟、高吞吐量的本地进程间通信场景。
1 回复
Rust网络编程库socketpair的使用:实现进程间高效双向通信的Unix域套接字接口
介绍
socketpair
是Unix系统提供的一个系统调用,用于创建一对已连接的匿名套接字,主要用于同一主机上的进程间通信(IPC)。在Rust中,我们可以通过标准库或第三方库来使用这个功能。
Unix域套接字相比网络套接字有更高的效率,因为数据不需要经过网络协议栈处理,而是直接在操作系统内核中传输。socketpair
创建的双向通道特别适合父子进程或线程间的通信。
使用方法
1. 使用标准库
Rust标准库提供了Unix域套接字的支持:
use std::os::unix::net::{UnixStream, UnixListener, UnixDatagram};
use std::io::{Read, Write};
fn main() -> std::io::Result<()> {
// 创建一对已连接的Unix流套接字
let (mut socket1, mut socket2) = UnixStream::pair()?;
// 在socket1写入数据
socket1.write_all(b"Hello from socket1")?;
// 从socket2读取数据
let mut buf = [0; 1024];
let n = socket2.read(&mut buf)?;
println!("Received from socket1: {}", String::from_utf8_lossy(&buf[..n]));
// 反向通信
socket2.write_all(b"Hello back from socket2")?;
let n = socket1.read(&mut buf)?;
println!("Received from socket2: {}", String::from_utf8_lossy(&buf[..n]));
Ok(())
}
2. 使用nix库
对于更底层的控制,可以使用nix
库:
use nix::sys::socket::{socketpair, AddressFamily, SockType, SockFlag};
use nix::unistd::{read, write};
use std::os::unix::io::RawFd;
fn main() {
// 创建socket pair
let (fd1, fd2): (RawFd, RawFd) = socketpair(
AddressFamily::Unix,
SockType::Stream,
None,
SockFlag::empty(),
).expect("socketpair failed");
// 写入数据到fd1
let message = b"Hello from fd1";
write(fd1, message).expect("write failed");
// 从fd2读取数据
let mut buf = [0u8; 1024];
let n = read(fd2, &mut buf).expect("read failed");
println!("Received: {}", String::from_utf8_lossy(&buf[..n]));
// 关闭文件描述符
nix::unistd::close(fd1).expect("close failed");
nix::unistd::close(fd2).expect("close failed");
}
3. 父子进程通信示例
socketpair
常用于父子进程间通信:
use std::os::unix::net::UnixStream;
use std::process;
use std::io::{Read, Write};
fn main() -> std::io::Result<()> {
// 创建socket pair
let (parent_sock, child_sock) = UnixStream::pair()?;
// 创建子进程
let child = process::Command::new("/bin/sh")
.arg("-c")
.arg("echo 'Hello from child' >&3; cat <&3")
.stdin(process::Stdio::inherit())
.stdout(process::Stdio::inherit())
.stderr(process::Stdio::inherit())
.fd(3, child_sock)
.spawn()?;
// 父进程写入数据
parent_sock.write_all(b"Hello from parent\n")?;
// 读取子进程响应
let mut buf = [0; 1024];
let n = parent_sock.read(&mut buf)?;
println!("Parent received: {}", String::from_utf8_lossy(&buf[..n]));
child.wait()?;
Ok(())
}
完整示例:使用tokio实现异步socketpair通信
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
#[tokio::main]
async fn main() -> std::io::Result<()> {
// 创建一对已连接的Unix流套接字
let (mut socket1, mut socket2) = UnixStream::pair()?;
// 异步写入数据到socket1
tokio::spawn(async move {
socket1.write_all(b"Hello from socket1").await?;
Ok::<(), std::io::Error>(())
});
// 异步从socket2读取数据
let mut buf = [0; 1024];
let n = socket2.read(&mut buf).await?;
println!("Received from socket1: {}", String::from_utf8_lossy(&buf[..n]));
// 反向通信
tokio::spawn(async move {
socket2.write_all(b"Hello back from socket2").await?;
Ok::<(), std::io::Error>(())
});
let n = socket1.read(&mut buf).await?;
println!("Received from socket2: {}", String::from_utf8_lossy(&buf[..n]));
Ok(())
}
注意事项
socketpair
创建的套接字默认是全双工的,可以同时读写- Unix域套接字比TCP/IP套接字更高效,适合高性能IPC场景
- 通信的进程需要有相同的用户权限(除非使用抽象命名空间)
- 对于需要持久化的通信,可以考虑使用命名Unix域套接字
- 在多线程环境中使用时需要注意同步问题
性能优化技巧
- 对于大量小消息,考虑使用
sendmsg
/recvmsg
系统调用批量传输 - 可以使用
SO_SNDBUF
和SO_RCVBUF
调整套接字缓冲区大小 - 考虑使用非阻塞I/O或异步I/O(如tokio)提高并发性能
- 对于高吞吐场景,可以使用
SCM_RIGHTS
传递文件描述符而不是数据
socketpair
是Unix系统下高效进程间通信的重要工具,Rust提供了多种使用方式,可以根据具体需求选择合适的方法。