You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32 Websocket客户端断开时C#服务器OnClose方法未触发问题

问题:WebSocketSharp服务器无法检测ESP32设备离线

我用WebSocketSharp库在C#中搭建了WebSocket服务器,电脑和浏览器客户端连接时服务器运行正常。核心需求是连接ESP32实现远程控制,目前ESP32客户端能成功连接C#服务器,正常收发消息,但ESP32断开连接时,服务器的OnClose方法无法触发,也就是服务器检测不到ESP32离线。我试过多个ESP客户端库,只有一个能成功连接,但还是存在这个问题。

C#服务器代码

using System;
using WebSocketSharp;
using WebSocketSharp.Server;
namespace web_socket
{
    public class websocser : WebSocketBehavior
    {
        protected override void OnOpen()
        {
            Console.WriteLine("Device Was Connected. ID: "+ID);
            base.OnOpen();
        }
        protected override void OnClose(CloseEventArgs e)
        {
            Console.WriteLine("Device Was Disconnected. ID: "+ID);
            base.OnClose(e);
        }
        protected override void OnMessage(MessageEventArgs e)
        {
            Console.WriteLine(e.Data);
            base.OnMessage(e);
        }
    }
}

ESP32客户端代码

#include <Arduino.h>
#include <WiFi.h>
#include <WebSocketsClient.h>
 
WebSocketsClient webSocket;
 
const char *ssid     = "#####";
const char *password = "#####";
 
unsigned long messageInterval = 5000;
bool connected = false;
 
#define DEBUG_SERIAL Serial
 
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
    const uint8_t* src = (const uint8_t*) mem;
    DEBUG_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
    for(uint32_t i = 0; i < len; i++) {
        if(i % cols == 0) {
            DEBUG_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
        }
        DEBUG_SERIAL.printf("%02X ", *src);
        src++;
    }
    DEBUG_SERIAL.printf("\n");
}
 
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
 
    switch(type) {
        case WStype_DISCONNECTED:
            DEBUG_SERIAL.printf("[WSc] Disconnected!\n");
            connected = false;
            break;
        case WStype_CONNECTED: {
            DEBUG_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
            connected = true;
 
            // send message to server when Connected
            DEBUG_SERIAL.println("[WSc] SENT: Connected");
            // webSocket.sendTXT("Connected");
        }
            break;
        case WStype_TEXT:
            DEBUG_SERIAL.printf("[WSc] RESPONSE: %s\n", payload);
            break;
        case WStype_BIN:
            DEBUG_SERIAL.printf("[WSc] get binary length: %u\n", length);
            hexdump(payload, length);
            break;
        case WStype_PING:
            // pong will be send automatically
            DEBUG_SERIAL.printf("[WSc] get ping\n");
            break;
        case WStype_PONG:
            // answer to a ping we send
            DEBUG_SERIAL.printf("[WSc] get pong\n");
            break;
        case WStype_ERROR:
        case WStype_FRAGMENT_TEXT_START:
        case WStype_FRAGMENT_BIN_START:
        case WStype_FRAGMENT:
        case WStype_FRAGMENT_FIN:
            break;
    }
}
 
void setup() {
    DEBUG_SERIAL.begin(115200);
 
//  DEBUG_SERIAL.setDebugOutput(true);
 
    DEBUG_SERIAL.println();
    DEBUG_SERIAL.println();
    DEBUG_SERIAL.println();
 
    for(uint8_t t = 4; t > 0; t--) {
        DEBUG_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
        DEBUG_SERIAL.flush();
        delay(1000);
    }
 
    WiFi.begin(ssid, password);
 
    while ( WiFi.status() != WL_CONNECTED ) {
      delay ( 500 );
      DEBUG_SERIAL.print ( "." );
    }
    DEBUG_SERIAL.print("Local IP: "); DEBUG_SERIAL.println(WiFi.localIP());
    // server address, port and URL
    webSocket.begin("192.168.1.34", 1005, "/ExEsp");
 
    // event handler
    webSocket.onEvent(webSocketEvent);
}
 
unsigned long lastUpdate = millis();
 
 
void loop() {
    webSocket.loop();
    // if (connected && lastUpdate+messageInterval<millis()){
    //     DEBUG_SERIAL.println("[WSc] SENT: Simple js client message!!");
    //     webSocket.sendTXT("Simple js client message!!");
    //     lastUpdate = millis();
    // }
}

解决方案

原因分析

问题出在TCP连接的断开检测机制上:当ESP32突然断电或网络异常断开时,不会主动发送WebSocket关闭帧,而WebSocketSharp默认不会主动检测死连接,导致服务器无法触发OnClose事件。浏览器客户端正常触发是因为浏览器会在关闭时主动发送关闭帧。

修复步骤

  1. 在C#服务器端启用Ping/Pong心跳检测
    修改WebSocket服务器的启动配置,开启自动Ping并设置超时时间,服务器会定期向客户端发送Ping,若客户端在超时时间内未回复Pong,就会判定连接断开并触发OnClose。

    补充服务器启动代码:

    var wssv = new WebSocketServer("ws://0.0.0.0:1005");
    wssv.AddWebSocketService<websocser>("/ExEsp");
    // 配置心跳:每10秒发送一次Ping,30秒未回复则断开
    wssv.WaitTime = TimeSpan.FromSeconds(30);
    wssv.PingInterval = TimeSpan.FromSeconds(10);
    wssv.Start();
    
  2. 确保ESP32客户端正常回复Ping
    你的ESP32代码中已经持续调用webSocket.loop(),WebSocketsClient库会自动回复Ping,这部分无需额外修改,只需保证loop()不被长时间阻塞即可。

  3. 主动断开时发送关闭帧
    如果ESP32需要主动断开连接,要显式调用webSocket.close(),这样会发送标准关闭帧,服务器能立即触发OnClose:

    // 主动断开时调用
    webSocket.close();
    

验证方法

  • 断开ESP32电源,等待服务器超时时间(30秒),查看控制台是否输出断开日志。
  • 在ESP32代码中主动调用webSocket.close(),查看服务器是否立即触发OnClose。

内容的提问来源于stack exchange,提问作者ogzerg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:40:44