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

유료 ¥4.99유료 ¥4.99

이 콘텐츠는 회원/유료 시스템과 연동되어 있으며, 잠금 해제 후 전체 자료를 보거나 첨부파일을 다운로드할 수 있습니다.

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

C#是Windows平台工业软件개발的主流언어,广泛적용于上位机、SCADA系统、데이터采集软件等领域。本文提供完整的C# Modbus TCP服务器和클라이언트实现代码,包括TCP监听、多클라이언트管理、Modbus프로토콜분석、레지스터 매핑、클라이언트读写操作以及完整的使用예제。代码基于.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 레지스터 매핑

주소范围유형설명
0x0000-0x03FF코일(Coil)1024个可读写布尔量
0x0000-0x03FF이산 입력1024个只读布尔量
0x0000-0x03FF유지 레지스터1024个可读写16位整数
0x0000-0x03FF입력 레지스터1024个只读16位整数

二、Modbus TCP服务器实现

2.1 데이터存储类(Modbus데이터Store.cs)

using System;

namespace ModbusTcpServer
{
    /// <summary>
    /// Modbus데이터存储,线程安全
    /// </summary>
    public class Modbus데이터Store
    {
        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 주소)
        {
            lock (_lock) { return _coils[주소]; }
        }
        
        public void SetCoil(ushort 주소, bool value)
        {
            lock (_lock) { _coils[주소] = value; }
        }
        
        public bool[] GetCoils(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                bool[] result = new bool[count];
                Array.복사(_coils, 시작Addr, result, 0, count);
                return result;
            }
        }
        
        public void SetCoils(ushort 시작Addr, bool[] values)
        {
            lock (_lock)
            {
                Array.복사(values, 0, _coils, 시작Addr, values.Length);
            }
        }

        // 이산 입력操作
        public bool[] GetInputs(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                bool[] result = new bool[count];
                Array.복사(_inputs, 시작Addr, result, 0, count);
                return result;
            }
        }
        
        public void SetInput(ushort 주소, bool value)
        {
            lock (_lock) { _inputs[주소] = value; }
        }

        // 유지 레지스터操作
        public ushort GetHoldingReg(ushort 주소)
        {
            lock (_lock) { return _holdingRegs[주소]; }
        }
        
        public void SetHoldingReg(ushort 주소, ushort value)
        {
            lock (_lock) { _holdingRegs[주소] = value; }
        }
        
        public ushort[] GetHoldingRegs(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                ushort[] result = new ushort[count];
                Array.복사(_holdingRegs, 시작Addr, result, 0, count);
                return result;
            }
        }
        
        public void SetHoldingRegs(ushort 시작Addr, ushort[] values)
        {
            lock (_lock)
            {
                Array.복사(values, 0, _holdingRegs, 시작Addr, values.Length);
            }
        }

        // 입력 레지스터操作
        public ushort[] GetInputRegs(ushort 시작Addr, ushort count)
        {
            lock (_lock)
            {
                ushort[] result = new ushort[count];
                Array.복사(_inputRegs, 시작Addr, result, 0, count);
                return result;
            }
        }
        
        public void SetInputReg(ushort 주소, ushort value)
        {
            lock (_lock) { _inputRegs[주소] = 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 Modbus데이터Store _데이터Store;
        private readonly List<TcpClient> _clients = new List<TcpClient>();
        private ushort _transactionId = 0;

        public int Port { get; } = 502;
        public Modbus데이터Store 데이터Store => _데이터Store;

        public ModbusTcpServer(int port = 502)
        {
            Port = port;
            _데이터Store = new Modbus데이터Store();
        }

        public void Start()
        {
            _listener = new TcpListener(IP주소.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.닫기();
                _clients.지우기();
            }
            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最大ADU길이

            try
            {
                while (_isRunning && client.Connected)
                {
                    int bytesRead = stream.Read(buffer, 0, buffer.Length);
                    if (bytesRead == 0) break; // 클라이언트断开

                    // 분석MBAP头
                    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.닫기();
                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);
            }
        }

        // 기능 코드01:读코일
        private byte[] ReadCoils(byte[] req, ushort transId, byte unitId)
        {
            ushort 시작Addr = (ushort)((req[8] << 8) | req[9]);
            ushort 수량 = (ushort)((req[10] << 8) | req[11]);
            
            bool[] coils = _데이터Store.GetCoils(시작Addr, 수량);
            byte byteCount = (byte)((수량 + 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 < 수량; i++)
            {
                if (coils[i])
                    response[9 + i / 8] |= (byte)(1 << (i % 8));
            }
            응답 반환;
        }

        // 기능 코드03:读유지 레지스터
        private byte[] ReadHolding회원가입s(byte[] req, ushort transId, byte unitId)
        {
            ushort 시작Addr = (ushort)((req[8] << 8) | req[9]);
            ushort 수량 = (ushort)((req[10] << 8) | req[11]);
            
            ushort[] regs = _데이터Store.GetHoldingRegs(시작Addr, 수량);
            byte byteCount = (byte)(수량 * 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 < 수량; i++)
            {
                response[9 + i * 2] = (byte)(regs[i] >> 8);
                response[9 + i * 2 + 1] = (byte)(regs[i] & 0xFF);
            }
            응답 반환;
        }

        // 기능 코드05:写单个코일
        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]);
            
            _데이터Store.SetCoil(addr, value == 0xFF00);
            
            // 回显请求
            byte[] response = new byte[12];
            BuildMBAPHeader(response, transId, unitId, 6);
            Array.복사(req, 7, response, 7, 5);
            응답 반환;
        }

        // 기능 코드06:写单个레지스터
        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]);
            
            _데이터Store.SetHoldingReg(addr, value);
            
            byte[] response = new byte[12];
            BuildMBAPHeader(response, transId, unitId, 6);
            Array.복사(req, 7, response, 7, 5);
            응답 반환;
        }

        // 기능 코드10:写多个레지스터
        private byte[] WriteMultiple회원가입s(byte[] req, ushort transId, byte unitId)
        {
            ushort 시작Addr = (ushort)((req[8] << 8) | req[9]);
            ushort 수량 = (ushort)((req[10] << 8) | req[11]);
            
            ushort[] values = new ushort[수량];
            for (int i = 0; i < 수량; i++)
            {
                values[i] = (ushort)((req[13 + i * 2] << 8) | req[13 + i * 2 + 1]);
            }
            _데이터Store.SetHoldingRegs(시작Addr, 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];
            응답 반환;
        }

        // 构建MBAP头
        private void BuildMBAPHeader(byte[] buffer, ushort transId, byte unitId, ushort length)
        {
            buffer[0] = (byte)(transId >> 8);
            buffer[1] = (byte)(transId & 0xFF);
            buffer[2] = 0x00; // 프로토콜ID高
            buffer[3] = 0x00; // 프로토콜ID低
            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;
            응답 반환;
        }
    }
}

三、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?.닫기();
            _client?.닫기();
        }

        // 读유지 레지스터(기능 코드03)
        public ushort[] ReadHolding회원가입s(ushort 시작Addr, ushort 수량)
        {
            byte[] request = BuildReadRequest(0x03, 시작Addr, 수량);
            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;
        }

        // 读코일(기능 코드01)
        public bool[] ReadCoils(ushort 시작Addr, ushort 수량)
        {
            byte[] request = BuildReadRequest(0x01, 시작Addr, 수량);
            byte[] response = SendAndReceive(request);
            
            if (response[7] != 0x01)
                throw new ModbusException(response[7], response[8]);
            
            byte byteCount = response[8];
            bool[] result = new bool[수량];
            for (int i = 0; i < 수량; i++)
            {
                result[i] = (response[9 + i / 8] & (1 << (i % 8))) != 0;
            }
            return result;
        }

        // 写单个레지스터(기능 코드06)
        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]);
        }

        // 写单个코일(기능 코드05)
        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]);
        }

        // 写多个레지스터(기능 코드10)
        public void WriteMultiple회원가입s(ushort 시작Addr, 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)(시작Addr >> 8);
            request[9] = (byte)(시작Addr & 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 시작Addr, ushort 수량)
        {
            byte[] request = new byte[12];
            BuildMBAPHeader(request, 6);
            request[7] = funcCode;
            request[8] = (byte)(시작Addr >> 8);
            request[9] = (byte)(시작Addr & 0xFF);
            request[10] = (byte)(수량 >> 8);
            request[11] = (byte)(수량 & 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.복사(buffer, response, bytesRead);
            응답 반환;
        }

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

    public class ModbusException : Exception
    {
        public byte 기능Code { get; }
        public byte ExceptionCode { get; }
        
        public ModbusException(byte funcCode, byte exceptionCode)
            : base($"Modbus异常:기능 코드=0x{funcCode:X2},异常码=0x{exceptionCode:X2}")
        {
            기능Code = 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.데이터Store.SetHoldingReg(0, 100);
            server.데이터Store.SetHoldingReg(1, 200);
            server.데이터Store.SetHoldingReg(2, 300);
            server.데이터Store.SetCoil(0, true);
            server.데이터Store.SetInputReg(0, 512);

            // 대기服务器启动
            Thread.Sleep(500);

            // 创建클라이언트
            using (var client = new ModbusTcpClient.ModbusTcpClient("127.0.0.1", 502))
            {
                client.Connect();
                client.UnitId = 1;

                // 1. 读유지 레지스터
                Console.WriteLine("=== 读유지 레지스터 ===");
                ushort[] regs = client.ReadHolding회원가입s(0, 5);
                for (int i = 0; i < regs.Length; i++)
                    Console.WriteLine($"레지스터{i}:{regs[i]}");

                // 2. 写单个레지스터
                Console.WriteLine("\n=== 写单个레지스터 ===");
                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($"레지스터{10 + i}:{regs[i]}");
            }

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

五、关键기술설명

5.1 MBAP头结构

Modbus TCP使用MBAP(Modbus Application Protocol)头替代RTU的슬레이브 주소和CRC。MBAP头共7바이트:트랜잭션 식별자(2바이트)、프로토콜 식별자(2바이트,固定0x0000)、길이(2바이트)、유닛 식별자(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) 에서 테스트 를 통과 한 다음 실제 장치를 연결 하여 검 증을 하는 것이 좋습니다 .

실제 프로젝트에 이 자료를 활용하시겠습니까?

도구 센터에서 프레임 분석, CRC 검증, 장비 디버깅을 진행하거나 요청을 제출하여 선정 및 연동 제안을 받아보세요.

엔지니어 회원

이 글을 실행 가능한 디버깅 자료로 활용하세요

가입하면 고급 프레임 분석, 자료 패키지 다운로드, 코드 예제, 엔지니어링 사례, 우선 기술 지원을 이용할 수 있어 실제 프로젝트 납품에 적합합니다.

고급 도구 무제한 이용
자료 패키지와 코드 패키지
완전한 엔지니어링 사례 라이브러리
우선 기술 지원 채널

답글 작성

이메일 주소는 공개되지 않습니다. 必填项已用 * 标注