C# 串口通信完整教程 (.NET Framework 4.0)
适用于 Visual Studio 2010 / .NET Framework 4.0
目录
基础概念
什么是串口通信
串口通信(Serial Communication)是一种串行数据传输方式,数据按位顺序传输。在工业控制、设备通信等领域广泛应用。
引用命名空间
csharp
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Collections.Generic;
SerialPort类详解
创建串口对象
csharp
// 方法1:无参构造
SerialPort serialPort = new SerialPort();
// 方法2:指定端口名
SerialPort serialPort = new SerialPort("COM1");
// 方法3:完整参数
SerialPort serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
重要属性配置
csharp
public class SerialPortConfig
{
public void ConfigurePort(SerialPort port)
{
// 端口名称(COM1, COM2 等)
port.PortName = "COM1";
// 波特率(常用:9600, 19200, 38400, 57600, 115200)
port.BaudRate = 9600;
// 数据位(5-8,常用8)
port.DataBits = 8;
// 停止位
port.StopBits = StopBits.One; // None=0, One=1, Two=2, OnePointFive=1.5
// 奇偶校验
port.Parity = Parity.None; // None, Odd, Even, Mark, Space
// 握手协议
port.Handshake = Handshake.None; // None, XOnXOff, RequestToSend, RequestToSendXOnXOff
// 读取超时(毫秒,-1表示无限等待)
port.ReadTimeout = 500;
// 写入超时(毫秒)
port.WriteTimeout = 500;
// 接收缓冲区大小(字节)
port.ReadBufferSize = 4096;
// 发送缓冲区大小(字节)
port.WriteBufferSize = 2048;
// 编码格式
port.Encoding = Encoding.UTF8;
// RTS(请求发送)信号
port.RtsEnable = false;
// DTR(数据终端就绪)信号
port.DtrEnable = false;
}
}
获取可用串口
csharp
public static string[] GetAvailablePorts()
{
return SerialPort.GetPortNames();
}
// 使用示例
string[] ports = GetAvailablePorts();
foreach (string port in ports)
{
Console.WriteLine("可用串口: " + port);
}
多线程串口通信
为什么需要多线程
- 避免UI阻塞:串口读取可能等待数据,阻塞主线程会导致界面卡死
- 异步处理:发送和接收可以并行进行
- 数据缓冲:使用队列缓存待发送数据
基于事件的接收(推荐方式)
csharp
public class EventBasedSerialPort
{
private SerialPort serialPort;
private object lockObject = new object();
public EventBasedSerialPort(string portName, int baudRate)
{
serialPort = new SerialPort(portName, baudRate);
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
}
// 数据接收事件处理(运行在独立线程)
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
// 检查可用字节数
int bytesToRead = sp.BytesToRead;
if (bytesToRead > 0)
{
byte[] buffer = new byte[bytesToRead];
// 读取数据
int bytesRead = sp.Read(buffer, 0, bytesToRead);
// 处理接收到的数据
ProcessReceivedData(buffer, bytesRead);
}
}
catch (TimeoutException ex)
{
Console.WriteLine("读取超时: " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine("接收数据异常: " + ex.Message);
}
}
private void ProcessReceivedData(byte[] data, int length)
{
// 转换为十六进制字符串显示
string hex = ByteArrayToHexString(data, length);
Console.WriteLine("接收到数据(HEX): " + hex);
// 转换为ASCII字符串
string text = Encoding.ASCII.GetString(data, 0, length);
Console.WriteLine("接收到数据(ASCII): " + text);
}
public void Open()
{
if (!serialPort.IsOpen)
{
serialPort.Open();
}
}
public void Close()
{
if (serialPort.IsOpen)
{
serialPort.Close();
}
}
private string ByteArrayToHexString(byte[] data, int length)
{
StringBuilder sb = new StringBuilder(length * 3);
for (int i = 0; i < length; i++)
{
sb.Append(data[i].ToString("X2"));
if (i < length - 1)
sb.Append(" ");
}
return sb.ToString();
}
}
基于线程池的发送队列
csharp
public class ThreadPoolSerialPort
{
private SerialPort serialPort;
private Queue<byte[]> sendQueue;
private object queueLock = new object();
private bool isProcessing = false;
public ThreadPoolSerialPort(string portName, int baudRate)
{
serialPort = new SerialPort(portName, baudRate);
serialPort.WriteTimeout = 1000;
sendQueue = new Queue<byte[]>();
}
public void Open()
{
if (!serialPort.IsOpen)
{
serialPort.Open();
}
}
public void Close()
{
if (serialPort.IsOpen)
{
serialPort.Close();
}
}
// 添加数据到发送队列
public void QueueSend(byte[] data)
{
lock (queueLock)
{
sendQueue.Enqueue(data);
// 如果没有正在处理的任务,启动处理
if (!isProcessing)
{
isProcessing = true;
ThreadPool.QueueUserWorkItem(ProcessSendQueue);
}
}
}
// 线程池处理发送队列
private void ProcessSendQueue(object state)
{
while (true)
{
byte[] dataToSend = null;
lock (queueLock)
{
if (sendQueue.Count > 0)
{
dataToSend = sendQueue.Dequeue();
}
else
{
isProcessing = false;
break;
}
}
if (dataToSend != null)
{
SendData(dataToSend);
}
}
}
// 实际发送数据
private void SendData(byte[] data)
{
try
{
if (serialPort.IsOpen)
{
serialPort.Write(data, 0, data.Length);
Console.WriteLine("发送成功: " + data.Length + " 字节");
}
else
{
Console.WriteLine("串口未打开");
}
}
catch (TimeoutException)
{
Console.WriteLine("发送超时");
}
catch (Exception ex)
{
Console.WriteLine("发送异常: " + ex.Message);
}
}
}
独立接收线程(轮询方式)
csharp
public class PollingSerialPort
{
private SerialPort serialPort;
private Thread receiveThread;
private bool isRunning = false;
public PollingSerialPort(string portName, int baudRate)
{
serialPort = new SerialPort(portName, baudRate);
serialPort.ReadTimeout = 100; // 短超时避免长时间阻塞
}
public void Open()
{
if (!serialPort.IsOpen)
{
serialPort.Open();
StartReceiveThread();
}
}
public void Close()
{
StopReceiveThread();
if (serialPort.IsOpen)
{
serialPort.Close();
}
}
private void StartReceiveThread()
{
isRunning = true;
receiveThread = new Thread(ReceiveThreadFunction);
receiveThread.IsBackground = true; // 设置为后台线程
receiveThread.Start();
}
private void StopReceiveThread()
{
isRunning = false;
if (receiveThread != null && receiveThread.IsAlive)
{
receiveThread.Join(1000); // 等待线程结束,最多1秒
}
}
// 接收线程函数
private void ReceiveThreadFunction()
{
byte[] buffer = new byte[1024];
while (isRunning)
{
try
{
if (serialPort.IsOpen && serialPort.BytesToRead > 0)
{
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
// 创建实际大小的数组
byte[] data = new byte[bytesRead];
Array.Copy(buffer, 0, data, 0, bytesRead);
// 处理数据
OnDataReceived(data);
}
}
else
{
Thread.Sleep(10); // 短暂休眠避免空轮询占用CPU
}
}
catch (TimeoutException)
{
// 读取超时是正常的,继续循环
}
catch (Exception ex)
{
Console.WriteLine("接收线程异常: " + ex.Message);
Thread.Sleep(100); // 发生异常后短暂休眠
}
}
}
// 数据接收事件(可以改为事件模式)
protected virtual void OnDataReceived(byte[] data)
{
Console.WriteLine("接收到 " + data.Length + " 字节");
}
}
超时处理机制
读取超时
csharp
public class TimeoutHandling
{
private SerialPort serialPort;
public TimeoutHandling(SerialPort port)
{
this.serialPort = port;
}
// 带超时的读取字符串
public string ReadLineWithTimeout(int timeoutMs)
{
serialPort.ReadTimeout = timeoutMs;
try
{
return serialPort.ReadLine();
}
catch (TimeoutException)
{
Console.WriteLine("读取超时");
return null;
}
}
// 带超时和重试的读取
public byte[] ReadBytesWithRetry(int bytesToRead, int timeoutMs, int retryCount)
{
serialPort.ReadTimeout = timeoutMs;
for (int i = 0; i < retryCount; i++)
{
try
{
byte[] buffer = new byte[bytesToRead];
int offset = 0;
while (offset < bytesToRead)
{
int read = serialPort.Read(buffer, offset, bytesToRead - offset);
offset += read;
}
return buffer;
}
catch (TimeoutException)
{
Console.WriteLine("读取超时,重试 " + (i + 1) + "/" + retryCount);
if (i == retryCount - 1)
{
throw;
}
Thread.Sleep(100); // 重试前短暂等待
}
}
return null;
}
// 带超时的等待指定数据量
public byte[] WaitForBytes(int expectedBytes, int totalTimeoutMs)
{
int startTime = Environment.TickCount;
List<byte> receivedData = new List<byte>();
while (Environment.TickCount - startTime < totalTimeoutMs)
{
if (serialPort.BytesToRead > 0)
{
byte[] buffer = new byte[serialPort.BytesToRead];
int read = serialPort.Read(buffer, 0, buffer.Length);
for (int i = 0; i < read; i++)
{
receivedData.Add(buffer[i]);
}
if (receivedData.Count >= expectedBytes)
{
return receivedData.ToArray();
}
}
Thread.Sleep(10);
}
throw new TimeoutException(string.Format(
"等待超时,期望 {0} 字节,实际接收 {1} 字节",
expectedBytes, receivedData.Count));
}
// 带超时的命令应答模式
public byte[] SendAndWaitResponse(byte[] command, int responseLength, int timeoutMs)
{
// 清空接收缓冲区
serialPort.DiscardInBuffer();
// 发送命令
serialPort.Write(command, 0, command.Length);
// 等待应答
return WaitForBytes(responseLength, timeoutMs);
}
}
写入超时
csharp
public class WriteTimeoutHandling
{
private SerialPort serialPort;
public void SafeWrite(byte[] data, int timeoutMs)
{
serialPort.WriteTimeout = timeoutMs;
try
{
serialPort.Write(data, 0, data.Length);
Console.WriteLine("写入成功");
}
catch (TimeoutException)
{
Console.WriteLine("写入超时,可能发送缓冲区已满");
// 尝试清空发送缓冲区
serialPort.DiscardOutBuffer();
}
catch (InvalidOperationException ex)
{
Console.WriteLine("串口未打开: " + ex.Message);
}
}
}
异常处理最佳实践
常见异常类型
csharp
public class ExceptionHandling
{
private SerialPort serialPort;
public void ComprehensiveExceptionHandling()
{
try
{
// 打开串口
if (!serialPort.IsOpen)
{
serialPort.Open();
}
// 执行操作
byte[] data = new byte[] { 0x01, 0x02, 0x03 };
serialPort.Write(data, 0, data.Length);
}
catch (UnauthorizedAccessException ex)
{
// 串口被其他程序占用
Console.WriteLine("串口访问被拒绝,可能被其他程序占用: " + ex.Message);
// 建议:提示用户关闭占用串口的程序
}
catch (ArgumentException ex)
{
// 参数错误,如端口名无效
Console.WriteLine("参数错误: " + ex.Message);
// 建议:检查端口名称是否正确
}
catch (IOException ex)
{
// IO错误,如设备被移除
Console.WriteLine("IO错误,设备可能已断开: " + ex.Message);
// 建议:尝试重新连接或提示用户检查设备
}
catch (InvalidOperationException ex)
{
// 操作无效,如端口未打开就进行读写
Console.WriteLine("操作无效: " + ex.Message);
// 建议:确保操作前串口已正确打开
}
catch (TimeoutException ex)
{
// 超时异常
Console.WriteLine("操作超时: " + ex.Message);
// 建议:增加超时时间或检查设备响应
}
catch (Exception ex)
{
// 其他未预料的异常
Console.WriteLine("未知异常: " + ex.Message);
Console.WriteLine("堆栈跟踪: " + ex.StackTrace);
}
finally
{
// 确保资源释放
if (serialPort != null && serialPort.IsOpen)
{
try
{
serialPort.Close();
}
catch
{
// 关闭时的异常不再抛出
}
}
}
}
// 安全的串口打开
public bool SafeOpen(string portName, int baudRate, int retryCount)
{
for (int i = 0; i < retryCount; i++)
{
try
{
if (serialPort == null)
{
serialPort = new SerialPort(portName, baudRate);
}
if (!serialPort.IsOpen)
{
serialPort.Open();
Console.WriteLine("串口打开成功");
return true;
}
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("串口被占用,尝试 " + (i + 1) + "/" + retryCount);
Thread.Sleep(1000);
}
catch (Exception ex)
{
Console.WriteLine("打开失败: " + ex.Message);
return false;
}
}
return false;
}
// 安全的串口关闭
public void SafeClose()
{
try
{
if (serialPort != null)
{
if (serialPort.IsOpen)
{
// 先停止接收
serialPort.DataReceived -= DataReceivedHandler;
// 清空缓冲区
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
// 关闭串口
serialPort.Close();
}
// 释放资源
serialPort.Dispose();
serialPort = null;
}
}
catch (Exception ex)
{
Console.WriteLine("关闭串口时发生异常: " + ex.Message);
}
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
// 数据接收处理
}
}
线程安全的异常处理
csharp
public class ThreadSafeExceptionHandling
{
private SerialPort serialPort;
private object lockObject = new object();
private bool isDisposed = false;
public void ThreadSafeWrite(byte[] data)
{
lock (lockObject)
{
if (isDisposed)
{
throw new ObjectDisposedException("SerialPort已被释放");
}
try
{
if (serialPort.IsOpen)
{
serialPort.Write(data, 0, data.Length);
}
}
catch (Exception ex)
{
HandleException(ex);
}
}
}
private void HandleException(Exception ex)
{
// 集中的异常处理逻辑
if (ex is TimeoutException)
{
Console.WriteLine("操作超时");
}
else if (ex is IOException)
{
Console.WriteLine("IO错误,可能设备已断开");
// 触发重连机制
TryReconnect();
}
else
{
Console.WriteLine("异常: " + ex.Message);
}
}
private void TryReconnect()
{
// 重连逻辑
}
public void Dispose()
{
lock (lockObject)
{
if (!isDisposed)
{
if (serialPort != null)
{
if (serialPort.IsOpen)
{
serialPort.Close();
}
serialPort.Dispose();
}
isDisposed = true;
}
}
}
}
数据转换
byte数组与字符串互转
csharp
public class DataConversion
{
// 1. ASCII编码转换
public static string BytesToASCII(byte[] data)
{
return Encoding.ASCII.GetString(data);
}
public static byte[] ASCIIToBytes(string text)
{
return Encoding.ASCII.GetBytes(text);
}
// 2. UTF-8编码转换
public static string BytesToUTF8(byte[] data)
{
return Encoding.UTF8.GetString(data);
}
public static byte[] UTF8ToBytes(string text)
{
return Encoding.UTF8.GetBytes(text);
}
// 3. GB2312编码转换(中文)
public static string BytesToGB2312(byte[] data)
{
return Encoding.GetEncoding("GB2312").GetString(data);
}
public static byte[] GB2312ToBytes(string text)
{
return Encoding.GetEncoding("GB2312").GetBytes(text);
}
// 4. 十六进制字符串转换
public static string BytesToHexString(byte[] data)
{
StringBuilder sb = new StringBuilder(data.Length * 2);
foreach (byte b in data)
{
sb.Append(b.ToString("X2"));
}
return sb.ToString();
}
public static byte[] HexStringToBytes(string hex)
{
// 移除空格和其他分隔符
hex = hex.Replace(" ", "").Replace("-", "").Replace(":", "");
if (hex.Length % 2 != 0)
{
throw new ArgumentException("十六进制字符串长度必须是偶数");
}
byte[] bytes = new byte[hex.Length / 2];
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return bytes;
}
// 5. 十六进制字符串(带空格)
public static string BytesToHexStringWithSpace(byte[] data)
{
StringBuilder sb = new StringBuilder(data.Length * 3);
for (int i = 0; i < data.Length; i++)
{
sb.Append(data[i].ToString("X2"));
if (i < data.Length - 1)
{
sb.Append(" ");
}
}
return sb.ToString();
}
// 6. 整数与byte数组转换(大端序)
public static byte[] IntToBigEndianBytes(int value)
{
byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return bytes;
}
public static int BigEndianBytesToInt(byte[] bytes, int startIndex)
{
if (bytes.Length - startIndex < 4)
{
throw new ArgumentException("字节数不足");
}
byte[] temp = new byte[4];
Array.Copy(bytes, startIndex, temp, 0, 4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(temp);
}
return BitConverter.ToInt32(temp, 0);
}
// 7. 整数与byte数组转换(小端序)
public static byte[] IntToLittleEndianBytes(int value)
{
return BitConverter.GetBytes(value);
}
public static int LittleEndianBytesToInt(byte[] bytes, int startIndex)
{
return BitConverter.ToInt32(bytes, startIndex);
}
// 8. 短整数转换
public static byte[] ShortToBytes(short value, bool bigEndian)
{
byte[] bytes = BitConverter.GetBytes(value);
if (bigEndian && BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return bytes;
}
public static short BytesToShort(byte[] bytes, int startIndex, bool bigEndian)
{
byte[] temp = new byte[2];
Array.Copy(bytes, startIndex, temp, 0, 2);
if (bigEndian && BitConverter.IsLittleEndian)
{
Array.Reverse(temp);
}
return BitConverter.ToInt16(temp, 0);
}
// 9. 浮点数转换
public static byte[] FloatToBytes(float value, bool bigEndian)
{
byte[] bytes = BitConverter.GetBytes(value);
if (bigEndian && BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return bytes;
}
public static float BytesToFloat(byte[] bytes, int startIndex, bool bigEndian)
{
byte[] temp = new byte[4];
Array.Copy(bytes, startIndex, temp, 0, 4);
if (bigEndian && BitConverter.IsLittleEndian)
{
Array.Reverse(temp);
}
return BitConverter.ToSingle(temp, 0);
}
// 10. BCD码转换(常用于仪表通信)
public static byte DecimalToBCD(int value)
{
if (value < 0 || value > 99)
{
throw new ArgumentException("BCD值必须在0-99之间");
}
int high = value / 10;
int low = value % 10;
return (byte)((high << 4) | low);
}
public static int BCDToDecimal(byte bcd)
{
int high = (bcd >> 4) & 0x0F;
int low = bcd & 0x0F;
return high * 10 + low;
}
}
使用示例
csharp
public class DataConversionExample
{
public void Examples()
{
// ASCII转换
string text = "Hello";
byte[] ascii = DataConversion.ASCIIToBytes(text);
string back = DataConversion.BytesToASCII(ascii);
Console.WriteLine("ASCII: " + back);
// 十六进制转换
byte[] data = new byte[] { 0x01, 0x02, 0xAB, 0xCD };
string hex = DataConversion.BytesToHexString(data);
Console.WriteLine("HEX: " + hex); // 输出: 0102ABCD
byte[] restored = DataConversion.HexStringToBytes("01 02 AB CD");
// 整数转换
int number = 0x12345678;
byte[] bigEndian = DataConversion.IntToBigEndianBytes(number);
Console.WriteLine("大端: " + DataConversion.BytesToHexString(bigEndian));
// BCD转换
byte bcd = DataConversion.DecimalToBCD(59);
Console.WriteLine("BCD: 0x" + bcd.ToString("X2")); // 输出: 0x59
int dec = DataConversion.BCDToDecimal(bcd);
Console.WriteLine("Decimal: " + dec); // 输出: 59
}
}
多进程通信问题
串口独占性问题
串口是独占资源,同一时间只能被一个进程打开。如果需要多进程访问串口,需要以下解决方案:
方案1:命名管道通信
csharp
// 服务进程(串口管理器)
public class SerialPortServer
{
private SerialPort serialPort;
// 注意:.NET 4.0 不支持 NamedPipeServerStream
// 需要使用其他IPC机制,如Windows消息、TCP Socket或WCF
}
方案2:共享内存(推荐.NET 4.0)
csharp
using System.IO.MemoryMappedFiles; // .NET 4.0支持
public class SharedMemorySerialPort
{
private const string MAP_NAME = "SerialPortSharedMemory";
private const int BUFFER_SIZE = 4096;
// 写入共享内存
public void WriteToSharedMemory(byte[] data)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(MAP_NAME, BUFFER_SIZE))
{
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
// 写入数据长度
accessor.Write(0, data.Length);
// 写入数据
accessor.WriteArray(4, data, 0, data.Length);
}
}
}
// 从共享内存读取
public byte[] ReadFromSharedMemory()
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(MAP_NAME))
{
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor())
{
// 读取数据长度
int length = accessor.ReadInt32(0);
if (length > 0 && length < BUFFER_SIZE)
{
byte[] data = new byte[length];
accessor.ReadArray(4, data, 0, length);
return data;
}
}
}
return null;
}
}
方案3:TCP Socket服务器(最灵活)
csharp
using System.Net;
using System.Net.Sockets;
// 串口服务器进程
public class SerialPortTcpServer
{
private SerialPort serialPort;
private TcpListener tcpListener;
private List<TcpClient> clients;
private object clientsLock = new object();
public SerialPortTcpServer(string portName, int baudRate, int tcpPort)
{
serialPort = new SerialPort(portName, baudRate);
serialPort.DataReceived += SerialPort_DataReceived;
tcpListener = new TcpListener(IPAddress.Loopback, tcpPort);
clients = new List<TcpClient>();
}
public void Start()
{
// 打开串口
serialPort.Open();
// 启动TCP监听
tcpListener.Start();
// 异步接受客户端
tcpListener.BeginAcceptTcpClient(AcceptCallback, null);
Console.WriteLine("串口服务器已启动");
}
private void AcceptCallback(IAsyncResult ar)
{
try
{
TcpClient client = tcpListener.EndAcceptTcpClient(ar);
lock (clientsLock)
{
clients.Add(client);
}
Console.WriteLine("客户端已连接");
// 继续接受下一个客户端
tcpListener.BeginAcceptTcpClient(AcceptCallback, null);
// 处理客户端请求
Thread clientThread = new Thread(HandleClient);
clientThread.IsBackground = true;
clientThread.Start(client);
}
catch (Exception ex)
{
Console.WriteLine("接受客户端异常: " + ex.Message);
}
}
private void HandleClient(object obj)
{
TcpClient client = (TcpClient)obj;
NetworkStream stream = client.GetStream();
byte[] buffer = new byte[1024];
try
{
while (client.Connected)
{
if (stream.DataAvailable)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
byte[] data = new byte[bytesRead];
Array.Copy(buffer, 0, data, 0, bytesRead);
// 转发到串口
serialPort.Write(data, 0, data.Length);
}
}
Thread.Sleep(10);
}
}
catch (Exception ex)
{
Console.WriteLine("客户端处理异常: " + ex.Message);
}
finally
{
lock (clientsLock)
{
clients.Remove(client);
}
client.Close();
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
int bytesToRead = serialPort.BytesToRead;
if (bytesToRead > 0)
{
byte[] buffer = new byte[bytesToRead];
int bytesRead = serialPort.Read(buffer, 0, bytesToRead);
// 广播给所有客户端
BroadcastToClients(buffer, bytesRead);
}
}
catch (Exception ex)
{
Console.WriteLine("串口接收异常: " + ex.Message);
}
}
private void BroadcastToClients(byte[] data, int length)
{
lock (clientsLock)
{
foreach (TcpClient client in clients.ToArray())
{
try
{
if (client.Connected)
{
NetworkStream stream = client.GetStream();
stream.Write(data, 0, length);
}
}
catch
{
// 客户端断开,忽略
}
}
}
}
public void Stop()
{
serialPort.Close();
tcpListener.Stop();
lock (clientsLock)
{
foreach (TcpClient client in clients)
{
client.Close();
}
clients.Clear();
}
}
}
// 客户端进程
public class SerialPortTcpClient
{
private TcpClient tcpClient;
private NetworkStream stream;
public void Connect(string serverIp, int port)
{
tcpClient = new TcpClient();
tcpClient.Connect(serverIp, port);
stream = tcpClient.GetStream();
// 启动接收线程
Thread receiveThread = new Thread(ReceiveData);
receiveThread.IsBackground = true;
receiveThread.Start();
}
public void SendData(byte[] data)
{
if (tcpClient.Connected)
{
stream.Write(data, 0, data.Length);
}
}
private void ReceiveData()
{
byte[] buffer = new byte[1024];
while (tcpClient.Connected)
{
try
{
if (stream.DataAvailable)
{
int bytesRead = stream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
byte[] data = new byte[bytesRead];
Array.Copy(buffer, 0, data, 0, bytesRead);
// 处理接收到的数据
OnDataReceived(data);
}
}
Thread.Sleep(10);
}
catch (Exception ex)
{
Console.WriteLine("接收异常: " + ex.Message);
break;
}
}
}
protected virtual void OnDataReceived(byte[] data)
{
Console.WriteLine("接收到数据: " + DataConversion.BytesToHexString(data));
}
public void Disconnect()
{
if (tcpClient != null)
{
tcpClient.Close();
}
}
}
完整示例代码
完整的串口通信类
csharp
using System;
using System.IO.Ports;
using System.Text;
using System.Threading;
using System.Collections.Generic;
public class AdvancedSerialPort : IDisposable
{
// 串口对象
private SerialPort serialPort;
// 线程控制
private Thread receiveThread;
private bool isRunning;
// 发送队列
private Queue<byte[]> sendQueue;
private object queueLock = new object();
private AutoResetEvent sendEvent;
// 数据缓冲
private List<byte> receiveBuffer;
private object bufferLock = new object();
// 事件
public event EventHandler<byte[]> DataReceived;
public event EventHandler<string> ErrorOccurred;
public event EventHandler Connected;
public event EventHandler Disconnected;
// 属性
public bool IsOpen
{
get { return serialPort != null && serialPort.IsOpen; }
}
public AdvancedSerialPort()
{
receiveBuffer = new List<byte>();
sendQueue = new Queue<byte[]>();
sendEvent = new AutoResetEvent(false);
}
// 配置并打开串口
public bool Open(string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits)
{
try
{
// 创建串口对象
serialPort = new SerialPort();
serialPort.PortName = portName;
serialPort.BaudRate = baudRate;
serialPort.DataBits = dataBits;
serialPort.Parity = parity;
serialPort.StopBits = stopBits;
serialPort.ReadTimeout = 500;
serialPort.WriteTimeout = 500;
serialPort.ReadBufferSize = 4096;
serialPort.WriteBufferSize = 2048;
// 打开串口
serialPort.Open();
// 启动线程
StartThreads();
// 触发连接事件
OnConnected();
return true;
}
catch (Exception ex)
{
OnError("打开串口失败: " + ex.Message);
return false;
}
}
// 关闭串口
public void Close()
{
try
{
// 停止线程
StopThreads();
// 关闭串口
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
// 触发断开事件
OnDisconnected();
}
catch (Exception ex)
{
OnError("关闭串口失败: " + ex.Message);
}
}
// 发送数据(加入队列)
public void SendAsync(byte[] data)
{
lock (queueLock)
{
sendQueue.Enqueue(data);
sendEvent.Set(); // 通知发送线程
}
}
// 同步发送数据
public bool SendSync(byte[] data, int timeoutMs)
{
try
{
if (serialPort.IsOpen)
{
serialPort.WriteTimeout = timeoutMs;
serialPort.Write(data, 0, data.Length);
return true;
}
}
catch (TimeoutException)
{
OnError("发送超时");
}
catch (Exception ex)
{
OnError("发送失败: " + ex.Message);
}
return false;
}
// 发送字符串
public void SendString(string text, Encoding encoding)
{
byte[] data = encoding.GetBytes(text);
SendAsync(data);
}
// 发送十六进制字符串
public void SendHexString(string hexString)
{
try
{
byte[] data = DataConversion.HexStringToBytes(hexString);
SendAsync(data);
}
catch (Exception ex)
{
OnError("发送十六进制失败: " + ex.Message);
}
}
// 清空缓冲区
public void ClearBuffers()
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
}
lock (bufferLock)
{
receiveBuffer.Clear();
}
}
// 启动线程
private void StartThreads()
{
isRunning = true;
// 接收线程
receiveThread = new Thread(ReceiveThreadFunction);
receiveThread.IsBackground = true;
receiveThread.Start();
// 发送线程
Thread sendThread = new Thread(SendThreadFunction);
sendThread.IsBackground = true;
sendThread.Start();
}
// 停止线程
private void StopThreads()
{
isRunning = false;
sendEvent.Set(); // 唤醒发送线程以便退出
if (receiveThread != null && receiveThread.IsAlive)
{
receiveThread.Join(1000);
}
}
// 接收线程
private void ReceiveThreadFunction()
{
byte[] buffer = new byte[1024];
while (isRunning)
{
try
{
if (serialPort.IsOpen && serialPort.BytesToRead > 0)
{
int bytesRead = serialPort.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
byte[] data = new byte[bytesRead];
Array.Copy(buffer, 0, data, 0, bytesRead);
// 添加到缓冲区
lock (bufferLock)
{
receiveBuffer.AddRange(data);
}
// 触发事件
OnDataReceived(data);
}
}
else
{
Thread.Sleep(10);
}
}
catch (TimeoutException)
{
// 超时正常,继续
}
catch (Exception ex)
{
OnError("接收异常: " + ex.Message);
Thread.Sleep(100);
}
}
}
// 发送线程
private void SendThreadFunction()
{
while (isRunning)
{
try
{
// 等待信号
sendEvent.WaitOne(100);
// 处理队列
while (true)
{
byte[] data = null;
lock (queueLock)
{
if (sendQueue.Count > 0)
{
data = sendQueue.Dequeue();
}
else
{
break;
}
}
if (data != null)
{
SendSync(data, 1000);
}
}
}
catch (Exception ex)
{
OnError("发送线程异常: " + ex.Message);
}
}
}
// 事件触发
protected virtual void OnDataReceived(byte[] data)
{
if (DataReceived != null)
{
DataReceived(this, data);
}
}
protected virtual void OnError(string message)
{
if (ErrorOccurred != null)
{
ErrorOccurred(this, message);
}
}
protected virtual void OnConnected()
{
if (Connected != null)
{
Connected(this, EventArgs.Empty);
}
}
protected virtual void OnDisconnected()
{
if (Disconnected != null)
{
Disconnected(this, EventArgs.Empty);
}
}
// 释放资源
public void Dispose()
{
Close();
if (serialPort != null)
{
serialPort.Dispose();
serialPort = null;
}
if (sendEvent != null)
{
sendEvent.Close();
}
}
}
使用示例
csharp
public class Program
{
private static AdvancedSerialPort serialPort;
static void Main(string[] args)
{
// 创建串口对象
serialPort = new AdvancedSerialPort();
// 注册事件
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.ErrorOccurred += SerialPort_ErrorOccurred;
serialPort.Connected += SerialPort_Connected;
serialPort.Disconnected += SerialPort_Disconnected;
// 打开串口
if (serialPort.Open("COM1", 9600, 8, Parity.None, StopBits.One))
{
Console.WriteLine("串口打开成功");
// 发送测试数据
serialPort.SendString("Hello World\r\n", Encoding.ASCII);
serialPort.SendHexString("01 02 03 04");
// 等待用户输入
Console.WriteLine("按任意键关闭...");
Console.ReadKey();
}
else
{
Console.WriteLine("串口打开失败");
}
// 关闭串口
serialPort.Close();
serialPort.Dispose();
}
private static void SerialPort_DataReceived(object sender, byte[] data)
{
string hex = DataConversion.BytesToHexStringWithSpace(data);
string ascii = Encoding.ASCII.GetString(data);
Console.WriteLine("接收: HEX=" + hex);
Console.WriteLine(" ASCII=" + ascii);
}
private static void SerialPort_ErrorOccurred(object sender, string message)
{
Console.WriteLine("错误: " + message);
}
private static void SerialPort_Connected(object sender, EventArgs e)
{
Console.WriteLine("已连接");
}
private static void SerialPort_Disconnected(object sender, EventArgs e)
{
Console.WriteLine("已断开");
}
}
常见问题解决
1. 串口被占用
csharp
// 检测串口是否被占用
public static bool IsPortAvailable(string portName)
{
try
{
SerialPort port = new SerialPort(portName);
port.Open();
port.Close();
return true;
}
catch (UnauthorizedAccessException)
{
return false; // 被占用
}
catch
{
return false; // 其他错误
}
}
2. 数据粘包问题
csharp
public class PacketParser
{
private List<byte> buffer = new List<byte>();
// 假设协议:头(0xAA 0x55) + 长度(1字节) + 数据 + 校验(1字节)
public List<byte[]> ParsePackets(byte[] newData)
{
List<byte[]> packets = new List<byte[]>();
// 添加新数据到缓冲区
buffer.AddRange(newData);
while (buffer.Count >= 4) // 最小包长度
{
// 查找包头
int headerIndex = -1;
for (int i = 0; i < buffer.Count - 1; i++)
{
if (buffer[i] == 0xAA && buffer[i + 1] == 0x55)
{
headerIndex = i;
break;
}
}
if (headerIndex == -1)
{
// 没有找到包头,清空缓冲区
buffer.Clear();
break;
}
// 移除包头前的无效数据
if (headerIndex > 0)
{
buffer.RemoveRange(0, headerIndex);
}
// 检查是否有足够的数据
if (buffer.Count < 4)
{
break;
}
// 读取数据长度
int dataLength = buffer[2];
int packetLength = 4 + dataLength; // 头(2) + 长度(1) + 数据 + 校验(1)
if (buffer.Count < packetLength)
{
// 数据不完整,等待更多数据
break;
}
// 提取完整包
byte[] packet = buffer.GetRange(0, packetLength).ToArray();
// 验证校验和
if (ValidateChecksum(packet))
{
packets.Add(packet);
}
// 移除已处理的包
buffer.RemoveRange(0, packetLength);
}
return packets;
}
private bool ValidateChecksum(byte[] packet)
{
byte checksum = 0;
for (int i = 0; i < packet.Length - 1; i++)
{
checksum += packet[i];
}
return checksum == packet[packet.Length - 1];
}
}
3. 串口重连机制
csharp
public class AutoReconnectSerialPort
{
private AdvancedSerialPort serialPort;
private string portName;
private int baudRate;
private Timer reconnectTimer;
private bool autoReconnect = true;
public void EnableAutoReconnect(int intervalMs)
{
autoReconnect = true;
reconnectTimer = new Timer(ReconnectTimerCallback, null, intervalMs, intervalMs);
}
private void ReconnectTimerCallback(object state)
{
if (autoReconnect && !serialPort.IsOpen)
{
Console.WriteLine("尝试重新连接...");
if (serialPort.Open(portName, baudRate, 8, Parity.None, StopBits.One))
{
Console.WriteLine("重连成功");
}
}
}
}
4. 性能优化技巧
csharp
// 批量发送优化
public void SendBatch(List<byte[]> dataList)
{
// 合并多个小包为一个大包
int totalLength = 0;
foreach (byte[] data in dataList)
{
totalLength += data.Length;
}
byte[] combined = new byte[totalLength];
int offset = 0;
foreach (byte[] data in dataList)
{
Array.Copy(data, 0, combined, offset, data.Length);
offset += data.Length;
}
// 一次性发送
SendSync(combined, 2000);
}
5. 日志记录
csharp
public class SerialPortLogger
{
private string logFilePath;
private object logLock = new object();
public SerialPortLogger(string filePath)
{
logFilePath = filePath;
}
public void Log(string direction, byte[] data)
{
lock (logLock)
{
try
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
string hex = DataConversion.BytesToHexStringWithSpace(data);
string line = string.Format("{0} [{1}] {2}\r\n", timestamp, direction, hex);
System.IO.File.AppendAllText(logFilePath, line);
}
catch (Exception ex)
{
Console.WriteLine("日志写入失败: " + ex.Message);
}
}
}
}
总结
本教程涵盖了C#串口通信的核心内容:
- 基础配置:SerialPort类的各项参数设置
- 多线程处理:事件驱动、线程池、独立接收线程等多种方式
- 超时控制:读写超时、带重试机制、命令应答模式
- 异常处理:各类异常的捕获和处理策略
- 数据转换:多种编码格式、字节序、BCD码等转换
- 多进程方案:共享内存、TCP服务器等IPC机制
- 实战示例:完整可用的串口通信类
最佳实践建议
- 始终使用
try-catch
保护串口操作 - 使用事件方式接收数据,避免轮询
- 发送大量数据时使用队列机制
- 正确处理粘包问题
- 实现自动重连提高可靠性
- 记录通信日志便于调试
- 多进程访问需要特殊设计
注意事项
- .NET Framework 4.0 不支持
async/await
- 避免使用 C# 6.0+ 的新语法
- 串口操作要考虑线程安全
- 及时释放串口资源
- 超时值要根据实际设备调整