From 6fb57e51522e6fb0e4d410345f7abba906bc9fec Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 11 Oct 2026 12:30:02 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=BB=BA=20oxide=5Fnet=20crat?= =?UTF-8?q?e=20=E4=BC=A0=E8=BE=93=E6=9E=9A=E4=B8=BE=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +++---- Cargo.toml | 3 +- crates/oxide_net/Cargo.toml | 12 ++ crates/oxide_net/src/lib.rs | 14 +++ crates/oxide_net/src/transport.rs | 180 ++++++++++++++++++++++++++++++ 5 files changed, 231 insertions(+), 20 deletions(-) create mode 100644 crates/oxide_net/Cargo.toml create mode 100644 crates/oxide_net/src/lib.rs create mode 100644 crates/oxide_net/src/transport.rs diff --git a/Cargo.lock b/Cargo.lock index 623f36fe..e48f5392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.15.3" +version = "0.15.4" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.15.3" +version = "0.15.4" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.15.3" +version = "0.15.4" dependencies = [ "ansi_term", "bumpalo", @@ -1227,7 +1227,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.15.3" +version = "0.15.4" dependencies = [ "lru", "oxide_bytecode", @@ -1236,7 +1236,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1256,7 +1256,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1270,7 +1270,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.15.3" +version = "0.15.4" dependencies = [ "num-bigint", "oxc_allocator", @@ -1285,7 +1285,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1297,7 +1297,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1306,7 +1306,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.15.3" +version = "0.15.4" dependencies = [ "bumpalo", "dashmap", @@ -1320,7 +1320,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1332,16 +1332,20 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.15.3" +version = "0.15.4" dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", ] +[[package]] +name = "oxide_net" +version = "0.15.4" + [[package]] name = "oxide_parser" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1356,7 +1360,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1372,7 +1376,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.15.3" +version = "0.15.4" dependencies = [ "num-bigint", "num-traits", @@ -1385,7 +1389,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.15.3" +version = "0.15.4" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1403,7 +1407,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.15.3" +version = "0.15.4" dependencies = [ "bumpalo", "dashmap", @@ -1416,7 +1420,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.15.3" +version = "0.15.4" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index ae688672..92f7d4da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/oxide_bytecode", "crates/oxide_code_cache", "crates/oxide_log", + "crates/oxide_net", "crates/oxide_compiler", "crates/oxide_emit", "crates/oxide_ir", @@ -23,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.15.3" +version = "0.15.4" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_net/Cargo.toml b/crates/oxide_net/Cargo.toml new file mode 100644 index 00000000..a6a8285c --- /dev/null +++ b/crates/oxide_net/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "oxide_net" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - Transport enum layer (Unix and TCP)" + +[dependencies] + +[lints] +workspace = true diff --git a/crates/oxide_net/src/lib.rs b/crates/oxide_net/src/lib.rs new file mode 100644 index 00000000..23a7a0ec --- /dev/null +++ b/crates/oxide_net/src/lib.rs @@ -0,0 +1,14 @@ +//! OxideJS 引擎无关传输层。 +//! +//! `transport` 提供 `Conn` 与 `Listener` 两枚枚举,各包装 Unix 域套接字与 TCP 连接两臂, +//! 统一六个常用操作:非阻塞、accept、克隆、读超时、写超时、write_all。协议层完全 +//! 传输无关,本 crate 只提供传输面。 +//! +//! 构造面(bind / connect)不含在本 crate:调用方先用 std 原语构造句柄,再包进枚举。 +//! 零外部依赖,纯 `std::net` 与 `std::os::unix::net`。 + +/// 传输枚举层:`Conn` 与 `Listener` 及统一操作。 +pub mod transport; + +/// 重导出 `transport` 模块的 `Conn` 与 `Listener` 枚举。 +pub use transport::{Conn, Listener}; diff --git a/crates/oxide_net/src/transport.rs b/crates/oxide_net/src/transport.rs new file mode 100644 index 00000000..b6642df8 --- /dev/null +++ b/crates/oxide_net/src/transport.rs @@ -0,0 +1,180 @@ +//! 传输枚举层:`Conn` 与 `Listener` 各包装 Unix 与 TCP 两臂,统一六个常用操作。 +//! +//! 关键约定: +//! - 两臂映射到 std 同名同语义方法,无行为分叉。 +//! - `Listener::accept` 返回 `Conn` 并丢弃对端地址,现有 accept 循环不消费地址。 +//! - 构造面(bind / connect)不含在本模块,调用方先用 std 原语构造句柄再包进枚举。 + +use std::io; +use std::io::Write; +use std::net::{TcpListener, TcpStream}; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::time::Duration; + +/// 双向连接:Unix 域套接字或 TCP 连接。 +/// +/// 两臂共享同一操作面,调用方无需感知具体传输。 +#[derive(Debug)] +pub enum Conn { + /// Unix 域套接字臂。 + Unix(UnixStream), + /// TCP 连接臂。 + Tcp(TcpStream), +} + +/// 监听套接字:Unix 域套接字监听者或 TCP 监听者。 +#[derive(Debug)] +pub enum Listener { + /// Unix 域套接字监听者臂。 + Unix(UnixListener), + /// TCP 监听者臂。 + Tcp(TcpListener), +} + +impl Listener { + /// 设置监听者非阻塞模式。 + /// + /// # 注意事项 + /// - 置非阻塞后,无待接受连接时 `accept` 返回 `io::ErrorKind::WouldBlock`。 + pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> { + match self { + Listener::Unix(l) => l.set_nonblocking(nonblocking), + Listener::Tcp(l) => l.set_nonblocking(nonblocking), + } + } + + /// 接受一个连接并包装为 `Conn`,丢弃对端地址。 + /// + /// # 边界与前提 + /// - Unix 与 TCP 臂的对端地址类型不同,本层不暴露;需要来源地址的调用方须扩展签名。 + /// - 非阻塞模式下无待接受连接时返回 `WouldBlock`。 + pub fn accept(&self) -> io::Result { + match self { + // 对端地址按约定丢弃,只取连接句柄。 + Listener::Unix(l) => Ok(Conn::Unix(l.accept()?.0)), + Listener::Tcp(l) => Ok(Conn::Tcp(l.accept()?.0)), + } + } +} + +impl Conn { + /// 克隆连接,返回共享同一底层套接字的新 `Conn`。 + /// + /// # 副作用 + /// - 新句柄与原句柄共享同一套接字:写侧共享,读侧相互独立(std `try_clone` 语义)。 + pub fn try_clone(&self) -> io::Result { + match self { + Conn::Unix(s) => Ok(Conn::Unix(s.try_clone()?)), + Conn::Tcp(s) => Ok(Conn::Tcp(s.try_clone()?)), + } + } + + /// 设置读超时。 + /// + /// # 边界与前提 + /// - `None` 表示不限时(可无限阻塞)。 + pub fn set_read_timeout(&self, timeout: Option) -> io::Result<()> { + match self { + Conn::Unix(s) => s.set_read_timeout(timeout), + Conn::Tcp(s) => s.set_read_timeout(timeout), + } + } + + /// 设置写超时。 + /// + /// # 边界与前提 + /// - `None` 表示不限时(可无限阻塞)。 + pub fn set_write_timeout(&self, timeout: Option) -> io::Result<()> { + match self { + Conn::Unix(s) => s.set_write_timeout(timeout), + Conn::Tcp(s) => s.set_write_timeout(timeout), + } + } + + /// 顺序写入全部字节。 + /// + /// # 边界与前提 + /// - 空切片为无操作,恒成功。 + /// - 已设超时且单次写入超时时返回 `Elapsed`(std 语义)。 + pub fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + match self { + Conn::Unix(s) => s.write_all(buf), + Conn::Tcp(s) => s.write_all(buf), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Read; + + /// Unix 回环:临时目录 bind Unix 套接字,accept 后写读往返,六方法逐一可用。 + #[test] + fn unix_loopback_roundtrip() { + let dir = std::env::temp_dir().join(format!("oxide_net_{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("unix.sock"); + let _ = std::fs::remove_file(&path); + + let listener = UnixListener::bind(&path).unwrap(); + let mut client = UnixStream::connect(&path).unwrap(); + + // 六方法逐一过一遍:非阻塞、accept、克隆、读写超时、write_all。 + let listener = Listener::Unix(listener); + listener.set_nonblocking(false).unwrap(); + let mut conn = listener.accept().unwrap(); + let _peer = conn.try_clone().unwrap(); + conn.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + conn.set_write_timeout(Some(Duration::from_secs(5))).unwrap(); + + conn.write_all(b"hello-oxide").unwrap(); + let mut buf = [0u8; 11]; + client.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"hello-oxide"); + + let _ = std::fs::remove_file(&path); + } + + /// TCP 回环:bind `127.0.0.1:0`(端口 0 由操作系统分配空闲端口), + /// 经 `local_addr` 取端口,connect 后写读往返。 + #[test] + fn tcp_loopback_roundtrip() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let mut client = TcpStream::connect(addr).unwrap(); + + let listener = Listener::Tcp(listener); + listener.set_nonblocking(false).unwrap(); + let mut conn = listener.accept().unwrap(); + let _peer = conn.try_clone().unwrap(); + conn.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + conn.set_write_timeout(Some(Duration::from_secs(5))).unwrap(); + + conn.write_all(b"hello-oxide").unwrap(); + let mut buf = [0u8; 11]; + client.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"hello-oxide"); + } + + /// 非阻塞 accept 空轮:置非阻塞后无连接时 accept 应返回 `WouldBlock`(两臂各验一次)。 + #[test] + fn nonblocking_accept_would_block() { + let dir = std::env::temp_dir().join(format!("oxide_net_{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("unix_nb.sock"); + let _ = std::fs::remove_file(&path); + + let unix = Listener::Unix(UnixListener::bind(&path).unwrap()); + unix.set_nonblocking(true).unwrap(); + let err = unix.accept().unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::WouldBlock); + + let tcp = Listener::Tcp(TcpListener::bind("127.0.0.1:0").unwrap()); + tcp.set_nonblocking(true).unwrap(); + let err = tcp.accept().unwrap_err(); + assert_eq!(err.kind(), io::ErrorKind::WouldBlock); + + let _ = std::fs::remove_file(&path); + } +}