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

Paid ¥4.99Paid ¥4.99

该内容已接入现有会員/有料系统,解锁后可完全な情報を見る或添付ファイルのダウンロード。

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

C#是Windowsプラットフォーム工业软件开发的主流语言,广泛应用于トップマシン、SCADAsystem、データの収集软件等领域。本記事提供完全的C# Modbus TCPサーバー·サーバー和クライアント側は実装代码,包括TCP监听、多クライアント側は管理、Modbusプロトコル解析、Register 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 Register 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 ReadHoldingRegisters(request, transId, unitId);
                    case 0x04: return ReadInputRegisters(request, transId, unitId);
                    case 0x05: return WriteSingleCoil(request, transId, unitId);
                    case 0x06: return WriteSingleRegister(request, transId, unitId);
                    case 0x0F: return WriteMultipleCoils(request, transId, unitId);
                    case 0x10: return WriteMultipleRegisters(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[] ReadHoldingRegisters(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[] WriteSingleRegister(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[] WriteMultipleRegisters(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[] ReadHoldingRegisters(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 WriteSingleRegister(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 WriteMultipleRegisters(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.ReadHoldingRegisters(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.WriteSingleRegister(3, 999);
                ushort[] check = client.ReadHoldingRegisters(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.WriteMultipleRegisters(10, new ushort[] { 11, 22, 33, 44 });
                regs = client.ReadHoldingRegisters(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 *.