WSL WslcSDK 端口映射 API 详解:WslcSetContainerSettingsPortMappings 使用指南
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
WSL(Windows Subsystem for Linux)的 WslcSDK 提供了一套用于管理 WSL 容器的 C API,其中WslcSetContainerSettingsPortMappings负责在容器创建前设置宿主机与容器之间的端口映射规则,是搭建"Windows 宿主机 ⇄ WSL 容器"网络服务暴露链路的关键接口。本文将基于官方 API 参考文档与仓库源码、测试用例,完整讲解该函数的签名、参数语义、底层校验逻辑、完整调用流程与当前实现的限制,帮助你正确地将容器内服务映射到 Windows 宿主机的指定端口与地址。
1. API 概览
WslcSetContainerSettingsPortMappings是 WslcSDK 中一组 "OPTIONAL CONTAINER SETTINGS"(可选容器设置)API 之一,用于为尚未创建的容器批量写入端口映射配置。完整声明定义在头文件 src/windows/WslcSDK/wslcsdk.h#L257-L260 中,同时作为导出符号列于 src/windows/WslcSDK/wslcsdk.def:
STDAPI WslcSetContainerSettingsPortMappings( _In_ WslcContainerSettings* containerSettings, _In_reads_opt_(portMappingCount) const WslcContainerPortMapping* portMappings, _In_ uint32_t portMappingCount);参数说明
| 参数 | 类型 | 方向 | 含义 |
|---|---|---|---|
containerSettings | WslcContainerSettings* | in | 由 WslcInitContainerSettings 初始化得到的容器设置句柄(不透明结构) |
portMappings | const WslcContainerPortMapping* | in, optional | 指向端口映射数组的指针,可为NULL(用于清空映射) |
portMappingCount | uint32_t | in | 端口映射条目的数量 |
返回值类型为HRESULT。S_OK表示设置成功;参数非法时返回E_INVALIDARG;暂不支持的协议返回E_NOTIMPL(详见下文"源码级实现剖析")。
该 API 只是把映射"写入"容器设置对象,真正的端口监听与转发发生在 WslcCreateContainer / WslcStartContainer 之后——即端口映射属于"创建期配置",必须在创建容器之前完成设置。
2. 前置知识:映射结构与协议枚举
WslcSetContainerSettingsPortMappings的输入数组元素类型为WslcContainerPortMapping,完整定义见 doc/docs/api-reference/c/structures/wslccontainerportmapping.md:
typedef struct WslcContainerPortMapping { _In_ uint16_t windowsPort; // Port on Windows host _In_ uint16_t containerPort; // Port inside container _In_ WslcPortProtocol protocol; // TCP or UDP // if you want to override the default binding address _In_opt_ struct sockaddr_storage* windowsAddress; // accepts ipv4/6 } WslcContainerPortMapping;| 字段 | 类型 | 说明 |
|---|---|---|
windowsPort | uint16_t | Windows 宿主机上对外监听的端口 |
containerPort | uint16_t | 容器内部服务的端口 |
protocol | WslcPortProtocol | 传输协议(TCP 或 UDP) |
windowsAddress | struct sockaddr_storage* | 可选,覆盖宿主机默认绑定地址,支持 IPv4/IPv6 |
其中protocol字段使用枚举 WslcPortProtocol:
typedef enum WslcPortProtocol { WSLC_PORT_PROTOCOL_TCP = 0, WSLC_PORT_PROTOCOL_UDP = 1 } WslcPortProtocol;| 枚举值 | 数值 | 说明 |
|---|---|---|
WSLC_PORT_PROTOCOL_TCP | 0 | TCP 协议(当前唯一受支持的值) |
WSLC_PORT_PROTOCOL_UDP | 1 | UDP 协议(当前仓库实现中返回E_NOTIMPL) |
containerSettings本身是不透明结构(见 doc/docs/api-reference/c/structures/wslccontainersettings.md),以对齐字节数组形式隐藏内部布局,调用方只能通过WslcInitContainerSettings初始化和一系列WslcSetContainerSettings*函数来填充。这正是 WslcSDK 的设计模式:先初始化、再逐项设置、最后创建容器。
3. 基础使用示例:将容器 80 端口映射到宿主机 8080
官方 API 参考文档(wslcsetcontainersettingsportmappings.md)给出了最核心的单条映射示例,完整继承如下:
WslcContainerPortMapping portMappings[1] = { 0 }; portMappings[0].windowsPort = (uint16_t)8080; portMappings[0].containerPort = (uint16_t)80; portMappings[0].protocol = WSLC_PORT_PROTOCOL_TCP; portMappings[0].windowsAddress = NULL; HRESULT hr = WslcSetContainerSettingsPortMappings( &containerSettings, portMappings, (uint32_t)_countof(portMappings));这段代码的语义是:将 Windows 宿主机的 8080 端口(默认绑定地址)转发到容器内的 80 端口,走 TCP 协议。windowsAddress = NULL表示使用默认绑定地址,不覆盖宿主机的监听地址。
将其放入完整生命周期中,一个最小可运行的调用序列为:
// 1. 初始化容器设置 WslcContainerSettings containerSettings; HRESULT hr = WslcInitContainerSettings("python:3.12-alpine", &containerSettings); if (FAILED(hr)) { /* 处理错误 */ } // 2. (可选)设置容器名称、网络模式等 hr = WslcSetContainerSettingsName(&containerSettings, "my-web-container"); hr = WslcSetContainerSettingsNetworkingMode(&containerSettings, WSLC_CONTAINER_NETWORKING_MODE_BRIDGED); // 3. 设置端口映射 WslcContainerPortMapping portMappings[1] = { 0 }; portMappings[0].windowsPort = (uint16_t)8080; portMappings[0].containerPort = (uint16_t)80; portMappings[0].protocol = WSLC_PORT_PROTOCOL_TCP; portMappings[0].windowsAddress = NULL; hr = WslcSetContainerSettingsPortMappings(&containerSettings, portMappings, (uint32_t)_countof(portMappings)); if (FAILED(hr)) { /* 处理错误 */ } // 4. 创建并启动容器(此时端口映射才会真正生效) // WslcContainer container = nullptr; // hr = WslcCreateContainer(session, &containerSettings, &container, nullptr); // hr = WslcStartContainer(container, WSLC_CONTAINER_START_FLAG_ATTACH, nullptr);注意:从源码测试可以看出,若网络模式为WSLC_CONTAINER_NETWORKING_MODE_NONE时仍设置端口映射,WslcCreateContainer会返回E_INVALIDARG(见下文"边界行为与错误处理"),因此配置端口映射时务必搭配支持端口转发的网络模式(如WSLC_CONTAINER_NETWORKING_MODE_BRIDGED)。
4. 绑定地址覆盖:IPv4 与 IPv6
当需要把宿主机监听限制到特定地址(而不是全部网卡)时,通过windowsAddress字段传入struct sockaddr_storage*。参考 test/windows/WslcSdkTests.cpp#L916-L996 中的两个功能测试,可以写出标准的 IPv4 绑定写法:
sockaddr_storage addr4{}; auto* sin4 = reinterpret_cast<sockaddr_in*>(&addr4); sin4->sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &sin4->sin_addr); WslcContainerPortMapping mapping{}; mapping.windowsPort = 12343; mapping.containerPort = 8000; mapping.protocol = WSLC_PORT_PROTOCOL_TCP; mapping.windowsAddress = &addr4; hr = WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1);IPv6 绑定(如仅允许回环地址::1访问)写法相同,只需把地址族切换为AF_INET6:
sockaddr_storage addr6{}; auto* sin6 = reinterpret_cast<sockaddr_in6*>(&addr6); sin6->sin6_family = AF_INET6; inet_pton(AF_INET6, "::1", &sin6->sin6_addr); WslcContainerPortMapping mapping{}; mapping.windowsPort = 12344; mapping.containerPort = 8000; mapping.protocol = WSLC_PORT_PROTOCOL_TCP; mapping.windowsAddress = &addr6; hr = WslcSetContainerSettingsPortMappings(&containerSettings, &mapping, 1);5. 源码级实现剖析:参数校验与内部存储
WslcSetContainerSettingsPortMappings的实现位于 src/windows/WslcSDK/wslcsdk.cpp#L1041-L1064,逻辑非常清晰,可以归纳为三步。
第一步:取值并做空指针/计数一致性校验。
auto internalType = CheckAndGetInternalType(containerSettings); RETURN_HR_IF(E_INVALIDARG, (portMappings == nullptr && portMappingCount != 0) || (portMappings != nullptr && portMappingCount == 0));即portMappings与portMappingCount必须"同生共死":指针为空且数量非零、或指针非空且数量为零,都会立即返回E_INVALIDARG。这与测试 test/windows/WslcSdkTests.cpp#L841-L854 中两个负面用例一一对应。
第二步:逐条校验每条映射的地址族与协议。
for (uint32_t i = 0; i < portMappingCount; ++i) { if (portMappings[i].windowsAddress != nullptr) { const auto family = portMappings[i].windowsAddress->ss_family; RETURN_HR_IF_MSG( E_INVALIDARG, family != AF_INET && family != AF_INET6, "Unsupported address family: %d at port mapping index %u", family, i); } RETURN_HR_IF_MSG( E_NOTIMPL, portMappings[i].protocol != 0, "Unsupported protocol: %d at port mapping index %u", portMappings[i].protocol, i); }- 地址族限制:
windowsAddress只接受AF_INET(IPv4)与AF_INET6(IPv6),其他地址族(如AF_UNIX)一律返回E_INVALIDARG,错误消息会带出具体的地址族编号与数组下标,便于定位出错条目。测试 test/windows/WslcSdkTests.cpp#L998-L1013 用AF_UNIX验证了这条路径。 - 协议限制:目前实现只接受
WSLC_PORT_PROTOCOL_TCP(枚举值为 0),传入 UDP(枚举值为 1)会返回E_NOTIMPL。这一限制也被记录在 doc/docs/api-reference/c/not-yet-implemented-apis.md 中,属于"已声明但尚未实现"的能力清单——即当前仓库中端口映射仅支持 TCP。
第三步:将映射数组及数量写入内部类型并返回S_OK。
internalType->ports = portMappings; internalType->portsCount = portMappingCount; return S_OK;注意ports保存的是调用方传入的指针,因此调用方必须在容器真正创建之前保持该数组内存有效,这与整个 WslcSDK "设置对象即描述符" 的设计一致。
6. 边界行为与错误处理
综合源码实现与测试用例(test/windows/WslcSdkTests.cpp#L839-L1014),该 API 的全部边界行为可归纳为下表:
| 场景 | 行为 | 依据 |
|---|---|---|
portMappings == NULL且portMappingCount != 0 | 返回E_INVALIDARG | wslcsdk.cpp#L1046;测试 #L845 |
portMappings != NULL且portMappingCount == 0 | 返回E_INVALIDARG | 同上;测试 #L853 |
portMappings == NULL且portMappingCount == 0 | 成功(S_OK),等价于清空端口映射 | 测试 #L860 |
windowsAddress->ss_family不是AF_INET/AF_INET6 | 返回E_INVALIDARG | wslcsdk.cpp#L1050-L1054;测试 #L1012 |
protocol不是WSLC_PORT_PROTOCOL_TCP(0) | 返回E_NOTIMPL | wslcsdk.cpp#L1056-L1057 |
| 网络模式为 NONE 却配置端口映射 | 设置 API 本身成功,但WslcCreateContainer返回E_INVALIDARG | 测试 #L863-L878 |
最后一条特别值得注意:"设置成功"不等于"创建成功"。网络模式与端口映射的语义冲突被推迟到容器创建阶段才暴露,因此最佳实践是在调用 WslcCreateContainer 后统一检查其返回值与errorMessage输出参数。
7. 在 WinRT 封装层中的调用方式
WslcSDK 除了 C 接口外,还在 src/windows/WslcSDK/winrt 下提供了 WinRT 投影层。其中 src/windows/WslcSDK/winrt/ContainerSettings.cpp#L269-L284 展示了该 API 在封装侧的典型用法——先将 WinRT 集合中的PortMapping投影对象批量转换为底层结构体数组,再一次性提交:
if (m_portMappings.Size() > 0) { m_portMappingsStructs.clear(); m_portMappingsStructs.reserve(m_portMappings.Size()); for (auto const& portMapping : m_portMappings) { if (!portMapping) { throw winrt::hresult_error(E_POINTER, L"Port mappings collection contains a null element"); } m_portMappingsStructs.push_back(GetStruct(portMapping)); } winrt::check_hresult(WslcSetContainerSettingsPortMappings( m_containerSettings.get(), m_portMappingsStructs.data(), static_cast<uint32_t>(m_portMappingsStructs.size()))); }可以看到,封装层在调用前做了两个额外的约定:映射集合为空(Size() == 0)时不会调用本 API(保持默认无映射);集合中出现空元素则直接抛出E_POINTER。同时通过winrt::check_hresult将任何失败HRESULT转换为 WinRT 异常,符合 WinRT 侧的惯例。
8. 与其他容器 API 的关系与调用顺序
WslcSetContainerSettingsPortMappings属于 Container APIs 中 "OPTIONAL CONTAINER SETTINGS" 一组。与同组的兄弟 API(WslcSetContainerSettingsName、WslcSetContainerSettingsInitProcess、WslcSetContainerSettingsNetworkingMode、WslcSetContainerSettingsHostName、WslcSetContainerSettingsDomainName、WslcSetContainerSettingsFlags、WslcSetContainerSettingsVolumes、WslcSetContainerSettingsNamedVolumes)一样,它们都遵循相同的生命周期约定:
WslcInitContainerSettings │ ▼ WslcSetContainerSettings* (按需多次调用,端口映射是其中之一) │ ▼ WslcCreateContainer ──► WslcStartContainer ──► WslcGetContainerInitProcess / WslcCreateContainerProcess │ ▼ WslcStopContainer ──► WslcDeleteContainer ──► WslcReleaseContainer端口映射的"写入期"严格位于WslcInitContainerSettings之后、WslcCreateContainer之前;一旦容器创建完成,修改容器设置对象不会再影响已创建的容器。从 Container APIs 索引 可以看到,端口映射只是众多可选设置之一,实际项目中通常与网络模式、初始化进程、卷挂载等组合使用。
9. 当前限制与注意事项
基于 not-yet-implemented-apis.md 与源码校验逻辑,使用本 API 时需注意:
- 仅支持 TCP:传入
WSLC_PORT_PROTOCOL_UDP会得到E_NOTIMPL。虽然 WslcPortProtocol 枚举同时定义了 TCP/UDP,但当前仓库实现只接受 TCP(数值 0)。 - 仅支持 IPv4/IPv6 地址族:
windowsAddress传入其他地址族(如AF_UNIX)会得到E_INVALIDARG,且错误信息包含具体的下标索引。 - 指针与计数必须配对:
NULL指针 + 非零计数、非空指针 + 零计数均属非法参数;NULL+ 零计数是合法的"清空映射"写法。 - 内存生命周期:
WslcSetContainerSettingsPortMappings内部保存的是调用方数组指针,容器创建前请勿释放或改写该数组。 - 与网络模式的配合:设置端口映射前请确认网络模式不是
WSLC_CONTAINER_NETWORKING_MODE_NONE,否则容器创建阶段会失败。 - 回调检查返回值:设置阶段成功不代表创建阶段成功,务必检查
WslcCreateContainer的返回值与errorMessage输出。
10. 小结
WslcSetContainerSettingsPortMappings是 WSL WslcSDK 中暴露容器网络服务到 Windows 宿主机的端口映射设置入口。通过本篇文章你可以掌握:函数签名与参数语义、WslcContainerPortMapping结构与WslcPortProtocol枚举的完整字段、官方示例及 IPv4/IPv6 绑定地址覆盖写法、源码层的三类参数校验规则(指针配对、地址族、协议),以及仓库测试用例所覆盖的全部边界行为。在实际开发中,建议将本 API 与 WslcInitContainerSettings、WslcSetContainerSettingsNetworkingMode 和 WslcCreateContainer 组合使用,并牢记当前实现仅支持 TCP、UDP 映射属于未实现能力。
【免费下载链接】WSLWindows Subsystem for Linux项目地址: https://gitcode.com/GitHub_Trending/ws/WSL
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考