作为一名.NET开发者,你是否曾为串口通信的稳定性问题而头疼?设备连接不稳定、数据丢失、界面卡顿...这些都是工业软件开发中的常见痛点。
今天,我将通过一个完整的串口通信解决方案,带你彻底搞定C#串口编程的所有技术难点。这套方案已在多个工业项目中验证,具备异步处理、缓冲优化、连接管理等企业级特性。
1. 连接状态管理混乱
C#// 错误示例:状态管理不一致
if (serialPort.IsOpen) // 这里可能抛异常
{
serialPort.Write(data); // 写入时端口可能已断开
}
2. 数据接收不完整
C#// 错误示例:可能丢失数据
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = serialPort.ReadExisting(); // 可能读取不完整
}
3. UI线程阻塞
C#// 错误示例:同步操作阻塞界面
serialPort.Write(data); // 可能导致界面卡顿
我们的解决方案采用三层架构:
SerialPortConfig - 统一管理连接参数OptimizedSerialPortHandler - 核心通信逻辑Form1 - 用户交互和状态展示C#public class SerialPortConfig
{
public string PortName { get; set; }
public int BaudRate { get; set; }
public Parity Parity { get; set; }
public int DataBits { get; set; }
public StopBits StopBits { get; set; }
public int ReadBufferSize { get; set; } = 4096;
public int WriteBufferSize { get; set; } = 4096;
public int ReadTimeout { get; set; } = 500;
public int WriteTimeout { get; set; } = 500;
}
设计亮点:
C#private bool _isConnected = false;
private readonly object _lockObject = new object();
public bool IsConnected => _isConnected && (_serialPort?.IsOpen ?? false);
public async Task<bool> ConnectAsync(SerialPortConfig config)
{
try
{
lock (_lockObject)
{
if (_serialPort?.IsOpen == true)
{
_serialPort.Close();
_isConnected = false;
}
_serialPort = new SerialPort(config.PortName, config.BaudRate,
config.Parity, config.DataBits, config.StopBits)
{
ReadBufferSize = config.ReadBufferSize,
WriteBufferSize = config.WriteBufferSize,
ReadTimeout = config.ReadTimeout,
WriteTimeout = config.WriteTimeout
};
}
await Task.Run(() => _serialPort.Open());
_isConnected = true;
return true;
}
catch (Exception ex)
{
_isConnected = false;
ErrorOccurred?.Invoke($"连接失败: {ex.Message}");
return false;
}
}
技术要点:
_isConnected + IsOpen)C#// 同步发送 - 适用于小数据量
public void SendData(byte[] data)
{
if (!IsConnected) return;
try
{
lock (_lockObject)
{
if (_serialPort.IsOpen)
{
_serialPort.Write(data, 0, data.Length);
}
}
}
catch (TimeoutException ex)
{
ErrorOccurred?.Invoke($"发送超时: {ex.Message}");
}
}
// 异步发送 - 适用于大数据量或高频发送
public async Task<bool> SendDataAsync(byte[] data, int timeoutMilliseconds = 1000)
{
if (!IsConnected) return false;
var timeoutCts = new CancellationTokenSource(timeoutMilliseconds);
try
{
await Task.Run(() =>
{
lock (_lockObject)
{
if (_serialPort.IsOpen)
{
_serialPort.Write(data, 0, data.Length);
}
}
}, timeoutCts.Token);
return true;
}
catch (OperationCanceledException)
{
ErrorOccurred?.Invoke("发送操作超时");
return false;
}
}
C#private async void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
byte[] data = await ReadDataAsync();
if (data.Length > 0)
{
DataReceived?.Invoke(data);
}
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"数据接收异常: {ex.Message}");
}
}
private async Task<byte[]> ReadDataAsync()
{
using (var ms = new MemoryStream())
{
try
{
int bytesRead;
do
{
bytesRead = await _serialPort.BaseStream.ReadAsync(
_receiveBuffer, 0, _receiveBuffer.Length);
if (bytesRead > 0)
{
ms.Write(_receiveBuffer, 0, bytesRead);
}
} while (bytesRead > 0 && _serialPort.BytesToRead > 0);
return ms.ToArray();
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"读取数据失败: {ex.Message}");
return new byte[0];
}
}
}
核心优势:
C#private void UpdateConnectionStatus(bool isConnected)
{
if (isConnected)
{
lblStatus.Text = "状态: 已连接";
lblStatus.ForeColor = Color.FromArgb(76, 175, 80); // 绿色
pnlConfig.Enabled = false; // 禁用配置面板
btnConnect.Enabled = false;
btnDisconnect.Enabled = true;
}
else
{
lblStatus.Text = "状态: 未连接";
lblStatus.ForeColor = Color.FromArgb(244, 67, 54); // 红色
pnlConfig.Enabled = true; // 启用配置面板
btnConnect.Enabled = true;
btnDisconnect.Enabled = false;
}
}
C#private void OnDataReceived(byte[] data)
{
if (InvokeRequired)
{
Invoke(new Action<byte[]>(OnDataReceived), data);
return;
}
_bytesReceived += data.Length;
lblBytesReceived.Text = $"接收: {_bytesReceived} 字节";
string text = Encoding.UTF8.GetString(data);
rtbReceived.SelectionColor = Color.Green;
rtbReceived.AppendText($"[{DateTime.Now:HH:mm:ss}] 接收: {text}\n");
rtbReceived.ScrollToCaret();
}
C#using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppSerialPortBuffer
{
public class SerialPortConfig
{
public string PortName { get; set; }
public int BaudRate { get; set; }
public Parity Parity { get; set; }
public int DataBits { get; set; }
public StopBits StopBits { get; set; }
public int ReadBufferSize { get; set; } = 4096;
public int WriteBufferSize { get; set; } = 4096;
public int ReadTimeout { get; set; } = 500;
public int WriteTimeout { get; set; } = 500;
}
}
C#using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AppSerialPortBuffer
{
public class OptimizedSerialPortHandler : IDisposable
{
private SerialPort _serialPort;
private CancellationTokenSource _cancellationTokenSource;
private byte[] _receiveBuffer;
private bool _disposed = false;
private bool _isConnected = false;
private readonly object _lockObject = new object();
public event Action<byte[]> DataReceived;
public event Action<string> ErrorOccurred;
public bool IsConnected => _isConnected && (_serialPort?.IsOpen ?? false);
public async Task<bool> ConnectAsync(SerialPortConfig config)
{
try
{
lock (_lockObject)
{
if (_serialPort?.IsOpen == true)
{
_serialPort.Close();
_isConnected = false;
}
_serialPort = new SerialPort(config.PortName, config.BaudRate, config.Parity, config.DataBits, config.StopBits)
{
ReadBufferSize = config.ReadBufferSize,
WriteBufferSize = config.WriteBufferSize,
ReadTimeout = config.ReadTimeout,
WriteTimeout = config.WriteTimeout,
Handshake = Handshake.None
};
_receiveBuffer = new byte[config.ReadBufferSize];
_serialPort.DataReceived += SerialPort_DataReceived;
_serialPort.ErrorReceived += SerialPort_ErrorReceived;
}
await Task.Run(() => _serialPort.Open());
_cancellationTokenSource = new CancellationTokenSource();
_isConnected = true; // 连接成功后设置标志
return true;
}
catch (Exception ex)
{
_isConnected = false; // 连接失败时确保标志为false
ErrorOccurred?.Invoke($"连接失败: {ex.Message}");
return false;
}
}
public void Disconnect()
{
try
{
_isConnected = false; // 立即设置连接状态为false
_cancellationTokenSource?.Cancel();
lock (_lockObject)
{
if (_serialPort?.IsOpen == true)
{
_serialPort.DataReceived -= SerialPort_DataReceived;
_serialPort.ErrorReceived -= SerialPort_ErrorReceived;
_serialPort.Close();
}
}
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"断开连接时出错: {ex.Message}");
}
finally
{
_isConnected = false; // 确保在任何情况下都设置为false
}
}
// 同步发送
public void SendData(byte[] data)
{
if (!IsConnected) return;
try
{
lock (_lockObject)
{
if (_serialPort.IsOpen)
{
_serialPort.Write(data, 0, data.Length);
}
}
}
catch (TimeoutException ex)
{
ErrorOccurred?.Invoke($"发送超时: {ex.Message}");
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"发送失败: {ex.Message}");
}
}
// 异步发送
public async Task<bool> SendDataAsync(byte[] data, int timeoutMilliseconds = 1000)
{
if (!IsConnected) return false;
var timeoutCts = new CancellationTokenSource(timeoutMilliseconds);
try
{
await Task.Run(() =>
{
lock (_lockObject)
{
if (_serialPort.IsOpen)
{
_serialPort.Write(data, 0, data.Length);
}
}
}, timeoutCts.Token);
return true;
}
catch (OperationCanceledException)
{
ErrorOccurred?.Invoke("发送操作超时");
return false;
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"异步发送失败: {ex.Message}");
return false;
}
}
private async void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
// 异步读取数据,提高响应速度
byte[] data = await ReadDataAsync();
if (data.Length > 0)
{
DataReceived?.Invoke(data);
}
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"数据接收异常: {ex.Message}");
}
}
// 异步读取数据 - 使用内存流优化
private async Task<byte[]> ReadDataAsync()
{
using (var ms = new MemoryStream())
{
try
{
int bytesRead;
do
{
bytesRead = await _serialPort.BaseStream.ReadAsync(_receiveBuffer, 0, _receiveBuffer.Length);
if (bytesRead > 0)
{
ms.Write(_receiveBuffer, 0, bytesRead);
}
} while (bytesRead > 0 && _serialPort.BytesToRead > 0);
return ms.ToArray();
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"读取数据失败: {ex.Message}");
return new byte[0];
}
}
}
private void SerialPort_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
{
ErrorOccurred?.Invoke($"串口错误: {e.EventType}");
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// 释放托管资源
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
_serialPort?.Close();
_serialPort?.Dispose();
}
// 释放非托管资源
_receiveBuffer = null;
_disposed = true;
}
}
}
}
C#using System.IO.Ports;
using System.Text;
using Timer = System.Windows.Forms.Timer;
namespace AppSerialPortBuffer
{
public partial class Form1 : Form
{
private OptimizedSerialPortHandler _serialHandler;
private Timer _statusTimer;
private long _bytesSent = 0;
private long _bytesReceived = 0;
public Form1()
{
InitializeComponent();
InitializeApplication();
}
private void InitializeApplication()
{
_serialHandler = new OptimizedSerialPortHandler();
_serialHandler.DataReceived += OnDataReceived;
_serialHandler.ErrorOccurred += OnErrorOccurred;
// 初始化端口列表
RefreshPortList();
// 初始化波特率
cmbBaudRate.Items.AddRange(new object[] { 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 });
cmbBaudRate.SelectedIndex = 4; // 115200
// 初始化校验位
foreach (Parity parity in Enum.GetValues(typeof(Parity)))
cmbParity.Items.Add(parity);
cmbParity.SelectedIndex = 0; // None
// 初始化数据位
cmbDataBits.Items.AddRange(new object[] { 7, 8 });
cmbDataBits.SelectedIndex = 1; // 8
// 初始化停止位
foreach (StopBits stopBits in Enum.GetValues(typeof(StopBits)))
cmbStopBits.Items.Add(stopBits);
cmbStopBits.SelectedIndex = 1; // One
btnConnect.Enabled = true;
btnDisconnect.Enabled = false;
// 状态定时器
_statusTimer = new Timer();
_statusTimer.Interval = 1000;
_statusTimer.Tick += StatusTimer_Tick;
_statusTimer.Start();
}
private void RefreshPortList()
{
cmbPort.Items.Clear();
cmbPort.Items.AddRange(SerialPort.GetPortNames());
if (cmbPort.Items.Count > 0)
cmbPort.SelectedIndex = 0;
}
private async void BtnConnect_Click(object sender, EventArgs e)
{
if (cmbPort.SelectedItem == null) return;
try
{
var config = new SerialPortConfig
{
PortName = cmbPort.SelectedItem.ToString(),
BaudRate = (int)cmbBaudRate.SelectedItem,
Parity = (Parity)cmbParity.SelectedItem,
DataBits = (int)cmbDataBits.SelectedItem,
StopBits = (StopBits)cmbStopBits.SelectedItem,
ReadBufferSize = (int)nudReadBuffer.Value,
WriteBufferSize = (int)nudWriteBuffer.Value,
ReadTimeout = (int)nudTimeout.Value,
WriteTimeout = (int)nudTimeout.Value
};
pgbConnection.Style = ProgressBarStyle.Marquee;
tsslStatus.Text = "正在连接...";
// 连接期间禁用连接按钮
btnConnect.Enabled = false;
bool success = await _serialHandler.ConnectAsync(config);
pgbConnection.Style = ProgressBarStyle.Continuous;
pgbConnection.Value = success ? 100 : 0;
if (success)
{
UpdateConnectionStatus(true);
tsslStatus.Text = "连接成功";
}
else
{
UpdateConnectionStatus(false);
tsslStatus.Text = "连接失败";
MessageBox.Show("连接失败,请检查端口设置!", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
UpdateConnectionStatus(false);
tsslStatus.Text = "连接失败";
MessageBox.Show("连接失败,请检查端口设置!", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void BtnDisconnect_Click(object sender, EventArgs e)
{
try
{
// 先禁用按钮,防止重复点击
btnDisconnect.Enabled = false;
_serialHandler.Disconnect();
// 强制更新UI状态
UpdateConnectionStatus(false);
tsslStatus.Text = "已断开连接";
}
catch (Exception ex)
{
MessageBox.Show($"断开连接时发生错误: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
// 即使出错也要更新状态
UpdateConnectionStatus(false);
}
}
private void UpdateConnectionStatus(bool isConnected)
{
if (isConnected)
{
lblStatus.Text = "状态: 已连接";
lblStatus.ForeColor = Color.FromArgb(76, 175, 80);
pnlConfig.Enabled = false;
grpSettings.Enabled = false;
btnConnect.Enabled = false;
btnDisconnect.Enabled = true;
pgbConnection.Value = 100;
}
else
{
lblStatus.Text = "状态: 未连接";
lblStatus.ForeColor = Color.FromArgb(244, 67, 54);
pnlConfig.Enabled = true;
grpSettings.Enabled = true;
btnConnect.Enabled = true;
btnDisconnect.Enabled = false;
pgbConnection.Value = 0;
}
}
private async void BtnSend_Click(object sender, EventArgs e)
{
if (!_serialHandler.IsConnected || string.IsNullOrEmpty(txtSend.Text))
return;
try
{
byte[] data = Encoding.UTF8.GetBytes(txtSend.Text);
if (chkAsync.Checked)
{
await _serialHandler.SendDataAsync(data);
}
else
{
_serialHandler.SendData(data);
}
_bytesSent += data.Length;
lblBytesSent.Text = $"发送: {_bytesSent} 字节";
rtbReceived.SelectionColor = Color.Blue;
rtbReceived.AppendText($"[{DateTime.Now:HH:mm:ss}] 发送: {txtSend.Text}\n");
rtbReceived.ScrollToCaret();
txtSend.Clear();
}
catch (Exception ex)
{
MessageBox.Show($"发送失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void BtnClear_Click(object sender, EventArgs e)
{
rtbReceived.Clear();
_bytesSent = 0;
_bytesReceived = 0;
lblBytesSent.Text = "发送: 0 字节";
lblBytesReceived.Text = "接收: 0 字节";
}
private void TxtSend_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
BtnSend_Click(sender, e);
e.Handled = true;
}
}
private void OnDataReceived(byte[] data)
{
if (InvokeRequired)
{
Invoke(new Action<byte[]>(OnDataReceived), data);
return;
}
_bytesReceived += data.Length;
lblBytesReceived.Text = $"接收: {_bytesReceived} 字节";
string text = Encoding.UTF8.GetString(data);
rtbReceived.SelectionColor = Color.Green;
rtbReceived.AppendText($"[{DateTime.Now:HH:mm:ss}] 接收: {text}\n");
rtbReceived.ScrollToCaret();
}
private void OnErrorOccurred(string error)
{
if (InvokeRequired)
{
Invoke(new Action<string>(OnErrorOccurred), error);
return;
}
rtbReceived.SelectionColor = Color.Red;
rtbReceived.AppendText($"[{DateTime.Now:HH:mm:ss}] 错误: {error}\n");
rtbReceived.ScrollToCaret();
}
private void StatusTimer_Tick(object sender, EventArgs e)
{
tsslTime.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
_serialHandler?.Disconnect();
_statusTimer?.Stop();
base.OnFormClosing(e);
}
}
}

C#// ❌ 错误:忘记释放资源
~OptimizedSerialPortHandler()
{
// 析构函数中不应该访问托管资源
_serialPort?.Close(); // 可能引发异常
}
// ✅ 正确:标准Dispose模式
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed && disposing)
{
_cancellationTokenSource?.Cancel();
_serialPort?.Close();
_serialPort?.Dispose();
}
_disposed = true;
}
C#// ❌ 错误:直接在数据事件中更新UI
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
textBox1.Text = serialPort.ReadExisting(); // 跨线程异常
}
// ✅ 正确:检查并使用Invoke
private void OnDataReceived(byte[] data)
{
if (InvokeRequired)
{
Invoke(new Action<byte[]>(OnDataReceived), data);
return;
}
// 安全更新UI
}
C#// ❌ 错误:只检查IsOpen
if (serialPort.IsOpen)
{
serialPort.Write(data); // 可能在写入前断开
}
// ✅ 正确:双重检查 + 异常处理
public bool IsConnected => _isConnected && (_serialPort?.IsOpen ?? false);
if (IsConnected)
{
try
{
lock (_lockObject)
{
if (_serialPort.IsOpen)
{
_serialPort.Write(data, 0, data.Length);
}
}
}
catch (Exception ex)
{
// 处理异常并更新状态
}
}
C#// 根据数据特点调整缓冲区大小
public SerialPortConfig GetOptimalConfig(DataPattern pattern)
{
return pattern switch
{
DataPattern.HighFrequency => new() { ReadBufferSize = 8192 },
DataPattern.LargePacket => new() { ReadBufferSize = 16384 },
DataPattern.Normal => new() { ReadBufferSize = 4096 },
_ => new()
};
}
MemoryStream 代替字符串拼接通过本文的完整方案,我们解决了C#串口通信的三大核心问题:
这套解决方案已在多个工业项目中验证,具备良好的稳定性和扩展性。无论是设备调试还是生产环境,都能提供可靠的通信保障。
技术成长建议:
💬 互动话题:
觉得这篇技术总结对你有帮助吗?请转发给更多需要的同行,让我们一起提升C#开发技能!
🔖 收藏这篇文章,随时查阅这套完整的串口通信解决方案!
相关信息
通过网盘分享的文件:AppSerialPortBuffer.zip 链接: https://pan.baidu.com/s/16h1DnPRJllKjwvfUV9O6jA?pwd=nw5x 提取码: nw5x --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!