C# 串口通信完整教程 (.NET Framework 4.0)

C# 串口通信完整教程 (.NET Framework 4.0)

适用于 Visual Studio 2010 / .NET Framework 4.0

目录

  1. 基础概念
  2. SerialPort类详解
  3. 多线程串口通信
  4. 超时处理机制
  5. 异常处理最佳实践
  6. 数据转换
  7. 多进程通信问题
  8. 完整示例代码
  9. 常见问题解决

基础概念

什么是串口通信

串口通信(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);
}

多线程串口通信

为什么需要多线程

  1. 避免UI阻塞:串口读取可能等待数据,阻塞主线程会导致界面卡死
  2. 异步处理:发送和接收可以并行进行
  3. 数据缓冲:使用队列缓存待发送数据

基于事件的接收(推荐方式)

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#串口通信的核心内容:

  1. 基础配置:SerialPort类的各项参数设置
  2. 多线程处理:事件驱动、线程池、独立接收线程等多种方式
  3. 超时控制:读写超时、带重试机制、命令应答模式
  4. 异常处理:各类异常的捕获和处理策略
  5. 数据转换:多种编码格式、字节序、BCD码等转换
  6. 多进程方案:共享内存、TCP服务器等IPC机制
  7. 实战示例:完整可用的串口通信类

最佳实践建议

  • 始终使用 try-catch 保护串口操作
  • 使用事件方式接收数据,避免轮询
  • 发送大量数据时使用队列机制
  • 正确处理粘包问题
  • 实现自动重连提高可靠性
  • 记录通信日志便于调试
  • 多进程访问需要特殊设计

注意事项

  • .NET Framework 4.0 不支持 async/await
  • 避免使用 C# 6.0+ 的新语法
  • 串口操作要考虑线程安全
  • 及时释放串口资源
  • 超时值要根据实际设备调整
相关推荐
Akshsjsjenjd3 小时前
docker网络
网络·docker·容器
我笔记4 小时前
.net过滤器和缓存
c#
独行soc4 小时前
2025年渗透测试面试题总结-105(题目+回答)
网络·python·安全·web安全·adb·渗透测试·安全狮
卓码软件测评4 小时前
【第三方网站代码登记测试_HTTP头语法代码详解】
网络·网络协议·http·架构·web
奇树谦4 小时前
Fast DDS 默认传输机制详解:共享内存与 UDP 的智能选择
网络·网络协议·udp·fastdds
缘华工业智维10 小时前
工业设备预测性维护:能源成本降低的“隐藏钥匙”?
大数据·网络·人工智能
安当加密10 小时前
达梦数据库TDE透明加密解决方案:构建高安全数据存储体系
网络·数据库·安全
wuxuanok12 小时前
WebSocket —— 在线聊天室
网络·websocket·网络协议
夏子曦12 小时前
C#内存管理深度解析:从栈堆原理到高性能编程实践
开发语言·c#