NodeJS后端无法接收Flutter前端WebSocket数据问题求助
问题概述
开发Flutter前端+NodeJS(TypeScript)后端的移动应用,后端部署在OVHcloud的Ubuntu 20.04 VPS上,已关闭VPS防火墙。测试时:
- 前端终端显示
connected,能打印发送的消息,但后端仅显示监听端口,无法接收数据、无任何响应。 - VPS上执行
tsc --w编译时,express模块出现147个错误,但仍能正常编译并启动服务;本地运行相同代码无编译错误,但同样无法接收数据。
Flutter前端代码
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:nagahni/client.dart'; import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'MyProject', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'MyProject'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { String? _message; // This function will send the message to our backend. void sendMessage(msg) { WebSocketChannel? channel; try { channel = IOWebSocketChannel.connect('ws://I put my IP here:I put my port here'); print('connected'); } catch (e) { if (kDebugMode) { print( "Error: $e"); } } channel?.sink.add(msg); channel?.stream.listen((event) { if (event != null) { if (kDebugMode) { print(event); } } channel!.sink.close(); }); if (kDebugMode) { print(msg); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( children: [ Center( child: TextField( onChanged: (e) => _message = e, ), ), Center( child: TextButton( child: const Text("Send String"), onPressed: () { // Check if the message isn't empty. if (_message!.isNotEmpty) { sendMessage(_message); } }, ), ), const SizedBox(height: 12), TextButton( onPressed: () { sendObject(); }, child: const Text('Send Object'), ), ], ), ), ); } }
NodeJS(TypeScript)后端代码
import express from 'express'; import { Server } from 'ws'; const PORT = process.env.MAIN_PORT; // This is basically the SSH port + 1 ex. if 22, then this would be 23 require('dotenv').config() const server = express() .use((req, res) => res.send("Hi there")) .listen(PORT, () => console.log(`Listening on Port ${PORT}`)) const wss = new Server({ server }); wss.on('connection', function(ws, req) { ws.on('message', message => { var data = message.toString(); if(data === "Hello") { console.log(data); ws.send("Hi from TypeScript NodeJS"); } else { console.log(data); ws.send("Say hello to me!"); } }) })
编译错误信息(部分)
[5:51:56 PM] Starting compilation in watch mode... node_modules/@types/express-serve-static-core/index.d.ts:104:68 - error TS1110: Type expected. 104 type RemoveTail<S extends string, Tail extends string> = S extends `${infer P}${Tail}` ? P : S; ~~~ node_modules/@types/express-serve-static-core/index.d.ts:104:77 - error TS1005: '}' expected. 104 type RemoveTail<S extends string, Tail extends string> = S extends `${infer P}${Tail}` ? P : S; ~ ... wayy more errors but i cannot paste it all ... [5:53:55 PM] Found 147 errors. Watching for file changes.
后端部署步骤
- mkdir node_project
- cd node_project
- npm init -y
- npm i @types/express
- npm i express
- npm i @types/ws
- npm i ws
问题原因分析与解决办法
1. TypeScript版本不兼容(编译错误根源)
编译错误里的${infer P}${Tail}是**TypeScript 4.1+**才支持的模板字符串类型推断特性。VPS上全局或项目依赖的TypeScript版本过低,导致无法解析express类型定义里的语法。
解决:
升级项目的TypeScript版本:
npm install typescript@latest --save-dev
同时检查tsconfig.json的target字段,建议设置为ES2020或更高,确保兼容新语法。
2. 环境变量加载顺序错误(后端收不到连接的核心原因)
后端代码中先赋值PORT = process.env.MAIN_PORT,再调用require('dotenv').config(),此时process.env.MAIN_PORT还未被加载,导致PORT为undefined。express监听undefined时会默认用端口3000,但你前端连接的是自定义端口,所以实际后端监听的端口和前端连接的端口不匹配,自然收不到数据。
解决:
调整代码顺序,先加载dotenv再读取环境变量:
import express from 'express'; import { Server } from 'ws'; require('dotenv').config() // 移到最前面 const PORT = process.env.MAIN_PORT; const server = express() .use((req, res) => res.send("Hi there")) .listen(PORT, () => console.log(`Listening on Port ${PORT}`)) const wss = new Server({ server }); // ... 其余代码不变
同时确认.env文件里的MAIN_PORT设置正确,比如:
MAIN_PORT=23
3. 前端WebSocket连接后立即关闭
前端sendMessage函数中,channel?.stream.listen的回调里立刻执行channel!.sink.close(),可能导致消息还未完全发送到后端,连接就被断开。后端还没来得及处理消息,前端就已经关闭连接。
解决:
移除立即关闭的逻辑,或者在收到后端回复后延迟关闭,或者让连接保持更长时间:
channel?.stream.listen((event) { if (event != null) { if (kDebugMode) { print(event); } } // 注释掉这行,不要立即关闭 // channel!.sink.close(); });
如果需要主动关闭,可以在发送消息后设置延迟,或者根据业务逻辑判断时机。
4. VPS云安全组端口未开放
虽然你关闭了VPS的防火墙,但OVHcloud的VPS默认有云安全组(Network Security Group),需要手动开放MAIN_PORT端口的入站规则,否则外部请求无法到达VPS的该端口。
解决:
登录OVHcloud控制台,找到对应VPS的安全组设置,添加入站规则:允许TCP协议的MAIN_PORT端口访问,来源设置为0.0.0.0/0(测试用,生产环境建议限制IP)。
5. 前端sendObject函数未实现
页面上的"Send Object"按钮调用了sendObject()函数,但代码中没有实现该函数,点击会导致报错,影响测试。建议补充实现或暂时注释该按钮。
内容的提问来源于stack exchange,提问作者exileValor

