WCF服务端处理客户端断开异常及会话清理问题
我来帮你搞定这两个WCF服务端的问题,针对你用.NET 4.5、net.pipe传输的单实例服务场景:
Q1:捕获通信异常,确保服务端不终止运行
WCF服务端遇到未处理的CommunicationException时崩溃,核心原因是异常没有被正确拦截处理。最可靠的方案是实现**IErrorHandler接口**全局捕获服务端异常,避免进程终止;同时给回调通道注册Faulted事件,提前处理客户端断开的情况,减少后续异常产生。
步骤1:实现自定义错误处理器
这个处理器会拦截所有服务端异常,判断是否为通信类异常并处理,防止异常扩散导致服务崩溃:
using System.ServiceModel; using System.ServiceModel.Channels; public class CustomErrorHandler : IErrorHandler { // 处理异常,返回true表示已处理,不会传播异常 public bool HandleError(Exception error) { // 匹配通信相关异常(包括直接抛出或嵌套在InnerException中的) if (error is CommunicationException || error.InnerException is CommunicationException) { // 这里可以添加日志记录、告警等逻辑 Console.WriteLine($"捕获到客户端断开的通信异常: {error.Message}"); return true; } // 非通信异常交给WCF默认处理 return false; } // 构造返回给客户端的错误消息(单向操作或客户端已断开时可以留空) public void ProvideFault(Exception error, MessageVersion version, ref Message fault) { // 如果需要给客户端返回自定义错误,可以在这里构造FaultMessage // 例如:fault = Message.CreateMessage(version, "", "客户端连接已断开"); } }
步骤2:注册错误处理器到服务宿主
在启动服务的代码中,把自定义错误处理器添加到服务行为里,确保所有端点都能使用:
using (ServiceHost host = new ServiceHost(typeof(Service))) { // 给所有端点添加错误处理器 foreach (var endpoint in host.Description.Endpoints) { endpoint.Behaviors.Add(new CustomErrorHandler()); } host.Open(); Console.WriteLine("WCF服务已启动,按任意键停止..."); Console.ReadKey(); host.Close(); }
步骤3:给回调通道注册Faulted事件
当客户端异常断开时,回调通道会进入Faulted状态,提前监听这个事件可以主动清理无效通道,减少后续调用时的异常:
修改你的Service类的Connect方法:
public void Connect(bool status) { IContractCallback callbackChannel = OperationContext.Current.GetCallbackChannel<IContractCallback>(); // 注册Faulted事件,监听客户端断开 ((ICommunicationObject)callbackChannel).Faulted += CallbackChannel_Faulted; lock (_lockObj) // 加锁保证线程安全(单实例服务多客户端并发) { bool isAlreadyConnected = _channeList.Any(cb => cb == callbackChannel); if (!isAlreadyConnected) { _channeList.Add(callbackChannel); callbackChannel.ConnectCallback(true); } else { callbackChannel.ConnectCallback(false); } } } // 处理回调通道故障的方法 private void CallbackChannel_Faulted(object sender, EventArgs e) { var faultedChannel = sender as IContractCallback; if (faultedChannel != null) { lock (_lockObj) { _channeList.Remove(faultedChannel); } // 释放故障通道资源 ((ICommunicationObject)faultedChannel).Abort(); Console.WriteLine("客户端断开,已清理无效回调通道"); } }
Q2:获取中止会话的SessionId,清理专属数据
要获取会话ID,我们可以从OperationContext.Current.SessionId获取,这个属性在服务端处理请求或回调通道事件时都是可用的。我们可以在错误处理器和Faulted事件中获取SessionId,然后执行清理逻辑。
1. 在Faulted事件中获取SessionId并清理
修改CallbackChannel_Faulted方法,加入SessionId的获取和清理逻辑:
private void CallbackChannel_Faulted(object sender, EventArgs e) { var faultedChannel = sender as IContractCallback; if (faultedChannel != null) { // 获取当前会话的SessionId string sessionId = OperationContext.Current?.SessionId; if (!string.IsNullOrEmpty(sessionId)) { Console.WriteLine($"会话 {sessionId} 已中止,开始清理专属数据"); CleanupSessionSpecificData(sessionId); } lock (_lockObj) { _channeList.Remove(faultedChannel); } ((ICommunicationObject)faultedChannel).Abort(); Console.WriteLine("客户端断开,已清理无效回调通道"); } } // 自定义的会话数据清理方法 private void CleanupSessionSpecificData(string sessionId) { // 这里根据你的业务逻辑实现,比如从缓存中移除该会话的专属数据 // 示例:SessionDataCache.Remove(sessionId); }
2. 在错误处理器中获取SessionId
如果异常是在调用回调方法时抛出的,也可以在CustomErrorHandler的HandleError方法中获取SessionId:
public bool HandleError(Exception error) { if (error is CommunicationException || error.InnerException is CommunicationException) { string sessionId = OperationContext.Current?.SessionId; if (!string.IsNullOrEmpty(sessionId)) { Console.WriteLine($"捕获到通信异常,会话 {sessionId} 中止,清理数据"); // 调用清理方法(单实例服务可直接调用实例方法) CleanupSessionSpecificData(sessionId); } Console.WriteLine($"捕获到客户端断开的通信异常: {error.Message}"); return true; } return false; }
额外的线程安全提示
因为你的服务是InstanceContextMode.Single(单实例),多客户端并发操作_channeList会有线程安全问题,所以所有对_channeList的增删操作都要加锁,你需要在Service类中定义锁对象:
public class Service : IContractServer { private readonly object _lockObj = new object(); // 线程锁对象 public List<IContractCallback> _channeList = new List<IContractCallback>(); // 其他方法... }
修正客户端的Disconnect调用
你的客户端代码中Disconnect的调用有问题,服务端的Disconnect参数是IContractServer,但客户端传的是自己的Proxy(服务契约实例),和服务端列表中的IContractCallback不匹配。建议修改服务端的Disconnect方法为无参数,让服务端自己获取当前客户端的回调通道:
// 服务端Disconnect方法修改 public void Disconnect() { var currentCallback = OperationContext.Current.GetCallbackChannel<IContractCallback>(); lock (_lockObj) { _channeList.Remove(currentCallback); } currentCallback.ConnectCallback(false); // 通知客户端已断开 } // 客户端MainWindow_OnClosing方法修改 private void MainWindow_OnClosing(object sender, CancelEventArgs e) { try { Proxy.Disconnect(); ((ICommunicationObject)Proxy).Close(); // 优雅关闭连接 } catch (CommunicationException) { ((ICommunicationObject)Proxy).Abort(); // 异常时强制终止 } }
内容的提问来源于stack exchange,提问作者DEVELOPER

