Flutter二次打开页面ServerSocket报流已监听如何正确解绑销毁
问题现象
StatefulWidget页面首次跳转进入时运行正常,二次打开页面抛出bad stream , stream was listen to报错,涉及代码如下:
@override void initState(){ listenToEvent(); super.initState(); } void listenToEvent(){ finale server =ServerSocket.bind(InternetAddress.anyIPv4,4000,shared:true); server.listen((socket){ // 第二次打开页面时异常从这里触发 handleConnection(socket); }); } void handleConnection(Socket client){ client.listen( //此处可正常读取所有数据 ); }
报错原因
ServerSocket返回的是单订阅流,单订阅流只允许注册一次监听。设置shared: true参数后,同端口的ServerSocket会全局复用,首次进入页面时已经给流注册了监听,二次进入页面重复调用listen就会触发报错。- 代码存在两个基础错误:一是
initState生命周期中super.initState()调用顺序错误,框架要求必须在initState开头首先调用父类初始化方法;二是ServerSocket.bind是异步方法,未等待绑定完成就直接调用listen逻辑存在时序问题。 - 页面销毁时没有释放ServerSocket和流订阅资源,导致端口和流实例持续被持有,无法被回收。
修复方案
- 把ServerSocket实例、流订阅实例存为State的成员变量,确保dispose阶段可以拿到引用释放资源。
- 非跨Isolate/全局复用场景,直接关闭
shared参数,避免流被全局复用。 - 在页面的
dispose生命周期中,依次取消所有流订阅、关闭ServerSocket释放端口。
修正后的完整代码:
class YourWidgetState extends State<YourWidget> { ServerSocket? _server; StreamSubscription<Socket>? _serverSubscription; StreamSubscription? _clientSubscription; @override void initState(){ super.initState(); // 必须放在initState最开头调用 listenToEvent(); } Future<void> listenToEvent() async { // 修正原代码拼写错误finale -> final,等待异步绑定完成 _server = await ServerSocket.bind( InternetAddress.anyIPv4, 4000, shared: false, // 页面私有服务关闭共享 ); _serverSubscription = _server!.listen((socket) { handleConnection(socket); }); } void handleConnection(Socket client){ _clientSubscription = client.listen( // 原有数据读取逻辑 ); } @override void dispose() { // 释放所有持有的资源 _clientSubscription?.cancel(); _serverSubscription?.cancel(); _server?.close(); super.dispose(); } }
如果确实需要用shared: true做全局端口复用,不要把ServerSocket的绑定和监听逻辑写在页面initState里,把这部分逻辑放到应用启动的全局初始化位置,保证整个应用生命周期只调用一次listen即可,页面层只负责接收转发的连接事件,不要重复注册监听。
内容的提问来源于stack exchange,提问作者PosF
相关产品推荐
相关产品推荐

