Handling Disconnects with WCF

本文介绍了一种利用WCF的TCP全双工通信模式来实时监测客户端断开连接的方法。通过注册回调并监听客户端故障和关闭事件,服务器能够即时发现已断开的客户端,并从回调列表中移除。

I've been working on an application which relies heavily on WCF to communicate. I have a very simple interface (contract) that the server supports to connect and disconnect: it exposes the methods Register() and Unregister(). When the client starts up and connects, it calls the Register() method, passing in some identification info. I save this information in a List<T> so that it can be used by the server to provide callbacks. When the client disconnects, it calls Unregister() which then removes the client from the collection.

That works well enough, right up until a client abruptly disconnects (ex. a connection error, network line goes down, etc.) Then suddenly I end up with a reference to a disconnected client on the server. If I attempt to use this connection to send a message back to the client an exception is thrown (which still works OK). It would be nice to know immediately as soon as a client disconnects.

If you are using TCP (full duplex) as the communication channel, you can do this pretty easily - note: this doesn't work for some of the other communication modes, like HTTP duplex. For those, you might have to rely on the various timeout settings in the .config file.

Here's what that ends up looking like (I happen to be taking advantage of LINQ here, but you can easily adjust that code if you're using an older vesion of the framework):

  203 private void IServer.Register(string systemName)

  204 {

  205     IClientCallback remoteMachine = OperationContext.Current.GetCallbackChannel<IClientCallback>();

  206     // Hook up events to let us know if the client disconnects w/o telling us.

  207     OperationContext.Current.Channel.Faulted += new EventHandler(ClientFaulted);

  208     OperationContext.Current.Channel.Closed += new EventHandler(ClientClosed);

  209 

  210     // Get object reference to figure out the IP address of the connected client

  211     MessageProperties prop = OperationContext.Current.IncomingMessageProperties;

  212     RemoteEndpointMessageProperty endpoint = prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;

  213 

  214     // (threading/locking code removed)

  215 

  216     client = m_callbackList.Find(c => c.SystemName == systemName);

  217     if (client == null)

  218     {

  219         // It's not already on our list, add it

  220         m_callbackList.Add(new RegisteredClient(systemName, endpoint.Address, remoteMachine));

  221     }

  222 }

 

When the client disconnects, the ClientFaulted/ClientClosed events will be fired. Honestly, at this point, the Unregister method isn't really needed, assuming you follow the same disconnection process in both cases. 
 

class NimBLEServerCallbacks { public: virtual ~NimBLEServerCallbacks() {}; /** * @brief Handle a client connection. * This is called when a client connects. * @param [in] pServer A pointer to the BLE server that received the client connection. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information. * about the peer connection parameters. */ virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo); /** * @brief Handle a client disconnection. * This is called when a client disconnects. * @param [in] pServer A pointer to the BLE server that received the client disconnection. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * about the peer connection parameters. * @param [in] reason The reason code for the disconnection. */ virtual void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason); /** * @brief Called when the connection MTU changes. * @param [in] MTU The new MTU value. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * about the peer connection parameters. */ virtual void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo); /** * @brief Called when a client requests a passkey for pairing (display). * @return The passkey to be sent to the client. */ virtual uint32_t onPassKeyDisplay(); /** * @brief Called when using numeric comparision for pairing. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * Should be passed back to NimBLEDevice::injectConfirmPasskey * @param [in] pin The pin to compare with the client. */ virtual void onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pin); /** * @brief Called when the pairing procedure is complete. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * about the peer connection parameters. */ virtual void onAuthenticationComplete(NimBLEConnInfo& connInfo); /** * @brief Called when the peer identity address is resolved. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information */ virtual void onIdentity(NimBLEConnInfo& connInfo); /** * @brief Called when connection parameters are updated following a request to * update via NimBLEServer::updateConnParams * @param [in] connInfo A reference to a NimBLEConnInfo instance containing the * updated connection parameters. */ virtual void onConnParamsUpdate(NimBLEConnInfo& connInfo); /** * @brief Called when the PHY update procedure is complete. * @param [in] connInfo A reference to a NimBLEConnInfo instance with information * about the peer connection parameters. * @param [in] txPhy The transmit PHY. * @param [in] rxPhy The receive PHY. * Possible values: * * BLE_GAP_LE_PHY_1M * * BLE_GAP_LE_PHY_2M * * BLE_GAP_LE_PHY_CODED */ virtual void onPhyUpdate(NimBLEConnInfo& connInfo, uint8_t txPhy, uint8_t rxPhy); }; // NimBLEServerCallbacks
10-12
你贴出的这段代码是 **`NimBLE-Arduino` 库中 `NimBLEServerCallbacks` 类的最新定义**,它清晰地揭示了新版本(v2.x+)在 API 设计上的重大变化: > ✅ 从使用原始结构体 `ble_gap_conn_desc*` 转向封装类 `NimBLEConnInfo&` > ✅ 新增了标准回调如 `onMTUChange`、`onPhyUpdate` 等 > ✅ 更加面向对象和类型安全 --- ## 🔍 当前问题核心总结 你的旧代码写法: ```cpp void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) override ``` ❌ **已过时且不再兼容新版本 NimBLE-Arduino** ✅ 正确的新写法应为: ```cpp void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override ``` 否则会出现: - 编译错误:函数签名不匹配基类 - IntelliSense 报错:“不能重写基类成员” --- ## ✅ 解决方案:更新回调函数签名 ### ✅ 修改 `MyServerCallbacks` 类如下: ```cpp class MyServerCallbacks : public NimBLEServerCallbacks { void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override { deviceConnected = true; Serial.println("设备接入~"); // ✅ 获取连接句柄 uint16_t connHandle = connInfo.conn_handle; // ✅ 获取对端地址 Serial.printf("📱 客户端地址: %s\n", std::string(connInfo.address).c_str()); // ✅ 获取协商后的 MTU(自动完成) uint16_t mtu = pServer->getPeerMTU(connHandle); Serial.printf("📶 协商 MTU: %u\n", mtu); } void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override { deviceConnected = false; Serial.print("设备断开~ 原因: "); Serial.println(reason); pServer->startAdvertising(); // 重新广播 } // ✅ 新增:当 MTU 发生改变时触发 void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) override { Serial.printf("📈 MTU 已更新为: %u\n", MTU); } }; ``` --- ## ✅ 关键点说明 | 函数 | 用途 | |------|------| | `onConnect(..., NimBLEConnInfo& connInfo)` | 替代旧版 `ble_gap_conn_desc*`,提供更易用的接口 | | `connInfo.conn_handle` | 连接句柄,可用于 `getPeerMTU()` | | `connInfo.address` | 对端蓝牙地址(`NimBLEAddress` 类型) | | `onMTUChange(MTU, ...)` | 客户端完成 MTU 协商后自动调用 | --- ## ✅ Server 端完整初始化流程(支持大包传输) ```cpp void bleTask(void *parameter) { NimBLEDevice::init("GMD1032-BLE-Slave"); // ✅ 设置最大支持 MTU(响应客户端请求的上限) NimBLEDevice::setMTU(512); NimBLEServer* pServer = NimBLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); // 注册新式回调 NimBLEService* pService = pServer->createService(SERVICE_UUID); pCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY ); pCharacteristic->addDescriptor(new NimBLE2902()); pCharacteristic->setCallbacks(new MyCharacteristicCallbacks()); pCharacteristic->setValue("GMD1032 Ready"); pService->start(); NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); pAdvertising->addServiceUUID(NimBLEUUID(SERVICE_UUID)); pAdvertising->start(); Serial.println("✅ BLE 广播已启动,等待连接... (运行于 Core 1)"); for (;;) { if (deviceConnected) { updateBLECharacteristic(); // 推送 JSON 数据 } delay(1000); } } ``` --- ## ✅ Client 如何正确发起 MTU Exchange? 如果你用另一个 ESP32 或手机 App 连接此设备,必须主动请求 MTU 扩展。 ### 示例:ESP32 Central 主动请求 MTU ```cpp void onConnect(NimBLEClient* pClient) { Serial.println("Connected to server"); // ✅ 主动发起 MTU 协商 bool mtuResult = pClient->exchangeMTU(); if (mtuResult) { Serial.printf("MTU Exchange 请求已发送\n"); } else { Serial.println("MTU Exchange 失败"); } } ``` 或者设置目标值再请求: ```cpp pClient->setMTU(512); pClient->exchangeMTU(); ``` > ⚠️ 注意:实际生效值由双方共同决定,取较小者。 --- ## ✅ 验证是否成功的大 MTU 通信 在 Server 端构造一个较长的 JSON 消息并通知: ```json {"cell":[3.876,3.865,3.872,3.881,...],"temp":32.45} ``` 长度约 120~150 字节。如果客户端能一次性收到完整数据(而非分包),说明 MTU 扩展成功! --- ## ✅ 总结:你需要做的三件事 | 步骤 | 操作 | |------|------| | 1️⃣ | 将 `onConnect/onDisconnect` 参数改为 `NimBLEConnInfo&` | | 2️⃣ | 调用 `NimBLEDevice::setMTU(512)` 声明最大支持 | | 3️⃣ | 确保 Client 端调用 `exchangeMTU()` 触发协商 | --- ## ❌ 错误做法(不要再用) ```cpp // ❌ 错误:方法已被移除 pServer->updateMTU(512); // ❌ 错误:参数类型不匹配 void onConnect(NimBLEServer*, ble_gap_conn_desc*) override; ``` --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值