Mod bus 프로토 콜 기반 TCP 서버 - 클 라이언 트 소 스 (C #)

Paid ¥4.99Paid ¥4.99

该内容已接入现有会员/付费系统,解锁后可查看完整资料或下载附件。

查看解锁方式
Mod bus 프로토 콜 기반 TCP 서버 - 클 라이언 트 소 스 (C #)

C#是Windows平台工业软件开发的主流语言,广泛应用于上位机、SCADAsystem、数据采集软件等领域。本文提供完整的C# Modbus TCP服务器和客户端实现代码,包括TCP监听、多客户端管理、Modbus协议解析、회원가입 Mapping、客户端读写操作以及完整的使用示例。代码基于.NET Framework 4.5+,可直接在Visual Studio中编译运行。

一、项目结构与依赖

1.1 开发环境

项目要求
开发工具Visual Studio 2017+ / Visual Studio Code
.NET版本.NET Framework 4.5+ 或 .NET Core 2.0+
语言C# 7.0+
NuGet包无需第三方库,使用System.Net.Sockets
项目类型控制台应用 / Windows Forms / WPF

1.2 회원가입 Mapping

地址范围Type说明
0x0000-0x03FF线圈(Coil)1024个可读写布尔量
0x0000-0x03FF离散输入1024个只读布尔量
0x0000-0x03FF保持寄存器1024个可读写16位整数
0x0000-0x03FF输入寄存器1024个只读16位整数

二、Modbus TCP服务器实现

2.1 数据存储类(ModbusDataStore.cs)

using System;

namespace ModbusTcpServer
{
    /// <summary>
    /// Modbus数据存储,线程安全
    /// </summary>
    public class ModbusDataStore
    {
        private readonly object _lock = new object();
        
        // 线圈(可读写布尔量)
        private readonly bool[] _coils = new bool[1024];
        // 离散输入(只读布尔量)
        private readonly bool[] _inputs = new bool[1024];
        // 保持寄存器(可读写16位)
        private readonly ushort[] _holdingRegs = new ushort[1024];
        // 输入寄存器(只读16位)
        private readonly ushort[] _inputRegs = new ushort[1024];

        // 线圈操作
        public bool GetCoil(ushort address)
        {
            lock (_lock) { return _coils[address]; }
        }
        
        public void SetCoil(ushort address, bool value)
        {
            lock (_lock) { _coils[address] = value; }
        }
        
        public bool[] GetCoils(ushort startAddr, ushort count)
        {
            lock (_lock)
            {
                bool[] result = new bool[count];
                Array.Copy(_coils, startAddr, result, 0, count);
                return result;
            }
        }
        
        public void SetCoils(ushort startAddr, bool[] values)
        {
            lock (_lock)
            {
                Array.Copy(values, 0, _coils, startAddr, values.Length);
            }
        }

        // 离散输入操作
        public bool[] GetInputs(ushort startAddr, ushort count)
        {
            lock (_lock)
            {
                bool[] result = new bool[count];
                Array.Copy(_inputs, startAddr, result, 0, count);
                return result;
            }
        }
        
        public void SetInput(ushort address, bool value)
        {
            lock (_lock) { _inputs[address] = value; }
        }

        // 保持寄存器操作
        public ushort GetHoldingReg(ushort address)
        {
            lock (_lock) { return _holdingRegs[address]; }
        }
        
        public void SetHoldingReg(ushort address, ushort value)
        {
            lock (_lock) { _holdingRegs[address] = value; }
        }
        
        public ushort[] GetHoldingRegs(ushort startAddr, ushort count)
        {
            lock (_lock)
            {
                ushort[] result = new ushort[count];
                Array.Copy(_holdingRegs, startAddr, result, 0, count);
                return result;
            }
        }
        
        public void SetHoldingRegs(ushort startAddr, ushort[] values)
        {
            lock (_lock)
            {
                Array.Copy(values, 0, _holdingRegs, startAddr, values.Length);
            }
        }

        // 输入寄存器操作
        public ushort[] GetInputRegs(ushort startAddr, ushort count)
        {
            lock (_lock)
            {
                ushort[] result = new ushort[count];
                Array.Copy(_inputRegs, startAddr, result, 0, count);
                return result;
            }
        }
        
        public void SetInputReg(ushort address, ushort value)
        {
            lock (_lock) { _inputRegs[address] = value; }
        }
    }
}

2.2 Modbus TCP服务器类(ModbusTcpServer.cs)

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ModbusTcpServer
{
    public class ModbusTcpServer
    {
        private TcpListener _listener;
        private Thread _listenThread;
        private bool _isRunning;
        private readonly ModbusDataStore _dataStore;
        private readonly List<TcpClient> _clients = new List<TcpClient>();
        private ushort _transactionId = 0;

        public int Port { get; } = 502;
        public ModbusDataStore DataStore => _dataStore;

        public ModbusTcpServer(int port = 502)
        {
            Port = port;
            _dataStore = new ModbusDataStore();
        }

        public void Start()
        {
            _listener = new TcpListener(IPAddress.Any, Port);
            _listener.Start();
            _isRunning = true;
            
            _listenThread = new Thread(ListenForClients)
            {
                IsBackground = true
            };
            _listenThread.Start();
            
            Console.WriteLine($"Modbus TCP服务器已启动,监听端口:{Port}");
        }

        public void Stop()
        {
            _isRunning = false;
            _listener?.Stop();
            lock (_clients)
            {
                foreach (var client in _clients)
                    client.Close();
                _clients.Clear();
            }
            Console.WriteLine("Modbus TCP服务器已停止");
        }

        private void ListenForClients()
        {
            while (_isRunning)
            {
                try
                {
                    TcpClient client = _listener.AcceptTcpClient();
                    lock (_clients) { _clients.Add(client); }
                    
                    Thread clientThread = new Thread(HandleClient)
                    {
                        IsBackground = true
                    };
                    clientThread.Start(client);
                    
                    Console.WriteLine($"客户端连接:{client.Client.RemoteEndPoint}");
                }
                catch (SocketException)
                {
                    // 服务器停止时会触发
                    break;
                }
            }
        }

        private void HandleClient(object obj)
        {
            TcpClient client = (TcpClient)obj;
            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[260]; // Modbus TCP最大ADULength

            try
            {
                while (_isRunning && client.Connected)
                {
                    int bytesRead = stream.Read(buffer, 0, buffer.Length);
                    if (bytesRead == 0) break; // 客户端断开

                    // ParseMBAP Head
                    ushort transId = (ushort)((buffer[0] << 8) | buffer[1]);
                    ushort protoId = (ushort)((buffer[2] << 8) | buffer[3]);
                    ushort length = (ushort)((buffer[4] << 8) | buffer[5]);
                    byte unitId = buffer[6];
                    byte funcCode = buffer[7];

                    // 处理请求并生成响应
                    byte[] response = ProcessRequest(buffer, bytesRead, transId, unitId, funcCode);
                    
                    // 发送响应
                    stream.Write(response, 0, response.Length);
                    stream.Flush();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"客户端异常:{ex.Message}");
            }
            finally
            {
                client.Close();
                lock (_clients) { _clients.Remove(client); }
                Console.WriteLine($"客户端断开:{client.Client.RemoteEndPoint}");
            }
        }

        private byte[] ProcessRequest(byte[] request, int length, 
            ushort transId, byte unitId, byte funcCode)
        {
            try
            {
                switch (funcCode)
                {
                    case 0x01: return ReadCoils(request, transId, unitId);
                    case 0x02: return ReadDiscreteInputs(request, transId, unitId);
                    case 0x03: return ReadHolding회원가입s(request, transId, unitId);
                    case 0x04: return ReadInput회원가입s(request, transId, unitId);
                    case 0x05: return WriteSingleCoil(request, transId, unitId);
                    case 0x06: return WriteSingle회원가입(request, transId, unitId);
                    case 0x0F: return WriteMultipleCoils(request, transId, unitId);
                    case 0x10: return WriteMultiple회원가입s(request, transId, unitId);
                    default:
                        return BuildExceptionResponse(transId, unitId, funcCode, 0x01);
                }
            }
            catch (IndexOutOfRangeException)
            {
                return BuildExceptionResponse(transId, unitId, funcCode, 0x02);
            }
            catch (Exception)
            {
                return BuildExceptionResponse(transId, unitId, funcCode, 0x04);
            }
        }

        // function code01:读线圈
        private byte[] ReadCoils(byte[] req, ushort transId, byte unitId)
        {
            ushort startAddr = (ushort)((req[8] << 8) | req[9]);
            ushort quantity = (ushort)((req[10] << 8) | req[11]);
            
            bool[] coils = _dataStore.GetCoils(startAddr, quantity);
            byte byteCount = (byte)((quantity + 7) / 8);
            
            byte[] response = new byte[9 + byteCount];
            BuildMBAPHeader(response, transId, unitId, (ushort)(3 + byteCount));
            response[7] = 0x01;
            response[8] = byteCount;
            
            for (int i = 0; i < quantity; i++)
            {
                if (coils[i])
                    response[9 + i / 8] |= (byte)(1 << (i % 8));
            }
            return response;
        }

        // function code03:read holding registers
        private byte[] ReadHolding회원가입s(byte[] req, ushort transId, byte unitId)
        {
            ushort startAddr = (ushort)((req[8] << 8) | req[9]);
            ushort quantity = (ushort)((req[10] << 8) | req[11]);
            
            ushort[] regs = _dataStore.GetHoldingRegs(startAddr, quantity);
            byte byteCount = (byte)(quantity * 2);
            
            byte[] response = new byte[9 + byteCount];
            BuildMBAPHeader(response, transId, unitId, (ushort)(3 + byteCount));
            response[7] = 0x03;
            response[8] = byteCount;
            
            for (int i = 0; i < quantity; i++)
            {
                response[9 + i * 2] = (byte)(regs[i] >> 8);
                response[9 + i * 2 + 1] = (byte)(regs[i] & 0xFF);
            }
            return response;
        }

        // function code05:write single coil
        private byte[] WriteSingleCoil(byte[] req, ushort transId, byte unitId)
        {
            ushort addr = (ushort)((req[8] << 8) | req[9]);
            ushort value = (ushort)((req[10] << 8) | req[11]);
            
            _dataStore.SetCoil(addr, value == 0xFF00);
            
            // 回显请求
            byte[] response = new byte[12];
            BuildMBAPHeader(response, transId, unitId, 6);
            Array.Copy(req, 7, response, 7, 5);
            return response;
        }

        // function code06:write single register
        private byte[] WriteSingle회원가입(byte[] req, ushort transId, byte unitId)
        {
            ushort addr = (ushort)((req[8] << 8) | req[9]);
            ushort value = (ushort)((req[10] << 8) | req[11]);
            
            _dataStore.SetHoldingReg(addr, value);
            
            byte[] response = new byte[12];
            BuildMBAPHeader(response, transId, unitId, 6);
            Array.Copy(req, 7, response, 7, 5);
            return response;
        }

        // function code10:write multiple registers
        private byte[] WriteMultiple회원가입s(byte[] req, ushort transId, byte unitId)
        {
            ushort startAddr = (ushort)((req[8] << 8) | req[9]);
            ushort quantity = (ushort)((req[10] << 8) | req[11]);
            
            ushort[] values = new ushort[quantity];
            for (int i = 0; i < quantity; i++)
            {
                values[i] = (ushort)((req[13 + i * 2] << 8) | req[13 + i * 2 + 1]);
            }
            _dataStore.SetHoldingRegs(startAddr, values);
            
            byte[] response = new byte[12];
            BuildMBAPHeader(response, transId, unitId, 6);
            response[7] = 0x10;
            response[8] = req[8];
            response[9] = req[9];
            response[10] = req[10];
            response[11] = req[11];
            return response;
        }

        // 构建MBAP Head
        private void BuildMBAPHeader(byte[] buffer, ushort transId, byte unitId, ushort length)
        {
            buffer[0] = (byte)(transId >> 8);
            buffer[1] = (byte)(transId & 0xFF);
            buffer[2] = 0x00; // ProtocolID高
            buffer[3] = 0x00; // ProtocolID低
            buffer[4] = (byte)(length >> 8);
            buffer[5] = (byte)(length & 0xFF);
            buffer[6] = unitId;
        }

        // 构建异常响应
        private byte[] BuildExceptionResponse(ushort transId, byte unitId, 
            byte funcCode, byte exceptionCode)
        {
            byte[] response = new byte[9];
            BuildMBAPHeader(response, transId, unitId, 3);
            response[7] = (byte)(funcCode | 0x80);
            response[8] = exceptionCode;
            return response;
        }
    }
}

三、Modbus TCP客户端实现

using System;
using System.Net.Sockets;

namespace ModbusTcpClient
{
    public class ModbusTcpClient : IDisposable
    {
        private TcpClient _client;
        private NetworkStream _stream;
        private ushort _transactionId = 0;
        
        public string Host { get; }
        public int Port { get; }
        public byte UnitId { get; set; } = 1;
        public int Timeout { get; set; } = 3000;

        public ModbusTcpClient(string host, int port = 502)
        {
            Host = host;
            Port = port;
        }

        public void Connect()
        {
            _client = new TcpClient();
            _client.ReceiveTimeout = Timeout;
            _client.SendTimeout = Timeout;
            _client.Connect(Host, Port);
            _stream = _client.GetStream();
            Console.WriteLine($"已连接到 {Host}:{Port}");
        }

        public void Disconnect()
        {
            _stream?.Close();
            _client?.Close();
        }

        // read holding registers(function code03)
        public ushort[] ReadHolding회원가입s(ushort startAddr, ushort quantity)
        {
            byte[] request = BuildReadRequest(0x03, startAddr, quantity);
            byte[] response = SendAndReceive(request);
            
            if (response[7] != 0x03)
                throw new ModbusException(response[7], response[8]);
            
            byte byteCount = response[8];
            ushort[] result = new ushort[byteCount / 2];
            for (int i = 0; i < result.Length; i++)
            {
                result[i] = (ushort)((response[9 + i * 2] << 8) | response[9 + i * 2 + 1]);
            }
            return result;
        }

        // 读线圈(function code01)
        public bool[] ReadCoils(ushort startAddr, ushort quantity)
        {
            byte[] request = BuildReadRequest(0x01, startAddr, quantity);
            byte[] response = SendAndReceive(request);
            
            if (response[7] != 0x01)
                throw new ModbusException(response[7], response[8]);
            
            byte byteCount = response[8];
            bool[] result = new bool[quantity];
            for (int i = 0; i < quantity; i++)
            {
                result[i] = (response[9 + i / 8] & (1 << (i % 8))) != 0;
            }
            return result;
        }

        // write single register(function code06)
        public void WriteSingle회원가입(ushort addr, ushort value)
        {
            byte[] request = new byte[12];
            BuildMBAPHeader(request, 6);
            request[7] = 0x06;
            request[8] = (byte)(addr >> 8);
            request[9] = (byte)(addr & 0xFF);
            request[10] = (byte)(value >> 8);
            request[11] = (byte)(value & 0xFF);
            
            byte[] response = SendAndReceive(request);
            if (response[7] != 0x06)
                throw new ModbusException(response[7], response[8]);
        }

        // write single coil(function code05)
        public void WriteSingleCoil(ushort addr, bool value)
        {
            byte[] request = new byte[12];
            BuildMBAPHeader(request, 6);
            request[7] = 0x05;
            request[8] = (byte)(addr >> 8);
            request[9] = (byte)(addr & 0xFF);
            request[10] = (byte)(value ? 0xFF : 0x00);
            request[11] = 0x00;
            
            byte[] response = SendAndReceive(request);
            if (response[7] != 0x05)
                throw new ModbusException(response[7], response[8]);
        }

        // write multiple registers(function code10)
        public void WriteMultiple회원가입s(ushort startAddr, ushort[] values)
        {
            byte byteCount = (byte)(values.Length * 2);
            byte[] request = new byte[13 + byteCount];
            BuildMBAPHeader(request, (ushort)(7 + byteCount));
            request[7] = 0x10;
            request[8] = (byte)(startAddr >> 8);
            request[9] = (byte)(startAddr & 0xFF);
            request[10] = (byte)(values.Length >> 8);
            request[11] = (byte)(values.Length & 0xFF);
            request[12] = byteCount;
            
            for (int i = 0; i < values.Length; i++)
            {
                request[13 + i * 2] = (byte)(values[i] >> 8);
                request[13 + i * 2 + 1] = (byte)(values[i] & 0xFF);
            }
            
            byte[] response = SendAndReceive(request);
            if (response[7] != 0x10)
                throw new ModbusException(response[7], response[8]);
        }

        private byte[] BuildReadRequest(byte funcCode, ushort startAddr, ushort quantity)
        {
            byte[] request = new byte[12];
            BuildMBAPHeader(request, 6);
            request[7] = funcCode;
            request[8] = (byte)(startAddr >> 8);
            request[9] = (byte)(startAddr & 0xFF);
            request[10] = (byte)(quantity >> 8);
            request[11] = (byte)(quantity & 0xFF);
            return request;
        }

        private void BuildMBAPHeader(byte[] buffer, ushort length)
        {
            _transactionId++;
            buffer[0] = (byte)(_transactionId >> 8);
            buffer[1] = (byte)(_transactionId & 0xFF);
            buffer[2] = 0x00;
            buffer[3] = 0x00;
            buffer[4] = (byte)(length >> 8);
            buffer[5] = (byte)(length & 0xFF);
            buffer[6] = UnitId;
        }

        private byte[] SendAndReceive(byte[] request)
        {
            _stream.Write(request, 0, request.Length);
            _stream.Flush();
            
            byte[] buffer = new byte[260];
            int bytesRead = _stream.Read(buffer, 0, buffer.Length);
            
            byte[] response = new byte[bytesRead];
            Array.Copy(buffer, response, bytesRead);
            return response;
        }

        public void Dispose()
        {
            Disconnect();
        }
    }

    public class ModbusException : Exception
    {
        public byte FunctionCode { get; }
        public byte ExceptionCode { get; }
        
        public ModbusException(byte funcCode, byte exceptionCode)
            : base($"Modbus异常:function code=0x{funcCode:X2},Exception Code=0x{exceptionCode:X2}")
        {
            FunctionCode = funcCode;
            ExceptionCode = exceptionCode;
        }
    }
}

四、完整使用示例

using System;
using System.Threading;

namespace ModbusTcpDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 启动服务器
            var server = new ModbusTcpServer.ModbusTcpServer(502);
            server.Start();
            
            // 设置一些初始数据
            server.DataStore.SetHoldingReg(0, 100);
            server.DataStore.SetHoldingReg(1, 200);
            server.DataStore.SetHoldingReg(2, 300);
            server.DataStore.SetCoil(0, true);
            server.DataStore.SetInputReg(0, 512);

            // 等待服务器启动
            Thread.Sleep(500);

            // 创建客户端
            using (var client = new ModbusTcpClient.ModbusTcpClient("127.0.0.1", 502))
            {
                client.Connect();
                client.UnitId = 1;

                // 1. read holding registers
                Console.WriteLine("=== read holding registers ===");
                ushort[] regs = client.ReadHolding회원가입s(0, 5);
                for (int i = 0; i < regs.Length; i++)
                    Console.WriteLine($"register{i}:{regs[i]}");

                // 2. write single register
                Console.WriteLine("\n=== write single register ===");
                client.WriteSingle회원가입(3, 999);
                ushort[] check = client.ReadHolding회원가입s(3, 1);
                Console.WriteLine($"写入后读取:{check[0]}");

                // 3. 读线圈
                Console.WriteLine("\n=== 读线圈 ===");
                bool[] coils = client.ReadCoils(0, 4);
                for (int i = 0; i < coils.Length; i++)
                    Console.WriteLine($"线圈{i}:{(coils[i] ? "ON" : "OFF")}");

                // 4. 写线圈
                Console.WriteLine("\n=== 写线圈 ===");
                client.WriteSingleCoil(1, true);
                coils = client.ReadCoils(1, 1);
                Console.WriteLine($"线圈1:{(coils[0] ? "ON" : "OFF")}");

                // 5. 批量写寄存器
                Console.WriteLine("\n=== 批量写寄存器 ===");
                client.WriteMultiple회원가입s(10, new ushort[] { 11, 22, 33, 44 });
                regs = client.ReadHolding회원가입s(10, 4);
                for (int i = 0; i < regs.Length; i++)
                    Console.WriteLine($"register{10 + i}:{regs[i]}");
            }

            Console.WriteLine("\n按任意键退出...");
            Console.ReadKey();
            server.Stop();
        }
    }
}

五、关键技术说明

5.1 MBAP Head结构

Modbus TCP使用MBAP(Modbus Application Protocol)头替代RTU的从站地址和CRC。MBAP Head共7字节:Transaction identifier(2字节)、Protocol Identifier(2字节,固定0x0000)、Length(2字节)、Unit identifier(1字节)。事务ID用于匹配请求和响应,服务器必须原样返回。

5.2 多客户端处理

服务器使用TcpListener.AcceptTcpClient()接受连接,每个客户端分配独立线程处理。数据存储类使用lock实现线程安全,确保多客户端同时访问时数据一致性。

5.3 异常处理

Mod bus 예외 응답 의 가장 높은 기능 코 드는 1 (원 래 기능 코드 + 0 x 8 0) 이며 1 바 이트 예외 코 드가 뒤 따 릅니다 .일반적인 예외 코드 : 0 x 01 불법 기능 코드 , 0 x 02 불법 주소 , 0 x 03 불법 데이터 값 , 0 x 04 슬 레이 브 장치 장애 .

6. 테스트 및 검 증

  • 使用Modbus Poll测试
  • 使用Modbus Slave模拟
  • Wireshark抓包
  • 压力测试

VII . 확장 제안

  • 异步模式
  • 日志记录
  • 配置文件
  • 数据持久化
  • Modbus RTU网关
  • 心跳检测
  • 权限控制

이 문서 에서 제공하는 C # Mod bus TCP 서버 및 클 라이언 트 코 드는 산업 자동 화 프로젝트 개발 에 직접 사용할 수있는 완전한 표준 기능 코드를 구현 합니다 .서버 는 다 중 클 라이언 트 동 시 액세 스를 지원 하며 클 라이언 트는 간단한 API 인터페 이 스를 제공합니다 .로 컬 백 루 프 주소 (12 7. 0. 0. 1) 에서 테스트 를 통과 한 다음 실제 장치를 연결 하여 검 증을 하는 것이 좋습니다 .

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 *.