TCP协议作为传输层核心协议,其可靠传输特性使其成为设备通信的首选方案。在工业控制系统中,我们经常能看到PLC与上位机通过TCP保持长连接,实时传输传感器数据。而在WPF应用中实现TCP通信,最典型的场景莫过于局域网聊天工具——就像我们团队去年开发的车间设备监控系统,操作员在控制室就能实时查看流水线状态。
要实现这个目标,需要理解三个核心对象:
在WPF中集成这些组件时,有个关键陷阱:网络回调线程不能直接操作UI。这就像车间工人不能直接修改控制室显示屏——必须通过Dispatcher这个"车间主任"来协调。我曾在项目中因为忽略这点导致界面卡死,后来通过Dispatcher.BeginInvoke解决了线程安全问题。
创建TCP服务器就像开一家餐厅:
csharp复制private TcpListener _listener;
private void StartListening(string ip, int port)
{
_listener = new TcpListener(IPAddress.Parse(ip), port);
_listener.Start();
// 后台线程处理连接请求
var listenThread = new Thread(ListenForClients)
{
IsBackground = true
};
listenThread.Start();
}
这里有个实用技巧:将线程设为IsBackground=true,这样程序退出时会自动终止线程,避免僵尸进程。我曾忘记设置这个属性,导致服务停止后线程仍在运行占用端口。
处理客户端连接就像接待顾客:
csharp复制private void ListenForClients()
{
while (true)
{
var client = _listener.AcceptTcpClient();
var clientThread = new Thread(HandleClientComm);
clientThread.Start(client);
}
}
private void HandleClientComm(object clientObj)
{
using (var client = (TcpClient)clientObj)
using (var stream = client.GetStream())
{
byte[] buffer = new byte[4096];
while (client.Connected)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead == 0) continue;
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Dispatcher.BeginInvoke(() =>
{
logTextBox.AppendText($"[{DateTime.Now}] 收到: {message}\n");
});
}
}
}
注意using语句确保资源释放,这是很多新手容易忽略的内存泄漏点。缓冲区大小建议设为4KB的整数倍,这是网络传输的优化区块大小。
客户端连接需要处理三种异常情况:
csharp复制private TcpClient _client;
private NetworkStream _stream;
private async Task ConnectToServer(string ip, int port)
{
_client = new TcpClient();
try
{
await _client.ConnectAsync(IPAddress.Parse(ip), port);
_stream = _client.GetStream();
// 启动接收线程
var receiveThread = new Thread(ReceiveData)
{
IsBackground = true
};
receiveThread.Start();
// 启动心跳线程
StartHeartbeat();
}
catch (Exception ex)
{
MessageBox.Show($"连接失败: {ex.Message}");
}
}
private void StartHeartbeat()
{
var heartbeatThread = new Thread(() =>
{
while (_client?.Connected == true)
{
SendMessage("HEARTBEAT");
Thread.Sleep(5000);
}
}) { IsBackground = true };
heartbeatThread.Start();
}
实际项目中我们发现直接使用NetworkStream效率不高,后来改用BufferedStream包装后性能提升40%:
csharp复制private BufferedStream _bufferedStream;
// 初始化时
_bufferedStream = new BufferedStream(_stream, 8192);
// 发送优化
public void SendMessage(string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
_bufferedStream.Write(data, 0, data.Length);
_bufferedStream.Flush();
}
对于高频小数据包,建议实现消息队列和批量发送机制。我们在AGV调度系统中采用此方案,网络负载降低了60%。
WPF的Dispatcher有三种常用调用方式:
csharp复制// 错误示范 - 直接跨线程访问
void UpdateUI(string msg)
{
textBox.Text = msg; // 抛出异常
}
// 正确做法1 - BeginInvoke
void SafeUpdate1(string msg)
{
Dispatcher.BeginInvoke(() => textBox.Text = msg);
}
// 正确做法2 - InvokeAsync
async Task SafeUpdate2(string msg)
{
await Dispatcher.InvokeAsync(() => textBox.Text = msg);
}
在实时监控系统中,我们测试发现BeginInvoke比Invoke快3-5ms,对于高频更新场景差异明显。
推荐使用WPF自带的性能分析工具:
在实现设备状态看板时,我们通过性能分析发现过度使用Dispatcher.Invoke导致UI卡顿。优化方案是:
xml复制<ListBox VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.IsDeferredScrollingEnabled="True"/>
这些优化使2000条数据记录的渲染时间从1.2秒降至0.3秒。