Go语言Modbus开发完整指南:grid-x/modbus库TCP/RTU/ASCII/UDP全协议实战

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

Go语言Modbus开发完整指南:grid-x/modbus库TCP/RTU/ASCII/UDP全协议实战缩略图
🌐 This page is not yet available in English. Showing the Chinese version. Back to Chinese page.
本文目录
  1. 1. 一、grid-x/modbus库概述
  2. 2. 二、安装与环境配置
  3. 3. 三、Modbus TCP客户端
  4. 4. 四、数据类型转换
  5. 5. 五、Modbus RTU串口通讯
  6. 6. 六、Modbus ASCII与UDP
  7. 7. 七、设备识别(function code43)
  8. 8. 八、完整工具类封装
  9. 9. 九、并发与多设备采集
  10. 10. 十、Modbus TCP服务端(slave)
  11. 11. 十一、常见问题与最佳实践
  12. 12. 十二、grid-x/modbus与其他语言Modbus库对比

grid-x/modbus是Go语言生态中最活跃、功能最完善的Modbus协议库之一,支持Modbus TCP、RTU、ASCII、UDP四种传输模式,提供基于context.Context的超时控制和简洁一致的API。本文详细介绍grid-x/modbus库的安装配置、TCP/RTU客户端实现、数据类型转换、并发采集、服务端实现以及完整的项目示例,帮助Go开发者快速掌握工业Modbus通讯开发。

一、grid-x/modbus库概述

1.1 核心特性

  • 多协议支持:Modbus TCP、RTU、ASCII、UDP四种传输模式
  • Context支持:所有操作接受context.Context,支持超时和取消
  • 功能完整:支持01-16标准功能码,以及22(掩码写)、23(读写多个)、24(读FIFO)、43(设备识别)
  • 主从兼备:既可作为客户端(master),也可作为服务端(slave)
  • 故障容错:fail-fast设计,连接失败立即报错,不静默重试
  • 跨平台:Windows、Linux、Mac OS、embeddedLinux均可运行
  • 零依赖:仅依赖Go标准库,无第三方依赖
  • 活跃维护:持续更新,社区活跃,文档完善

1.2 与其他Go Modbus库对比

库名协议支持Context服务端活跃度推荐场景
grid-x/modbusTCP/RTU/ASCII/UDP支持支持新项目首选
goburrow/modbusTCP/RTU不支持支持遗留项目
simonvetter/modbusTCP/RTU支持不支持简单客户端
aldas/go-modbus-clientTCP支持不支持Request/响应模式

二、安装与环境配置

2.1 安装grid-x/modbus

# 初始化Go模块
go mod init modbus_demo

# 安装grid-x/modbus
go get github.com/grid-x/modbus

# 验证安装
go list -m all | grep grid-x

2.2 导入包

import (
    "context"
    "github.com/grid-x/modbus"
)

三、Modbus TCP客户端

3.1 Quick Start(简洁模式)

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/grid-x/modbus"
)

func main() {
    // 创建TCP客户端(简洁模式,自动管理连接)
    client := modbus.TCPClient("192.168.1.100:502")

    ctx := context.Background()

    // read holding registers(function code03)
    // 注意:返回值是[]byte,需要手动解析为uint16
    results, err := client.ReadHoldingRegisters(ctx, 0, 10)
    if err != nil {
        log.Fatalf("读保持寄存器失败:%v", err)
    }

    // 解析寄存器值(每2字节为一个16位寄存器,高字节在前)
    fmt.Printf("读取到%d字节数据:\n", len(results))
    for i := 0; i < len(results); i += 2 {
        value := uint16(results[i])<<8 | uint16(results[i+1])
        fmt.Printf("  register%d:%d\n", i/2, value)
    }
}

3.2 高级模式(手动管理连接)

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/grid-x/modbus"
)

func main() {
    // 创建TCP Handler(可配置超时、slave address、日志等)
    handler := modbus.NewTCPClientHandler("192.168.1.100:502")
    handler.Timeout = 10 * time.Second
    handler.SlaveID = 1
    handler.Logger = log.New(os.Stdout, "modbus: ", log.LstdFlags)

    ctx := context.Background()

    // 手动建立连接(多次请求复用同一连接)
    if err := handler.Connect(ctx); err != nil {
        log.Fatalf("连接失败:%v", err)
    }
    defer handler.Close()

    // 创建客户端
    client := modbus.NewClient(handler)

    // 连续多次请求(复用连接)
    for i := 0; i < 5; i++ {
        results, err := client.ReadHoldingRegisters(ctx, uint16(i*10), 10)
        if err != nil {
            log.Printf("No.%d次读取失败:%v", i, err)
            continue
        }
        fmt.Printf("批次%d:读取到%dbyte\n", i, len(results))
    }
}

3.3 完整功能码示例

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/grid-x/modbus"
)

func main() {
    client := modbus.TCPClient("192.168.1.100:502")
    ctx := context.Background()

    // === function code01:读线圈 ===
    // 返回[]byte,每一位对应一个线圈状态
    coils, err := client.ReadCoils(ctx, 0, 8)
    if err != nil {
        log.Fatalf("读线圈失败:%v", err)
    }
    fmt.Println("=== 读线圈(function code01)===")
    for i := 0; i < 8; i++ {
        byteIdx := i / 8
        bitIdx := uint(i % 8)
        state := (coils[byteIdx] >> bitIdx) & 0x01
        fmt.Printf("  线圈%d:%s\n", i, map[byte]string{0: "断开", 1: "吸合"}[state])
    }

    // === function code02:read discrete inputs ===
    inputs, err := client.ReadDiscreteInputs(ctx, 0, 4)
    if err != nil {
        log.Fatalf("读离散输入失败:%v", err)
    }
    fmt.Println("\n=== read discrete inputs(function code02)===")
    for i := 0; i < 4; i++ {
        state := (inputs[i/8] >> uint(i%8)) & 0x01
        fmt.Printf("  输入%d:%d\n", i, state)
    }

    // === function code03:read holding registers ===
    regs, err := client.ReadHoldingRegisters(ctx, 0, 10)
    if err != nil {
        log.Fatalf("读保持寄存器失败:%v", err)
    }
    fmt.Println("\n=== read holding registers(function code03)===")
    for i := 0; i < len(regs); i += 2 {
        value := uint16(regs[i])<<8 | uint16(regs[i+1])
        fmt.Printf("  register%d:%d\n", i/2, value)
    }

    // === function code04:read input registers ===
    inputRegs, err := client.ReadInputRegisters(ctx, 0, 2)
    if err != nil {
        log.Fatalf("读输入寄存器失败:%v", err)
    }
    fmt.Println("\n=== read input registers(function code04)===")
    for i := 0; i < len(inputRegs); i += 2 {
        value := uint16(inputRegs[i])<<8 | uint16(inputRegs[i+1])
        fmt.Printf("  输入寄存器%d:%d\n", i/2, value)
    }

    // === function code05:write single coil ===
    // 0xFF00 = 吸合,0x0000 = 断开
    _, err = client.WriteSingleCoil(ctx, 0, 0xFF00)
    if err != nil {
        log.Fatalf("写线圈失败:%v", err)
    }
    fmt.Println("\n=== write single coil(function code05)===")
    fmt.Println("  线圈0已置为吸合")

    // === function code06:write single register ===
    _, err = client.WriteSingleRegister(ctx, 100, 1234)
    if err != nil {
        log.Fatalf("写寄存器失败:%v", err)
    }
    fmt.Println("\n=== write single register(function code06)===")
    fmt.Println("  register100已写入1234")

    // === function code0F:write multiple coils ===
    // value参数为[]byte,按位打包
    coilData := []byte{0x05} // binary00000101 = 线圈0和线圈2吸合
    _, err = client.WriteMultipleCoils(ctx, 0, 8, coilData)
    if err != nil {
        log.Fatalf("写多个线圈失败:%v", err)
    }
    fmt.Println("\n=== write multiple coils(function code0F)===")
    fmt.Println("  线圈0-7已批量写入")

    // === function code10:write multiple registers ===
    // value参数为[]byte,每2字节一个寄存器
    regData := []byte{0x00, 0x64, 0x00, 0xC8, 0x01, 0x2C} // 100, 200, 300
    _, err = client.WriteMultipleRegisters(ctx, 200, 3, regData)
    if err != nil {
        log.Fatalf("写多个寄存器失败:%v", err)
    }
    fmt.Println("\n=== write multiple registers(function code10)===")
    fmt.Println("  register200-202已批量写入")
}

四、数据类型转换

grid-x/modbus的所有读方法返回[]byte,需要开发者手动解析为目标数据类型。以下提供完整的转换工具函数。

4.1 寄存器与字节转换

package modbusutil

import "encoding/binary"

// BytesToRegisters 将[]byte解析为[]uint16(大端序)
func BytesToRegisters(data []byte) []uint16 {
    regs := make([]uint16, len(data)/2)
    for i := 0; i < len(regs); i++ {
        regs[i] = binary.BigEndian.Uint16(data[i*2 : i*2+2])
    }
    return regs
}

// RegistersToBytes 将[]uint16转换为[]byte(大端序)
func RegistersToBytes(regs []uint16) []byte {
    data := make([]byte, len(regs)*2)
    for i, v := range regs {
        binary.BigEndian.PutUint16(data[i*2:i*2+2], v)
    }
    return data
}

// BytesToBools 将[]byte解析为[]bool(低位在前)
func BytesToBools(data []byte, count int) []bool {
    bools := make([]bool, count)
    for i := 0; i < count; i++ {
        bools[i] = (data[i/8]>>uint(i%8))&0x01 == 1
    }
    return bools
}

// BoolsToBytes 将[]bool打包为[]byte
func BoolsToBytes(bools []bool) []byte {
    byteCount := (len(bools) + 7) / 8
    data := make([]byte, byteCount)
    for i, v := range bools {
        if v {
            data[i/8] |= 1 << uint(i%8)
        }
    }
    return data
}

4.2 32位数据类型(浮点数、整数)

import "math"

// BytesToFloat32ABCD Parse32位浮点数(ABCD大端序)
func BytesToFloat32ABCD(data []byte) float32 {
    bits := uint32(data[0])<<24 | uint32(data[1])<<16 |
        uint32(data[2])<<8 | uint32(data[3])
    return math.Float32frombits(bits)
}

// BytesToFloat32CDAB Parse32位浮点数(CDAB字交换序)
func BytesToFloat32CDAB(data []byte) float32 {
    bits := uint32(data[2])<<24 | uint32(data[3])<<16 |
        uint32(data[0])<<8 | uint32(data[1])
    return math.Float32frombits(bits)
}

// BytesToFloat32DCBA Parse32位浮点数(DCBA小端序)
func BytesToFloat32DCBA(data []byte) float32 {
    bits := uint32(data[3])<<24 | uint32(data[2])<<16 |
        uint32(data[1])<<8 | uint32(data[0])
    return math.Float32frombits(bits)
}

// Float32ToBytesABCD 将float32转换为4字节(ABCD大端序)
func Float32ToBytesABCD(f float32) []byte {
    bits := math.Float32bits(f)
    return []byte{
        byte(bits >> 24), byte(bits >> 16),
        byte(bits >> 8), byte(bits),
    }
}

// BytesToInt32 Parse32位有符号整数(大端序)
func BytesToInt32(data []byte) int32 {
    return int32(uint32(data[0])<<24 | uint32(data[1])<<16 |
        uint32(data[2])<<8 | uint32(data[3]))
}

// BytesToUint32 Parse32位无符号整数(大端序)
func BytesToUint32(data []byte) uint32 {
    return uint32(data[0])<<24 | uint32(data[1])<<16 |
        uint32(data[2])<<8 | uint32(data[3])
}

// Int32ToBytes 将int32转换为4字节(大端序)
func Int32ToBytes(v int32) []byte {
    u := uint32(v)
    return []byte{byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u)}
}

4.3 字节序对照表

endianness说明32位值0x41420000的存储方式
ABCD大端序(标准)reg[0]=0x4142, reg[1]=0x0000
CDABWord exchangereg[0]=0x0000, reg[1]=0x4142
DCBA小端序reg[0]=0x0000, reg[1]=0x4241
BADC字节交换reg[0]=0x4241, reg[1]=0x0000

五、Modbus RTU串口通讯

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/grid-x/modbus"
)

func main() {
    // 创建RTU Handler
    handler := modbus.NewRTUClientHandler("/dev/ttyUSB0")
    handler.BaudRate = 9600      // Baud rate
    handler.DataBits = 8         // data bit
    handler.Parity = "N"         // check digit:N=无,E=偶,O=奇
    handler.StopBits = 1         // stop bit
    handler.SlaveID = 1          // slave address
    handler.Timeout = 5 * time.Second

    ctx := context.Background()

    // connect
    if err := handler.Connect(ctx); err != nil {
        log.Fatalf("RTU连接失败:%v", err)
    }
    defer handler.Close()

    client := modbus.NewClient(handler)

    // Read and hold register
    results, err := client.ReadHoldingRegisters(ctx, 0, 10)
    if err != nil {
        log.Fatalf("Read failed:%v", err)
    }

    regs := make([]uint16, len(results)/2)
    for i := range regs {
        regs[i] = uint16(results[i*2])<<8 | uint16(results[i*2+1])
    }
    fmt.Printf("register value:%v\n", regs)

    // Windows串口示例:
    // handler := modbus.NewRTUClientHandler("COM3")
}

六、Modbus ASCII与UDP

// === Modbus ASCII ===
// ASCII模式使用可打印字符,适合调试和低可靠性链路
client := modbus.ASCIIClient("/dev/ttyUSB0")
// 配置方式与RTU相同,通过Handler设置

// === Modbus UDP ===
// UDP模式无连接,适合广播和简单查询
client := modbus.RTUOverUDPClient("192.168.1.255:502")

// === RTU over TCP ===
// 将RTU帧封装在TCP中(部分设备使用此模式)
client := modbus.RTUOverTCPClient("192.168.1.100:502")

// === ASCII over TCP ===
client := modbus.ASCIIOverTCPClient("192.168.1.100:502")

七、设备识别(function code43)

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/grid-x/modbus"
)

func main() {
    client := modbus.TCPClient("192.168.1.100:502")
    ctx := context.Background()

    // 读取基本设备信息(厂商名、产品代码、版本号)
    deviceInfo, err := client.ReadDeviceIdentification(ctx, modbus.ReadDeviceIDCodeBasic)
    if err != nil {
        log.Fatalf("读取设备信息失败:%v", err)
    }

    fmt.Printf("设备信息:%+v\n", deviceInfo)

    // 读取常规设备信息(厂商URL、产品名、型号、应用名)
    regularInfo, err := client.ReadDeviceIdentification(ctx, modbus.ReadDeviceIDCodeRegular)
    if err != nil {
        log.Printf("读取常规信息失败:%v", err)
    } else {
        fmt.Printf("常规信息:%+v\n", regularInfo)
    }

    // 读取特定对象(对象ID=0通常是厂商名)
    specificInfo, err := client.ReadDeviceIdentificationSpecificObject(ctx, 0)
    if err != nil {
        log.Printf("读取特定对象失败:%v", err)
    } else {
        fmt.Printf("对象0:%s\n", specificInfo)
    }
}

八、完整工具类封装

package modbusutil

import (
    "context"
    "encoding/binary"
    "math"
    "sync"
    "time"

    "github.com/grid-x/modbus"
)

// ModbusClient 封装grid-x/modbus的工具类
type ModbusClient struct {
    handler *modbus.TCPClientHandler
    client  modbus.Client
    mu      sync.Mutex
}

// NewModbusClient 创建TCP客户端
func NewModbusClient(address string, slaveID byte, timeout time.Duration) (*ModbusClient, error) {
    handler := modbus.NewTCPClientHandler(address)
    handler.SlaveID = slaveID
    handler.Timeout = timeout

    ctx := context.Background()
    if err := handler.Connect(ctx); err != nil {
        return nil, err
    }

    return &ModbusClient{
        handler: handler,
        client:  modbus.NewClient(handler),
    }, nil
}

// Close 关闭连接
func (m *ModbusClient) Close() error {
    return m.handler.Close()
}

// ReadRegisters read holding registers,返回[]uint16
func (m *ModbusClient) ReadRegisters(ctx context.Context, addr, qty uint16) ([]uint16, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    data, err := m.client.ReadHoldingRegisters(ctx, addr, qty)
    if err != nil {
        return nil, err
    }
    return BytesToRegisters(data), nil
}

// ReadInputRegisters read input registers
func (m *ModbusClient) ReadInputRegisters(ctx context.Context, addr, qty uint16) ([]uint16, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    data, err := m.client.ReadInputRegisters(ctx, addr, qty)
    if err != nil {
        return nil, err
    }
    return BytesToRegisters(data), nil
}

// ReadCoils 读线圈,返回[]bool
func (m *ModbusClient) ReadCoils(ctx context.Context, addr, qty uint16) ([]bool, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    data, err := m.client.ReadCoils(ctx, addr, qty)
    if err != nil {
        return nil, err
    }
    return BytesToBools(data, int(qty)), nil
}

// ReadDiscreteInputs read discrete inputs
func (m *ModbusClient) ReadDiscreteInputs(ctx context.Context, addr, qty uint16) ([]bool, error) {
    m.mu.Lock()
    defer m.mu.Unlock()

    data, err := m.client.ReadDiscreteInputs(ctx, addr, qty)
    if err != nil {
        return nil, err
    }
    return BytesToBools(data, int(qty)), nil
}

// WriteRegister write single register
func (m *ModbusClient) WriteRegister(ctx context.Context, addr, value uint16) error {
    m.mu.Lock()
    defer m.mu.Unlock()

    _, err := m.client.WriteSingleRegister(ctx, addr, value)
    return err
}

// WriteRegisters write multiple registers
func (m *ModbusClient) WriteRegisters(ctx context.Context, addr uint16, values []uint16) error {
    m.mu.Lock()
    defer m.mu.Unlock()

    data := RegistersToBytes(values)
    _, err := m.client.WriteMultipleRegisters(ctx, addr, uint16(len(values)), data)
    return err
}

// WriteCoil write single coil
func (m *ModbusClient) WriteCoil(ctx context.Context, addr uint16, value bool) error {
    m.mu.Lock()
    defer m.mu.Unlock()

    var v uint16
    if value {
        v = 0xFF00
    }
    _, err := m.client.WriteSingleCoil(ctx, addr, v)
    return err
}

// WriteCoils write multiple coils
func (m *ModbusClient) WriteCoils(ctx context.Context, addr uint16, values []bool) error {
    m.mu.Lock()
    defer m.mu.Unlock()

    data := BoolsToBytes(values)
    _, err := m.client.WriteMultipleCoils(ctx, addr, uint16(len(values)), data)
    return err
}

// ReadFloat32 读32位浮点数(ABCD大端序)
func (m *ModbusClient) ReadFloat32(ctx context.Context, addr uint16) (float32, error) {
    data, err := m.client.ReadHoldingRegisters(ctx, addr, 2)
    if err != nil {
        return 0, err
    }
    return BytesToFloat32ABCD(data), nil
}

// WriteFloat32 写32位浮点数(ABCD大端序)
func (m *ModbusClient) WriteFloat32(ctx context.Context, addr uint16, value float32) error {
    data := Float32ToBytesABCD(value)
    _, err := m.client.WriteMultipleRegisters(ctx, addr, 2, data)
    return err
}

// ReadInt32 读32位有符号整数
func (m *ModbusClient) ReadInt32(ctx context.Context, addr uint16) (int32, error) {
    data, err := m.client.ReadHoldingRegisters(ctx, addr, 2)
    if err != nil {
        return 0, err
    }
    return BytesToInt32(data), nil
}

// ReadString 读字符串(每个寄存器存2个字符)
func (m *ModbusClient) ReadString(ctx context.Context, addr, regCount uint16) (string, error) {
    data, err := m.client.ReadHoldingRegisters(ctx, addr, regCount)
    if err != nil {
        return "", err
    }
    return string(data), nil
}

// 辅助函数(同前)
func BytesToRegisters(data []byte) []uint16 {
    regs := make([]uint16, len(data)/2)
    for i := range regs {
        regs[i] = binary.BigEndian.Uint16(data[i*2 : i*2+2])
    }
    return regs
}

func RegistersToBytes(regs []uint16) []byte {
    data := make([]byte, len(regs)*2)
    for i, v := range regs {
        binary.BigEndian.PutUint16(data[i*2:i*2+2], v)
    }
    return data
}

func BytesToBools(data []byte, count int) []bool {
    bools := make([]bool, count)
    for i := 0; i < count; i++ {
        bools[i] = (data[i/8]>>uint(i%8))&0x01 == 1
    }
    return bools
}

func BoolsToBytes(bools []bool) []byte {
    byteCount := (len(bools) + 7) / 8
    data := make([]byte, byteCount)
    for i, v := range bools {
        if v {
            data[i/8] |= 1 << uint(i%8)
        }
    }
    return data
}

func BytesToFloat32ABCD(data []byte) float32 {
    bits := uint32(data[0])<<24 | uint32(data[1])<<16 |
        uint32(data[2])<<8 | uint32(data[3])
    return math.Float32frombits(bits)
}

func Float32ToBytesABCD(f float32) []byte {
    bits := math.Float32bits(f)
    return []byte{byte(bits >> 24), byte(bits >> 16), byte(bits >> 8), byte(bits)}
}

func BytesToInt32(data []byte) int32 {
    return int32(uint32(data[0])<<24 | uint32(data[1])<<16 |
        uint32(data[2])<<8 | uint32(data[3]))
}

九、并发与多设备采集

package main

import (
    "context"
    "fmt"
    "log"
    "sync"
    "time"

    "github.com/grid-x/modbus"
)

// DeviceConfig 设备配置
type DeviceConfig struct {
    Name    string
    Address string
    SlaveID byte
}

// ReadResult 读取结果
type ReadResult struct {
    Device string
    Regs   []uint16
    Error  error
}

func readDevice(ctx context.Context, cfg DeviceConfig, wg *sync.WaitGroup, results chan<- ReadResult) {
    defer wg.Done()

    handler := modbus.NewTCPClientHandler(cfg.Address)
    handler.SlaveID = cfg.SlaveID
    handler.Timeout = 3 * time.Second

    if err := handler.Connect(ctx); err != nil {
        results <- ReadResult{Device: cfg.Name, Error: err}
        return
    }
    defer handler.Close()

    client := modbus.NewClient(handler)
    data, err := client.ReadHoldingRegisters(ctx, 0, 10)
    if err != nil {
        results <- ReadResult{Device: cfg.Name, Error: err}
        return
    }

    regs := make([]uint16, len(data)/2)
    for i := range regs {
        regs[i] = uint16(data[i*2])<<8 | uint16(data[i*2+1])
    }
    results <- ReadResult{Device: cfg.Name, Regs: regs}
}

func main() {
    devices := []DeviceConfig{
        {Name: "PLC1", Address: "192.168.1.100:502", SlaveID: 1},
        {Name: "PLC2", Address: "192.168.1.101:502", SlaveID: 1},
        {Name: "PLC3", Address: "192.168.1.102:502", SlaveID: 1},
    }

    ctx := context.Background()
    var wg sync.WaitGroup
    results := make(chan ReadResult, len(devices))

    // 并发读取所有设备
    for _, dev := range devices {
        wg.Add(1)
        go readDevice(ctx, dev, &wg, results)
    }

    wg.Wait()
    close(results)

    // 处理结果
    for r := range results {
        if r.Error != nil {
            fmt.Printf("device%sRead failed:%v\n", r.Device, r.Error)
        } else {
            fmt.Printf("device%s:%v\n", r.Device, r.Regs)
        }
    }
}

十、Modbus TCP服务端(slave)

grid-x/modbus库本身不直接提供服务端实现,但可以基于其协议编码/解码功能构建服务端。以下是一个简单的TCP从站实现示例。

package main

import (
    "encoding/binary"
    "fmt"
    "log"
    "net"
    "sync"
)

// ModbusServer 简单的Modbus TCPslave
type ModbusServer struct {
    coils       []bool
    inputs      []bool
    holdingRegs []uint16
    inputRegs   []uint16
    mu          sync.RWMutex
}

func NewModbusServer() *ModbusServer {
    return &ModbusServer{
        coils:       make([]bool, 1024),
        inputs:      make([]bool, 1024),
        holdingRegs: make([]uint16, 1024),
        inputRegs:   make([]uint16, 1024),
    }
}

func (s *ModbusServer) handleConnection(conn net.Conn) {
    defer conn.Close()
    buf := make([]byte, 260) // Modbus TCP最大ADULength

    for {
        n, err := conn.Read(buf)
        if err != nil {
            return
        }
        if n < 8 {
            continue
        }

        // ParseMBAP Head
        transID := binary.BigEndian.Uint16(buf[0:2])
        // protoID := binary.BigEndian.Uint16(buf[2:4]) // 固定0x0000
        // length := binary.BigEndian.Uint16(buf[4:6])
        unitID := buf[6]
        funcCode := buf[7]

        // 处理请求
        response := s.processRequest(funcCode, buf[8:n])

        // 构建响应MBAP Head
        resp := make([]byte, 7+len(response))
        binary.BigEndian.PutUint16(resp[0:2], transID)
        binary.BigEndian.PutUint16(resp[2:4], 0x0000) // ProtocolID
        binary.BigEndian.PutUint16(resp[4:6], uint16(1+len(response)))
        resp[6] = unitID
        copy(resp[7:], response)

        conn.Write(resp)
    }
}

func (s *ModbusServer) processRequest(funcCode byte, data []byte) []byte {
    s.mu.Lock()
    defer s.mu.Unlock()

    switch funcCode {
    case 0x03: // read holding registers
        addr := binary.BigEndian.Uint16(data[0:2])
        qty := binary.BigEndian.Uint16(data[2:4])
        byteCount := qty * 2
        resp := make([]byte, 2+byteCount)
        resp[0] = funcCode
        resp[1] = byte(byteCount)
        for i := uint16(0); i < qty; i++ {
            binary.BigEndian.PutUint16(resp[2+i*2:], s.holdingRegs[addr+i])
        }
        return resp

    case 0x06: // write single register
        addr := binary.BigEndian.Uint16(data[0:2])
        value := binary.BigEndian.Uint16(data[2:4])
        s.holdingRegs[addr] = value
        resp := make([]byte, 5)
        resp[0] = funcCode
        binary.BigEndian.PutUint16(resp[1:3], addr)
        binary.BigEndian.PutUint16(resp[3:5], value)
        return resp

    case 0x01: // 读线圈
        addr := binary.BigEndian.Uint16(data[0:2])
        qty := binary.BigEndian.Uint16(data[2:4])
        byteCount := (qty + 7) / 8
        resp := make([]byte, 2+byteCount)
        resp[0] = funcCode
        resp[1] = byte(byteCount)
        for i := uint16(0); i < qty; i++ {
            if s.coils[addr+i] {
                resp[2+i/8] |= 1 << (i % 8)
            }
        }
        return resp

    case 0x05: // write single coil
        addr := binary.BigEndian.Uint16(data[0:2])
        value := binary.BigEndian.Uint16(data[2:4])
        s.coils[addr] = value == 0xFF00
        resp := make([]byte, 5)
        resp[0] = funcCode
        binary.BigEndian.PutUint16(resp[1:3], addr)
        binary.BigEndian.PutUint16(resp[3:5], value)
        return resp

    default:
        // Exception Response:非法功能码
        return []byte{funcCode | 0x80, 0x01}
    }
}

func main() {
    server := NewModbusServer()

    // 设置初始值
    server.holdingRegs[0] = 100
    server.holdingRegs[1] = 200
    server.coils[0] = true

    listener, err := net.Listen("tcp", ":502")
    if err != nil {
        log.Fatalf("监听失败:%v", err)
    }
    defer listener.Close()

    fmt.Println("Modbus TCP服务端已启动,监听:502")

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Printf("接受连接失败:%v", err)
            continue
        }
        go server.handleConnection(conn)
    }
}

十一、常见问题与最佳实践

11.1 common problems

  • 返回数据是[]byte不是[]uint16:grid-x/modbus的设计哲学是返回原始字节,由调用方解析。使用本文提供的BytesToRegisters工具函数转换
  • 连接超时:检查IPaddress、端口、防火墙,确认设备在线。通过handler.Timeout设置超时时间
  • Exception Response:检查SlaveID是否正确,功能码和地址是否在设备支持范围内
  • 浮点数解析错误:确认设备字节序(ABCD/CDAB/DCBA/BADC),使用对应的转换函数
  • RTU串口打不开:检查串口设备名,确认权限(Linux需加入dialout组)
  • 并发冲突:同一个handler不要在多个goroutine中同时使用,加互斥锁或每个goroutine用独立连接
  • 简洁模式每次新建连接:modbus.TCPClient()简洁模式每次请求都建立新连接,高频请求请用高级模式复用连接

11.2 最佳实践

  • 使用context控制超时:所有操作传入带超时的context,避免永久阻塞
  • 高频请求复用连接:使用NewTCPClientHandler + Connect/Close手动管理连接,减少TCP握手开销
  • 批量读取:尽量一次读取多个连续寄存器,减少通讯次数
  • 合理设置超时:局域网1-3秒,RTUserial port3-5秒
  • 并发加锁:多goroutine共享同一客户端时使用sync.Mutex保护
  • 错误处理:每次调用都检查error,不要忽略
  • 日志记录:开发阶段设置handler.Logger,查看原始通讯数据
  • 资源释放:使用defer handler.Close()确保连接释放
  • 地址规划:提前规划寄存器地址,编写地址映射文档
  • 测试验证:先用Modbus Poll确认设备通讯正常,再集成Go代码

十二、grid-x/modbus与其他语言Modbus库对比

语言库名返回类型Context支持部署复杂度
Gogrid-x/modbus[]byte(需手动解析)支持低(单二进制)
Clibmodbusuint16_t数组不支持中(需编译)
Pythonpymodbus对象(.registers/.bits)支持(asyncio)低(解释执行)
JavaEasyModbusint[]/boolean[]不支持高(需JVM)
C#NModbusushort[]/bool[]支持(async/await)中(需.NET)

grid-x/modbus以其简洁的API、完善的协议支持和优秀的并发性能,成为Go语言工业Modbus开发的首选库。本文提供的工具类封装可直接用于项目开发,建议先使用Modbus Poll确认设备通讯参数,再将Go代码集成到实际系统中。

技术术语(共 5 个)—— Click to Expand
Modbus TCP基于以太网的Modbus协议变体,使用TCP/IP传输
function codeModbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
registerModbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
线圈Modbus位可读写数据,地址从00001开始
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— Click to Expand
来源 Modbus Chinese Network(modbus.cn) —— China leadingModbuscommunication protocol technical community Category Modbus programming development 字数 1790 字 · 阅读约 5 分钟 更新 2026-06-26 永久链接 https://www.modbus.cn/12406.html
Recommended Tool: Modbus Debug Assistant WeChat Mini Program
Modbus Chinese Network官方推出的Modbus debugging tool,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 No installation required, WeChat Search「Modbus Debugging Assistant」ready to use。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
Related Tags
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

Leave a Reply

Your email address will not be published. Required fields are marked *.