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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:48:36