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,然后:
剩下两个槽给用户。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 就关掉
dnsfeature,省一个槽;静态地址不占 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::addpanic「adding a socket to a full SocketSet」,embassy-net 没有返回错误的构造函数。 - 按「DHCP + DNS + 同时存在的套接字数」定
N;套接字 drop 会归还槽。