You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在C# WCF单实例中实现客户端变更的多端同步可见?

嘿,看起来你要在单实例WCF服务里实现客户端操作的跨端同步,让所有连接的客户端都能看到其他用户的变更,还要显示操作方的IP对吧?我给你捋捋完整的实现思路和代码补充,刚好能匹配你给出的代码片段方向:

实现单实例WCF中客户端变更的跨客户端同步

1. 先把WCF服务设为单实例模式

所有客户端必须连接到同一个服务实例,才能共享状态和推送通知。你可以通过特性或者配置文件来设置:

代码特性方式

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ClientSyncService : IClientSyncService
{
    // 服务核心实现代码
}

配置文件方式(app.config/web.config)

<service name="YourNamespace.ClientSyncService">
  <endpoint address="" binding="netTcpBinding" contract="YourNamespace.IClientSyncService"/>
  <host>
    <baseAddresses>
      <add baseAddress="net.tcp://localhost:8080/ClientSyncService"/>
    </baseAddresses>
  </host>
  <behavior>
    <serviceMetadata httpGetEnabled="false"/>
    <serviceDebug includeExceptionDetailInFaults="true"/>
    <!-- 单实例核心配置 -->
    <serviceBehavior instanceContextMode="Single"/>
  </behavior>
</service>

2. 定义双工回调契约

要实现服务主动向客户端推送消息,必须用WCF的双工通信。先定义客户端需要实现的回调接口:

// 客户端回调接口,用于接收服务端的变更通知
public interface IClientSyncCallback
{
    [OperationContract(IsOneWay = true)]
    void OnDataChanged(string changeContent, string operatorIp);
}

然后在服务契约里指定这个回调契约:

[ServiceContract(CallbackContract = typeof(IClientSyncCallback))]
public interface IClientSyncService
{
    [OperationContract]
    void SubmitClientChange(string changeContent);
    
    [OperationContract]
    void RegisterClient();
}

3. 服务端完整实现(包含你的代码片段补充)

在单实例服务里维护所有连接的客户端,实现IP获取、变更检测和通知推送:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class ClientSyncService : IClientSyncService, INotifyPropertyChanged
{
    // 线程安全集合存储所有连接的客户端回调
    private readonly List<IClientSyncCallback> _connectedClients = new List<IClientSyncCallback>();
    private readonly object _lockObj = new object();

    // 你的PropertyChanged事件逻辑
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisedPropertyChanged(string prop)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        
        // 如果是数据库相关属性变更,直接触发客户端通知
        if (prop.Equals("DatabaseContent", StringComparison.OrdinalIgnoreCase))
        {
            NotifyAllClients("数据库内容已更新", "Server");
        }
    }

    // 补充你代码里的IP获取逻辑
    private string GetClientIp()
    {
        OperationContext context = OperationContext.Current;
        RemoteEndpointMessageProperty endpointProp = 
            context.IncomingMessageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
        
        return endpointProp?.Address ?? "未知IP";
    }

    // 客户端提交变更时触发的方法
    public void SubmitClientChange(string changeContent)
    {
        string clientIp = GetClientIp();
        
        // 这里可以添加数据库写入逻辑,比如把变更内容和IP存入数据库
        // SaveChangeToDb(changeContent, clientIp);
        
        // 通知所有在线客户端
        NotifyAllClients(changeContent, clientIp);
        
        // 触发属性变更事件(如果需要关联数据库监听)
        RaisedPropertyChanged("DatabaseContent");
    }

    // 客户端连接时注册回调实例
    public void RegisterClient()
    {
        IClientSyncCallback callback = OperationContext.Current.GetCallbackChannel<IClientSyncCallback>();
        lock (_lockObj)
        {
            if (!_connectedClients.Contains(callback))
            {
                _connectedClients.Add(callback);
            }
        }
    }

    // 向所有客户端推送变更通知
    private void NotifyAllClients(string changeContent, string operatorIp)
    {
        lock (_lockObj)
        {
            // 先移除失效的客户端(比如断开连接的)
            _connectedClients.RemoveAll(c => !IsCallbackChannelAlive(c));
            
            // 遍历推送
            foreach (var client in _connectedClients)
            {
                try
                {
                    client.OnDataChanged(changeContent, operatorIp);
                }
                catch (Exception)
                {
                    // 推送失败说明客户端已断开,移除该实例
                    _connectedClients.Remove(client);
                }
            }
        }
    }

    // 检查回调通道是否存活
    private bool IsCallbackChannelAlive(IClientSyncCallback callback)
    {
        if (callback is ICommunicationObject commObj)
        {
            return commObj.State == CommunicationState.Opened;
        }
        return false;
    }

    // 数据库变更监听逻辑(可选,比如定时轮询或用SQL Change Tracking)
    private void StartDbChangeMonitoring()
    {
        // 示例:每5秒检查一次数据库变更
        Timer monitorTimer = new Timer(CheckDbChanges, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
    }

    private void CheckDbChanges(object state)
    {
        // 这里实现数据库查询逻辑,获取最新变更
        string latestChange = GetLatestDbChange();
        if (!string.IsNullOrEmpty(latestChange))
        {
            // 服务端检测到的变更,IP标记为服务端
            NotifyAllClients(latestChange, "Server");
        }
    }
}

4. 客户端实现:接收并显示变更

客户端需要实现回调接口,连接服务时注册自己:

// 客户端回调处理类,接收服务端推送
public class SyncCallbackHandler : IClientSyncCallback
{
    public void OnDataChanged(string changeContent, string operatorIp)
    {
        // 注意:如果是WinForm/WPF需要切换到UI线程更新
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {operatorIp} 执行了变更:{changeContent}");
        
        // WinForm示例:
        // yourForm.Invoke(new Action(() => {
        //     txtChangeLog.Text += $"[{DateTime.Now}] {operatorIp}: {changeContent}\r\n";
        // }));
    }
}

// 客户端连接服务的代码
public void ConnectToSyncService()
{
    InstanceContext instanceCtx = new InstanceContext(new SyncCallbackHandler());
    ClientSyncServiceClient clientProxy = new ClientSyncServiceClient(instanceCtx);
    
    try
    {
        clientProxy.Open();
        clientProxy.RegisterClient();
        Console.WriteLine("已连接到同步服务");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"连接失败:{ex.Message}");
    }
}

关键注意事项

  • 线程安全:单实例服务会被多客户端并发访问,操作客户端集合时一定要加锁,避免线程冲突。
  • 双工绑定支持:必须使用支持双工的绑定(比如netTcpBindingwsDualHttpBinding),basicHttpBinding不支持双工通信。
  • 客户端断开处理:定期检查回调通道状态,移除失效客户端,避免推送时抛出异常。
  • 数据库变更检测优化:如果需要高实时性,建议用SQL Server的Change Tracking或触发器+Service Broker,比定时轮询更高效。

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

火山引擎 最新活动