一、为什么需要双向通信?
想象一下,你正在开发一个股票行情软件,服务器需要实时推送最新股价到客户端,同时客户端也可能提交交易请求。这种场景下,单向通信就像对讲机——每次只能一方说话,而双向通信则像电话——双方可以随时交流。
WCF(Windows Communication Foundation)是.NET平台的老牌通信框架,而WPF(Windows Presentation Foundation)擅长构建漂亮的桌面界面。把它们结合起来,就能实现:
- 服务端主动推送数据(比如股价变动)
- 客户端随时发起请求(比如下单交易)
二、搭建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股
}
}
注意事项:
- 线程安全:服务端回调运行在非UI线程,必须通过
Dispatcher操作界面 - 连接管理:实际项目中需要处理断线重连
- 异常处理:网络操作必须包裹在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:检查三点:
- 回调契约是否正确定义
IsOneWay - 服务端是否通过
OperationContext.Current获取了回调通道 - 防火墙是否放行了TCP端口
Q2:如何提高性能?
- 使用二进制编码:在binding中设置
transferMode="Buffered" - 限制推送频率:服务端添加节流控制
Q3:如何调试?
在服务端构造函数中加入:
ServiceDebugBehavior.IncludeExceptionDetailInFaults = true;
七、升级到现代技术栈
如果你正在使用.NET Core/.NET 5+,可以考虑:
- 用gRPC替代WCF - 性能更好,跨平台支持
- 使用SignalR - 特别适合需要Web支持的场景
但传统WCF方案仍然在以下情况有价值:
- 维护遗留系统
- 需要利用WS-*标准协议
- 已有大量WCF基础设施
八、总结
通过WCF的双工通信能力,我们实现了:
✅ 服务端主动推送实时数据
✅ 客户端同步发起业务请求
✅ 完整的桌面应用交互体验
记住关键点:
- 回调契约是双向通信的基础
- 处理好跨线程UI更新
- 生产环境需要完善的错误处理
完整示例代码可以这样组织:
StockService/
├── Server (WCF服务宿主)
├── Client (WPF应用程序)
└── Contracts (共享契约定义)
评论