☰
Modbus、Socket、TCP之间的关系
2026/10/27 1:55:31 网站建设 项目流程

Modbus、Socket、TCP 详解与对比

一、先理清核心:三者不在同一层级

三者是分层的包含 / 依赖关系,不是同一层面的并列技术。

从网络分层(自上而下):

  1. 应用层:Modbus —— 定义「数据长什么样、代表什么含义」,是设备之间的对话语言
  2. 传输层:TCP —— 定义「数据怎么可靠传输」,是底层的传输通道
  3. 编程接口层:Socket —— 定义「程序怎么操作 TCP/UDP」,是操作系统给开发者的操作工具

一句话概括:Modbus TCP 协议,通过 Socket 编程接口,基于 TCP 传输通道,实现工业设备之间的数据通信。

┌────────────────────────────────────────────┐ │ 应用层 Modbus(数据长什么样、什么含义) │ ← 设备之间的"对话语言" ├────────────────────────────────────────────┤ │ 传输层 TCP(怎么可靠送达) │ ← 底层"运输通道" ├────────────────────────────────────────────┤ │ 网络层 IP(怎么找到主机) │ ├────────────────────────────────────────────┤ │ 编程接口 Socket(怎么操作 TCP/UDP) │ ← 开发者手里的"工具" └────────────────────────────────────────────┘

再补一张三者横向总表(这是全场最该被拍照存下来的一页):

维度TCPSocketModbus
本质传输层协议编程接口(API)应用层协议
有没有"数据含义"无(纯字节)无(纯字节)有(地址+功能码+寄存器)
消息边界无(字节流)无(TCP)/有(UDP)有(帧自带)
可靠传输自带交给协议交给底层
能不能单独"用"依赖 Socket 调用依赖操作系统依赖 TCP/串口承载

金句:Socket 是"工具",TCP 是"运输方式",Modbus 是"信的内容"。三者不是并列,是层层套娃。

1. TCP:传输层的可靠传输协议

基础定义

TCP(Transmission Control Protocol,传输控制协议),是 OSI 七层模型中传输层的核心协议,核心目标是在不可靠的物理网络上,提供面向连接、可靠、有序的字节流传输。

核心特点
  • 面向连接:通信前必须通过三次握手建立连接,断开前四次挥手,类似打电话的逻辑 —— 先拨通再说话。

  • 可靠传输:自带丢包重传、序号确认、流量控制、拥塞控制,保证数据不丢失、不重复、按顺序到达。

  • 字节流无边界:TCP 只负责传输连续的字节流,不区分 “报文”“数据包” 的边界,上层协议自己定义数据格式和拆分逻辑。

  • 全双工:连接建立后,双方可以同时双向发送数据。

易忽略的工程坑
  • TCP 是字节流,天然存在粘包、拆包问题,不存在 “发一次就对应收一次” 的关系,上层必须自己处理报文边界。

三种经典解法(上位机必考):

  1. 长度前缀:帧头固定 + 长度字段 + 数据(最常用、最可靠)
  2. 分隔符:帧尾加固定结束符(如\r\n),收到就切
  3. 固定长度:每帧长度写死
C# 里怎么写 TCP
// 服务端(TcpListener) var listener = new TcpListener(IPAddress.Any, 502); listener.Start(); while (true) { var client = await listener.AcceptTcpClientAsync(); // 阻塞等待连接 _ = Task.Run(() => HandleClient(client)); // 每连接一个线程/任务 } // 客户端(TcpClient) using var client = new TcpClient(); await client.ConnectAsync("192.168.1.10", 502); NetworkStream stream = client.GetStream(); await stream.WriteAsync(bytes); // 发 int n = await stream.ReadAsync(buf); // 收

2. Socket 详解(编程接口,不是协议)

// TCP Socket 服务端 var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Any, 502)); listener.Listen(100); var conn = await listener.AcceptAsync(); byte[] buf = new byte[1024]; int n = await conn.ReceiveAsync(buf); // 收 await conn.SendAsync(data);

Socket 与 TcpClient的关系

TcpClient本质是对Socket的封装,用法更友好;Socket更底层、更灵活。做上位机:

  • 简单场景用TcpClient/UdpClient够用;

  • 需要精细控制(超时、KeepAlive、绑定网卡、TCP_NODELAY)时才直接用Socket。

  • 核心操作流程(TCP 模式)
    • 服务端:创建 Socket → 绑定 IP 和端口 → 监听连接 → 接受客户端连接 → 收发数据 → 关闭连接
    • 客户端:创建 Socket → 连接服务端 IP 端口 → 收发数据 → 关闭连接

3. Modbus 详解(应用层协议)

核心机制(主从模式)
  • 单主多从:一个主站(上位机 / PLC),多个从站(传感器、变频器、IO 模块),每个从站有唯一地址。
  • 请求 - 响应:主站主动发请求报文(从站地址 + 功能码 + 数据 + 校验),从站对应响应;从站不能主动发数据。
  • 核心功能码:01 读线圈、02 读离散输入、03 读保持寄存器、04 读输入寄存器、05 写单线圈、06 写单寄存器等。
易忽略的工程坑
  • Modbus TCP 虽然基于 TCP,但它是应用层协议,有自己的报文头和数据规范,不是直接发字符串。
  • 功能码、寄存器地址不同厂商可能有偏移,比如有的从 0 开始、有的从 1 开始,对接时必须核对设备手册。

两种形态:Modbus RTU 与 Modbus TCP

Modbus RTU(跑在串口上)帧格式:

代码块

从站地址(1B) | 功能码(1B) | 数据(N×1B) | CRC16校验(2B)
  • 靠CRC16校验完整性(串口没有可靠传输保障);
  • 帧之间靠3.5 个字符时间的静默间隔分隔帧;
  • 波特率、数据位、校验、停止位要和设备一致。

Modbus TCP(跑在 TCP 上)帧格式:

代码块

MBAP报文头(7B) + PDU MBAP = 事务标识(2B) + 协议标识(2B,恒0) + 长度(2B) + 单元标识(1B,即从站号) PDU = 功能码(1B) + 数据(NB)
  • 没有 CRC16(因为 TCP 已经保证可靠了);
  • 多一个"事务标识",用于匹配请求和响应;
  • 默认端口502。
using NModbus; using System.Net.Sockets; // 1. 连接设备(IP + 端口,Modbus TCP 默认 502) using var client = new TcpClient(); await client.ConnectAsync("192.168.1.100", 502); // 2. 创建 Modbus 主站 var factory = new ModbusFactory(); IModbusMaster master = factory.CreateMaster(client); // 3. 超时与重试(工业现场必配) master.Transport.ReadTimeout = 1000; // 读超时 1s master.Transport.WriteTimeout = 1000; // 写超时 1s master.Transport.Retries = 3; // 失败重试 3 次 byte slave = 1; // 从站地址(设备手册里查,多数默认 1) // 4. 读保持寄存器(地址 0 起,读 10 个)—— 最常用 ushort[] regs = await master.ReadHoldingRegistersAsync(slave, 0, 10); // 5. 读输入寄存器(只读模拟量,如温度/压力) ushort[] inputs = await master.ReadInputRegistersAsync(slave, 0, 2); // 6. 读线圈 / 读离散输入(开关量) bool[] coils = await master.ReadCoilsAsync(slave, 0, 8); bool[] dis = await master.ReadInputsAsync(slave, 0, 8); // 7. 写单个寄存器 / 写单个线圈 await master.WriteSingleRegisterAsync(slave, 0, 1234); await master.WriteSingleCoilAsync(slave, 0, true); // 8. 批量写多个寄存器 / 线圈 await master.WriteMultipleRegistersAsync(slave, 0, new ushort[] { 1, 2, 3 }); await master.WriteMultipleCoilsAsync(slave, 0, new bool[] { true, false, true }); //从站 using NModbus; using System.Net; using System.Net.Sockets; var factory = new ModbusFactory(); var listener = new TcpListener(IPAddress.Any, 502); listener.Start(); IModbusSlaveNetwork network = factory.CreateSlaveNetwork(listener); IModbusSlave slave = factory.CreateSlave(unitId: 1); slave.DataStore.HoldingRegisters[0] = 250; // 温度 25.0℃ slave.DataStore.CoilDiscretes[0] = true; // 运行状态 network.AddSlave(slave); await network.ListenAsync(); Console.WriteLine("从站已启动 (ID=1, 端口502)");

选型决策(很多分享会漏掉,但最实用)

场景选谁理由
对接标准仪表/变频器/PLCModbus设备已定义好协议,直接用最稳
和自家下位机自定义协议Socket(TCP)+自定义帧灵活、数据量大、可加密
低延迟、可容忍丢包Socket(UDP)快,但不可靠
只想要字节流、不管含义裸 TCP/TcpClient简单直接

给新手的进阶路线:先吃透 TCP + Socket(地基),再学 Modbus(套层格式),会发现 Modbus 不过是在 TCP 上包了一层固定报文。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询