【WSL的端口开放给局域网内多个openclaw网关使用】一款实现WSL中运行的vLLM服务器端口开放软件(简单实现)

痛点:

因为用windows 11 系统中运行了WSL来跑vLLM大语言模型的服务器,用了WSL config文件配置之后,windows 11 可以通过Localhost来访问WSL中的vLLM的服务端口(我用了7988端口)。但是又想让局域网中的多台设备(Mac)的openclaw网关来访问vLLM服务器(服务器配置好了Qwen3-Coder-Next)。但是惊奇发现,WSL的服务器只能在windows 11中访问,于是:

需要win11将WSL中的vLLM服务端口暴露给局域网中的其它设备。

首先,我们可以配置好windows 11是可以直接访问到vLLM服务器的端口的,例如,当你运行http://localhost:7988/v1/models 这个地址是会返回{Qwen3-Coder-Next} 的模型数据给你的,又或者你win11 可以用base URL来访问WSL中的服务端口:可以用 curl http://localhost:7988/v1/api 等地址来确认访问。如果localhost不能访问到WSL,你需要设置一下(详情问AI即可)

一旦win11 可以通过localhost来访问WSL中的服务端口,那么就进行以下步骤:

思路:通过node或者python来写一个简单的端口报文转发服务来实现,让局域网中的其它设备访问win11 服务器的 8988端口,然后通过自己编写的node程序来将8988端口请求转发到7988端口,然后将7988端口的报文转发到8988端口,实现相互通讯。(端口号可以自定义)

详细操作步骤:

1:在win 11 中建立一个新文件夹,例如:D:\repo\wsl-port-forward这个路径下,用powershell打开,用node新建一个运行环境(也可以用python),不过node的环境创建对应的是 npm init -y,python对应python的虚拟程序创建是:python -m venv .venv

2:安装依赖(node已经初始化完成):
npm install express http-proxy-middleware axios

3:创建一个proxy.js文件,复制以下代码:

// proxy.js (修复版)
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const axios = require('axios');
const { execSync, spawnSync } = require('child_process');
const os = require('os');

const LOCAL_PORT = 7988;
const WSL_PORT = 7988;
let wslIp = null; // 缓存 WSL IP,避免频繁调用

// 【关键修复】增强 WSL IP 获取逻辑(返回 Promise)
function getWslIp() {
  return new Promise((resolve, reject) => {
    console.log('🔍 正在获取 WSL2 IP...');

    try {
      // 方案1: 尝试标准命令 (Windows 10/11)
      let output = '';
      if (os.platform() === 'win32') {
        // 使用 PowerShell 更可靠
        const psResult = spawnSync('powershell.exe', [
          '-Command', 
          '(wsl hostname -I).Trim().Split(" ")[0]'
        ], { encoding: 'utf-8', timeout: 5000 });
        
        if (psResult.status === 0 && psResult.stdout.trim()) {
          output = psResult.stdout.trim();
          console.log(`💡 通过 PowerShell 获取到 IP: ${output}`);
        } else {
          console.warn('⚠️ PowerShell 方法失败,尝试备用方案');
          // 备用方案:直接调用 wsl
          const cmdResult = execSync('wsl hostname -I', { 
            encoding: 'utf-8', 
            timeout: 3000 
          });
          output = cmdResult.trim().split(' ')[0];
        }
      } else {
        // 非 Windows 系统 (如 macOS/Linux)
        output = execSync('hostname -I', { 
          encoding: 'utf-8', 
          timeout: 3000 
        }).trim().split(' ')[0];
      }

      if (!output || !output.match(/\d+\.\d+\.\d+\.\d+/)) {
        throw new Error(`无效的 IP 格式: "${output}"`);
      }

      wslIp = output;
      console.log(`✅ 成功获取 WSL2 IP: ${wslIp}`);
      resolve(wslIp);
    } catch (err) {
      console.error('🔥 获取 WSL IP 时发生严重错误:');
      console.error('  命令输出:', err.stdout?.trim() || '无');
      console.error('  错误详情:', err.message);
      console.error('  完整错误对象:', JSON.stringify(err, null, 2));
      
      // 【关键】不立即退出,而是每 5 秒重试
      console.log('⏳ 5 秒后重试获取 WSL IP...');
      setTimeout(() => {
        getWslIp().then(resolve).catch(reject);
      }, 5000);
    }
  });
}

// 【关键修复】异步初始化
async function initProxy() {
  // 1. 先获取 WSL IP
  wslIp = await getWslIp();
  
  // 2. 检查端口是否可用
  try {
    await new Promise((resolve, reject) => {
      const testServer = express().listen(LOCAL_PORT, '0.0.0.0', () => {
        testServer.close(); // 立即关闭测试服务器
        resolve();
      });
      testServer.on('error', (err) => {
        reject(new Error(`端口 ${LOCAL_PORT} 被占用或无权限: ${err.message}`));
      });
    });
    console.log(`✅ 端口 ${LOCAL_PORT} 可用`);
  } catch (err) {
    console.error('🔥 端口检查失败:', err.message);
    console.error('💡 解决方案:');
    console.error('   1. 关闭已占用 7988 端口的程序 (如 netsh portproxy / 其他服务)');
    console.error('   2. 以管理员身份运行 PowerShell: netsh interface portproxy delete v4tov4 listenport=7988');
    console.error('   3. 或修改 LOCAL_PORT 为其他值 (如 8080)');
    process.exit(1);
  }

  // 4. 创建代理服务器(此时 WSL IP 已就绪)
  const app = express();
  
  // 【增强】详细的请求日志
  app.use((req, res, next) => {
    const start = Date.now();
    console.log(`📥 [${new Date().toISOString()}] ${req.method} ${req.url}`);
    res.on('finish', () => {
      console.log(`📤 [${new Date().toISOString()}] ${req.method} ${req.url} - ${res.statusCode} (${Date.now() - start}ms)`);
    });
    next();
  });

  // 健康检查 (带重试)
  app.get('/health', async (req, res) => {
    if (!wslIp) {
      return res.status(503).json({ error: 'WSL IP 未初始化' });
    }

    try {
      const targetUrl = `http://${wslIp}:${WSL_PORT}/health`;
      console.log(`🩺 健康检查: GET ${targetUrl}`);
      
      const response = await axios.get(targetUrl, { timeout: 3000 });
      res.json({ 
        status: 'ok', 
        target: targetUrl,
        upstream: response.data 
      });
    } catch (err) {
      console.error('🩺 健康检查失败:', err.message);
      res.status(503).json({ 
        error: 'Upstream unhealthy',
        details: err.message,
        lastKnownIp: wslIp
      });
    }
  });

   // 【关键修复】代理中间件
   app.use(
     '/',
     createProxyMiddleware({
       target: `http://${wslIp}:${WSL_PORT}`,
       changeOrigin: true,
       logLevel: 'debug', // 启用详细日志
       onError: (err, req, res) => {
         console.error(`🚨 代理错误 [${req.method} ${req.url}]:`, err.message);
         res.status(502).json({ 
           error: 'Bad Gateway',
           details: err.message,
           timestamp: new Date().toISOString()
         });
       },
       onProxyReq: (proxyReq, req) => {
         console.log(`📡 转发请求到: ${proxyReq.path}`);
       }
     })
   );

   // 【关键】处理服务器错误
   const server = app.listen(LOCAL_PORT, '0.0.0.0', () => {
     console.log(`🚀 HTTP 代理服务器已启动!`);
     console.log(`   本地访问: http://localhost:${LOCAL_PORT}/docs`);
     console.log(`   局域网访问: http://<你的局域网IP>:${LOCAL_PORT}/docs`);
     console.log(`   代理目标: http://${wslIp}:${WSL_PORT} (初始 IP,动态更新)`);
     console.log('✅ 服务器将持续运行,按 Ctrl+C 停止');
   });

  // 【关键】防止进程意外退出
  server.on('error', (err) => {
    console.error('🔥 服务器致命错误:', err.message);
    if (err.code === 'EADDRINUSE') {
      console.error(`💡 端口 ${LOCAL_PORT} 已被占用! 请先执行:`);
      console.error('   netsh interface portproxy delete v4tov4 listenport=7988 listenaddress=0.0.0.0');
    }
    process.exit(1);
  });

  // 保持进程活跃
  setInterval(() => {
    if (!wslIp) {
      console.log('🔄 后台重试获取 WSL IP...');
      getWslIp();
    }
  }, 10000);
}

// 【关键】全局错误捕获
process.on('uncaughtException', (err) => {
  console.error('💥 未捕获的异常:', err.message);
  console.error(err.stack);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('💥 未处理的 Promise rejection:', reason);
});

// 【关键】优雅关闭
process.on('SIGINT', () => {
  console.log('\n👋 正在关闭代理服务器...');
  process.exit(0);
});

// 启动!
console.log('🔧 初始化代理服务...');
initProxy().catch(err => {
  console.error('🔥 初始化失败:', err);
  process.exit(1);
});

4: 保存文件后,新建一个ReadMe.md文件:

# WSL 端口转发代理服务

将请求从 Windows 端口转发到 WSL2。

## 启动方式

```bash
node proxy.js
```

## 说明

- 本地访问: http://localhost:7988
- 局域网访问: http://<你的局域网IP>:7988
- 代理目标: WSL2 的 `http://<wsl-ip>:7988`

5:现在你可以运行程序了:node proxy.js
(确保你已经安装了node, 一般openclaw的环境要求node>22)
在这里插入图片描述

搞定,现在你的局域网中的其它设备都可以访问到WSL中的vLLM服务器了。(请自行解决防火墙问题,你都看到这里了,小问题就能自己解决了吧)

不足和改进:

1:端口转发没有设置鉴权,请勿用于公网访问。
2:端口接收的数据是明文直接转发,为了安全,你可以增加鉴权层,增加加密层,增加有害信息过滤层,关键词过滤层以及其它需要符合法律法规和安全规范的行为能力,让软件世界更加安全。
3:祝大家新年快乐!

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐