一、为什么需要双向通信?

想象一下,你正在开发一个股票行情软件,服务器需要实时推送最新股价到客户端,同时客户端也可能提交交易请求。这种场景下,单向通信就像对讲机——每次只能一方说话,而双向通信则像电话——双方可以随时交流。

WCF(Windows Communication Foundation)是.NET平台的老牌通信框架,而WPF(Windows Presentation Foundation)擅长构建漂亮的桌面界面。把它们结合起来,就能实现:

  1. 服务端主动推送数据(比如股价变动)
  2. 客户端随时发起请求(比如下单交易)

二、搭建WCF服务

我们先从服务端开始。下面是一个支持双向通信的WCF服务示例:

// 技术栈:.NET Framework 4.8 + WCF
using System;
using System.ServiceModel;

// 1. 定义回调契约 - 让服务端能"打电话"回客户端
public interface IStockCallback
{
    [OperationContract(IsOneWay = true)] // 单向调用,不等待响应
    void UpdatePrice(string stockCode, decimal price);
}

// 2. 主服务契约
[ServiceContract(CallbackContract = typeof(IStockCallback))]
public interface IStockService
{
    [OperationContract]
    void Subscribe(string stockCode);

    [OperationContract]
    void PlaceOrder(string stockCode, int quantity);
}

// 3. 实现服务
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] // 每个会话一个实例
public class StockService : IStockService
{
    public void Subscribe(string stockCode)
    {
        // 获取客户端的"电话号码"(回调通道)
        var callback = OperationContext.Current.GetCallbackChannel<IStockCallback>();
        
        // 模拟每秒推送一次数据
        var timer = new System.Timers.Timer(1000);
        timer.Elapsed += (s, e) => 
        {
            try
            {
                var rnd = new Random();
                callback.UpdatePrice(stockCode, 100 + rnd.Next(0, 10));
            }
            catch 
            {
                timer.Stop(); // 客户端断开时停止推送
            }
        };
        timer.Start();
    }

    public void PlaceOrder(string stockCode, int quantity)
    {
        Console.WriteLine($"收到订单:{stockCode} x {quantity}");
    }
}

关键点解析:

  • CallbackContract:这是双向通信的灵魂,相当于给客户端分配了一个接听电话的号码
  • IsOneWay:像发短信一样,发完就不管对方是否收到
  • InstanceContextMode.PerSession:每个客户端连接单独维护状态

三、WPF客户端的实现

现在来构建能接收推送的WPF客户端:

// 技术栈:.NET Framework 4.8 + WPF
public partial class MainWindow : Window, IStockCallback
{
    private IStockService _proxy;
    
    public MainWindow()
    {
        InitializeComponent();
        ConnectToService();
    }

    private void ConnectToService()
    {
        // 1. 建立双工通道
        var context = new InstanceContext(this); // 把自己注册为回调对象
        var binding = new NetTcpBinding(SecurityMode.None); // 简单起见禁用加密
        var endpoint = new EndpointAddress("net.tcp://localhost:8000/StockService");
        
        // 2. 创建代理
        var factory = new DuplexChannelFactory<IStockService>(context, binding, endpoint);
        _proxy = factory.CreateChannel();
        
        // 3. 订阅股票代码
        _proxy.Subscribe("AAPL");
    }

    // 实现回调接口
    public void UpdatePrice(string stockCode, decimal price)
    {
        // 跨线程更新UI
        Dispatcher.Invoke(() => 
        {
            PriceLabel.Content = $"{stockCode}: {price:C}";
            PriceHistory.Text += $"{DateTime.Now:T} - {price}\n";
        });
    }

    private void BuyButton_Click(object sender, RoutedEventArgs e)
    {
        _proxy.PlaceOrder("AAPL", 100); // 下单100股
    }
}

注意事项:

  1. 线程安全:服务端回调运行在非UI线程,必须通过Dispatcher操作界面
  2. 连接管理:实际项目中需要处理断线重连
  3. 异常处理:网络操作必须包裹在try-catch中

四、配置与通信细节

在App.config中需要配置双工绑定:

<system.serviceModel>
  <services>
    <service name="StockService">
      <endpoint 
        address="net.tcp://localhost:8000/StockService"
        binding="netTcpBinding"
        bindingConfiguration="NoSecurity"
        contract="IStockService" />
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://localhost:8000/"/>
        </baseAddresses>
      </host>
    </service>
  </services>
  <bindings>
    <netTcpBinding>
      <binding name="NoSecurity" securityMode="None">
        <reliableSession enabled="true"/> <!-- 启用可靠会话 -->
      </binding>
    </netTcpBinding>
  </bindings>
</system.serviceModel>

五、技术对比与选型建议

方案 优点 缺点
WCF双工通信 原生支持,集成度高 仅限Windows环境
SignalR 跨平台,支持Web 桌面应用需要额外宿主
WebSocket 标准协议,通用性强 需要手动处理消息格式

适合场景:

  • 企业内部Windows桌面应用
  • 需要复杂事务处理的系统
  • 已有WCF基础设施的项目

六、常见问题解决方案

Q1:客户端收不到推送?
A:检查三点:

  1. 回调契约是否正确定义IsOneWay
  2. 服务端是否通过OperationContext.Current获取了回调通道
  3. 防火墙是否放行了TCP端口

Q2:如何提高性能?

  • 使用二进制编码:在binding中设置transferMode="Buffered"
  • 限制推送频率:服务端添加节流控制

Q3:如何调试?
在服务端构造函数中加入:

ServiceDebugBehavior.IncludeExceptionDetailInFaults = true;

七、升级到现代技术栈

如果你正在使用.NET Core/.NET 5+,可以考虑:

  1. 用gRPC替代WCF - 性能更好,跨平台支持
  2. 使用SignalR - 特别适合需要Web支持的场景

但传统WCF方案仍然在以下情况有价值:

  • 维护遗留系统
  • 需要利用WS-*标准协议
  • 已有大量WCF基础设施

八、总结

通过WCF的双工通信能力,我们实现了:
✅ 服务端主动推送实时数据
✅ 客户端同步发起业务请求
✅ 完整的桌面应用交互体验

记住关键点:

  1. 回调契约是双向通信的基础
  2. 处理好跨线程UI更新
  3. 生产环境需要完善的错误处理

完整示例代码可以这样组织:

StockService/
├── Server (WCF服务宿主)
├── Client (WPF应用程序) 
└── Contracts (共享契约定义)