
上位机(PC端)与下位机(如PLC、单片机、传感器等设备端)通信的核心在于数据按既定协议进行传输。C#作为.NET平台的主流开发语言,其丰富的类库为各种通信方式提供了强有力的支持。对于初学者,应优先掌握以下三种核心通信方式:串口(RS232/485)、TCP/IP(以太网)以及工业领域主流的Modbus协议。
一、常见通信方式的C#实现
1. 串口通信(RS232/485)
串口是最基础的设备通信方式。C#内置了System.IO.Ports.SerialPort类,无需借助第三方库即可实现完整功能。
完整代码示例(可直接运行):
using System;
using System.IO.Ports;
using System.Text;
namespace SerialPortCommunication
{
class SerialPortDemo
{
// 定义串口对象
private SerialPort _serialPort;
public SerialPortDemo()
{
// 初始化串口配置(需与设备参数保持一致)
_serialPort = new SerialPort
{
PortName = "COM3", // 串口名称(在设备管理器中查看)
BaudRate = 9600, // 波特率
Parity = Parity.None, // 校验位
DataBits = 8, // 数据位
StopBits = StopBits.One, // 停止位
ReadTimeout = 500, // 读取超时
WriteTimeout = 500 // 写入超时
};
// 注册数据接收事件(当串口有数据到达时触发)
_serialPort.DataReceived += SerialPort_DataReceived;
}
/// <summary>
/// 打开串口
/// </summary>
public bool OpenSerialPort()
{
try
{
if (!_serialPort.IsOpen)
{
_serialPort.Open();
Console.WriteLine("串口打开成功!");
return true;
}
Console.WriteLine("串口已打开!");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"打开失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 向下位机发送数据
/// </summary>
public void SendData(string sendData)
{
if (!_serialPort.IsOpen)
{
Console.WriteLine("串口未打开,无法发送!");
return;
}
try
{
byte[] data = Encoding.ASCII.GetBytes(sendData);
_serialPort.Write(data, 0, data.Length);
Console.WriteLine($"发送:{sendData}");
}
catch (Exception ex)
{
Console.WriteLine($"发送失败:{ex.Message}");
}
}
/// <summary>
/// 接收来自下位机的数据
/// </summary>
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
SerialPort sp = (SerialPort)sender;
byte[] buffer = new byte[sp.BytesToRead];
sp.Read(buffer, 0, buffer.Length);
string receiveData = Encoding.ASCII.GetString(buffer);
Console.WriteLine($"接收:{receiveData}");
}
catch (Exception ex)
{
Console.WriteLine($"接收失败:{ex.Message}");
}
}
/// <summary>
/// 关闭串口(释放资源)
/// </summary>
public void CloseSerialPort()
{
try
{
if (_serialPort.IsOpen)
{
_serialPort.Close();
Console.WriteLine("串口已关闭!");
}
}
catch (Exception ex)
{
Console.WriteLine($"关闭失败:{ex.Message}");
}
finally
{
_serialPort.Dispose();
}
}
// 测试入口
static void Main(string[] args)
{
SerialPortDemo demo = new SerialPortDemo();
demo.OpenSerialPort();
// 循环发送数据
while (true)
{
Console.WriteLine("输入发送内容(输入q退出):");
string input = Console.ReadLine();
if (input.Equals("q", StringComparison.OrdinalIgnoreCase)) break;
demo.SendData(input);
}
demo.CloseSerialPort();
}
}
}
关键说明:
- PortName 需要替换为设备实际占用的串口号(通过设备管理器查看端口);
- 波特率、校验位等通信参数必须与下位机配置完全一致,否则通信会失败;
- DataReceived 事件在独立线程中触发,若需要在WinForm或WPF界面中更新UI,必须使用
Invoke方法进行跨线程调用。
2. TCP/IP通信(以太网)
TCP是一种面向连接的可靠通信协议。C#通过TcpClient(客户端)和TcpListener(服务端)类来实现,适用于需要进行远距离、稳定数据传输的场景。
上位机作为TCP客户端示例:
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
namespace TcpClientCommunication
{
class TcpClientDemo
{
private TcpClient _tcpClient;
private NetworkStream _networkStream;
/// <summary>
/// 连接下位机(作为服务器)
/// </summary>
public bool Connect(string ip, int port)
{
try
{
_tcpClient = new TcpClient();
_tcpClient.Connect(ip, port);
_networkStream = _tcpClient.GetStream();
Console.WriteLine($"连接成功:{ip}:{port}");
// 启动独立线程持续接收数据
new System.Threading.Thread(ReceiveData).Start();
return true;
}
catch (Exception ex)
{
Console.WriteLine($"连接失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 接收数据
/// </summary>
private void ReceiveData()
{
byte[] buffer = new byte[1024];
int bytesRead;
try
{
while (_tcpClient.Connected)
{
bytesRead = _networkStream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
string data = Encoding.ASCII.GetString(buffer, 0, bytesRead);
Console.WriteLine($"接收:{data}");
}
}
}
catch (IOException)
{
Console.WriteLine("连接已断开");
}
}
/// <summary>
/// 发送数据
/// </summary>
public void SendData(string sendData)
{
if (!_tcpClient.Connected)
{
Console.WriteLine("未连接,无法发送!");
return;
}
try
{
byte[] data = Encoding.ASCII.GetBytes(sendData);
_networkStream.Write(data, 0, data.Length);
Console.WriteLine($"发送:{sendData}");
}
catch (Exception ex)
{
Console.WriteLine($"发送失败:{ex.Message}");
}
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
_networkStream?.Close();
_tcpClient?.Close();
Console.WriteLine("连接已关闭");
}
// 测试入口
static void Main(string[] args)
{
TcpClientDemo demo = new TcpClientDemo();
// 请替换为下位机的实际IP地址和端口
demo.Connect("192.168.1.100", 8080);
while (true)
{
Console.WriteLine("输入发送内容(输入q退出):");
string input = Console.ReadLine();
if (input.Equals("q", StringComparison.OrdinalIgnoreCase)) break;
demo.SendData(input);
}
demo.Disconnect();
}
}
}
关键说明:
- 进行TCP通信前,需确保下位机已开启对应端口的监听服务。
NetworkStream.Read是阻塞方法,必须放在独立线程中执行,否则会导致主线程卡死。
- 若需使用无连接的UDP通信,可采用
UdpClient类,其逻辑类似但无需提前建立连接。
- 在处理自定义协议包时,深入理解TCP/IP网络模型的基础知识至关重要。
3. Modbus通信(工业主流协议)
Modbus是工业自动化领域的通用协议,主要分为Modbus RTU(基于串口)和Modbus TCP(基于以太网)。不建议手动解析复杂的协议帧,优先使用成熟的第三方库如NModbus4(可直接通过NuGet包管理器安装)。
Modbus TCP示例(读取PLC寄存器):
using System;
using Modbus.Device;
using System.Net.Sockets;
namespace ModbusTcpCommunication
{
class ModbusTcpDemo
{
private TcpClient _tcpClient;
private IModbusMaster _modbusMaster;
/// <summary>
/// 连接Modbus TCP设备
/// </summary>
public bool Connect(string ip, int port = 502)
{
try
{
_tcpClient = new TcpClient();
_tcpClient.Connect(ip, port);
// 创建Modbus主站(即上位机)
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
_modbusMaster.Transport.SlaveAddress = 1; // 从站地址(需与设备配置一致)
Console.WriteLine("Modbus连接成功");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"连接失败:{ex.Message}");
return false;
}
}
/// <summary>
/// 读取保持寄存器(例如PLC的D寄存器)
/// </summary>
public ushort[] ReadRegisters(ushort startAddr, ushort length)
{
try
{
// 功能码03:读取保持寄存器
ushort[] registers = _modbusMaster.ReadHoldingRegisters(startAddr, length);
return registers;
}
catch (Exception ex)
{
Console.WriteLine($"读取失败:{ex.Message}");
return null;
}
}
/// <summary>
/// 写入单个寄存器
/// </summary>
public void WriteRegister(ushort addr, ushort value)
{
try
{
_modbusMaster.WriteSingleRegister(addr, value);
Console.WriteLine($"写入寄存器{addr}:{value}");
}
catch (Exception ex)
{
Console.WriteLine($"写入失败:{ex.Message}");
}
}
// 测试入口
static void Main(string[] args)
{
ModbusTcpDemo demo = new ModbusTcpDemo();
if (demo.Connect("192.168.1.100"))
{
// 读取起始地址为100的2个寄存器
ushort[] regs = demo.ReadRegisters(100, 2);
if (regs != null)
{
Console.WriteLine($"寄存器100:{regs[0]}, 寄存器101:{regs[1]}");
}
// 向寄存器102写入值1234
demo.WriteRegister(102, 1234);
}
}
}
}
关键说明:
- Modbus TCP默认使用502端口,从站地址通常默认为1,这些必须与现场设备的实际配置一致。
- 常用操作包括:读取保持寄存器(功能码03)、读取输入寄存器(04)、读取线圈(01),以及写入单个线圈(05)和写入单个寄存器(06)。
- 如需实现Modbus RTU通信,只需将
TcpClient替换为SerialPort,并使用ModbusSerialMaster.CreateRtu()方法来创建主站实例。
- 在工业软件架构中,除了通信,后端服务如何高效处理与存储这些采集到的数据同样是系统设计的关键考量。
二、通用注意事项与最佳实践
- 参数严格匹配:所有通信参数(如波特率、IP地址、端口号、从站地址等)必须与下位机设备侧的配置完全一致,这是通信成功的基础。
- 完善的异常处理:通信过程极易因网络波动、设备掉线等出现异常,务必妥善捕获和处理
IOException、SocketException、超时等异常。
- 线程安全:异步数据接收通常在独立线程中进行,若需在WinForm等GUI应用中更新界面显示,必须使用
Control.Invoke进行跨线程安全调用。
- 资源及时释放:通信结束后,必须显式关闭串口、TCP连接等资源,避免造成端口占用或内存泄漏,良好的资源管理是构建稳定可靠系统的重要一环。
总结
通过本文的实践,我们可以总结出C#上位机通信开发的几个要点:
- 核心工具选择:串口通信首选
SerialPort类,TCP/IP通信使用TcpClient/TcpListener,而Modbus通信则优先采用成熟的NModbus4等第三方库以提升开发效率。
- 成功的关键:精确匹配的通信参数(波特率、IP端口、站地址等)是建立稳定通信链路的前提。
- 工程的健壮性:必须重视异常处理、线程安全与资源释放,尤其是在需要与用户界面交互的复杂应用场景中。