Rust in Everything
基于 embassy-net 0.9.1

adding a socket to a full SocketSet

主线位置:网络栈启动时,Runner 开始驱动 smoltcp 之前。StackResources 的三块内存和 embassy_net::new 做的事见 Stack 与 Runner,套接字怎么占槽见 TCP 与 UDP 套接字。

flowchart TB
  res["StackResources N 个槽"] --> new["embassy_net::new"]
  new --> dns["dns:占 1 个"]
  new --> dhcp["dhcpv4:占 1 个"]
  dns --> user["用户套接字各占 1 个"]
  dhcp --> user
  user --> q{"还有空槽"}
  q -->|有| ok["SocketHandle"]
  q -->|没有| boom["panic"]

实验 crate exp-embassy/pitb-net(复制自 net-std,进程内两根管子接起来的假驱动 PipeDriver),bin p24_full(platform-std,release;embassy-net 开着 dns、dhcpv4、tcp、udp 等 feature);M2 Max、macOS 27、rustc 1.99.0。

现象

一个小 HTTP 服务:四个监听任务,每个建一个 TcpSocket 去 accept(80),好让四个连接能同时进来。用 DHCP 拿地址,StackResources<4> 想着「四个连接四个槽」:

#[embassy_executor::task(pool_size = 4)]
async fn listener(stack: Stack<'static>, id: usize) {
  let mut s = TcpSocket::new(stack, leak([0u8; 256]), leak([0u8; 256]));
  println!("listener {id}: socket created");
  let _ = s.accept(80).await;
}

let config = Config::dhcpv4(Default::default());
let (stack, runner) = embassy_net::new(da, config, leak(StackResources::<4>::new()), 1);
// spawn net_task(runner),再 spawn listener(stack, 0..4)
StackResources<4>, dhcpv4 + dns features
listener 3: socket created
listener 2: socket created

thread 'main' (5047255) panicked at ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/smoltcp-0.13.1/src/iface/socket_set.rs:83:42:
adding a socket to a full SocketSet
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

只建了两个就 panic。任务按后进先出的顺序先跑(3、2,见状态与运行队列),所以打印的编号是倒着的。std 上 panic 打断整个进程;板子上进 panic 处理函数,通常就是复位或死循环。

出处

embassy_net::new 把 StackResources<SOCK> 里的 SOCK 个槽交给 smoltcp 的 SocketSet,然后:

  • 开了 dns feature,先加一个 DNS 套接字(第 350 行);
  • 配置是 Config::dhcpv4,再加一个 DHCPv4 套接字(第 708 行 起)。

剩下两个槽给用户。TcpSocket::new 调 i.sockets.add(..),smoltcp 的 SocketSet::add 找不到空槽,而存储是借来的定长切片(ManagedSlice::Borrowed,不能 push):

        match &mut self.sockets {
            ManagedSlice::Borrowed(_) => panic!("adding a socket to a full SocketSet"),

它的文档写着「This function panics if the storage is fixed-size (not a Vec) and is full」。embassy-net 的 TcpSocket::new / UdpSocket::new 都没有返回 Result 的版本,所以这里只能 panic。

修法

槽数 = DHCP(用 Config::dhcpv4 时 1)+ DNS(开了 dns feature 时 1)+ 同时存在的用户套接字数。四个监听任务要 6:

let (stack, runner) = embassy_net::new(da, config, leak(StackResources::<6>::new()), 1);
StackResources<6>, dhcpv4 + dns features
listener 3: socket created
listener 2: socket created
listener 1: socket created
listener 0: socket created
all four listeners up
  • 套接字 drop 时槽会还回去(TCP 与 UDP 套接字),所以算的是「同一时刻最多有几个」,不是程序里一共建过几个。
  • 不用 DNS 就关掉 dns feature,省一个槽;静态地址不占 DHCP 的槽。
  • embassy-net 的 wait_config_up 文档示例里写的就是「provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP)」。

怎么确认没中招

  • panic 信息是 adding a socket to a full SocketSet,位置在 smoltcp 的 socket_set.rs:数一数 StackResources::<N> 的 N,减去 DHCP 和 DNS。
  • cargo tree -e features -i embassy-net --offline 看 dns、dhcpv4 是否被别的依赖打开了。
  • 每个会建套接字的任务的 pool_size 加起来,就是用户套接字的上限。

小结

  • StackResources<N> 的 N 是所有套接字共用的槽数,DNS、DHCP 各占一个。
  • 槽满时 smoltcp SocketSet::add panic「adding a socket to a full SocketSet」,embassy-net 没有返回错误的构造函数。
  • 按「DHCP + DNS + 同时存在的套接字数」定 N;套接字 drop 会归还槽。

💬 关联讨论

发起讨论
加载中...