DotNetty 实现 Modbus TCP 系列 (四) Client & Server

本文已收录至:开源 DotNetty 实现的 Modbus TCP/IP 协议

Client

public class ModbusClient
{
    public string Ip { get; }
    public int Port { get; }
    public short UnitIdentifier { get; }
    public IChannel Channel { get; private set; }

    private MultithreadEventLoopGroup group;
    private ConnectionState connectionState;
    private ushort transactionIdentifier;
    private readonly string handlerName = "response";

    public ModbusClient(short unitIdentifier, string ip, int port = 502)
    {
        Ip = ip;
        Port = port;
        UnitIdentifier = unitIdentifier;

        connectionState = ConnectionState.NotConnected;
    }

    public async Task Connect()
    {
        group = new MultithreadEventLoopGroup();

        try
        {
            var bootstrap = new Bootstrap();
            bootstrap
                .Group(group)
                .Channel<TcpSocketChannel>()
                .Option(ChannelOption.TcpNodelay, true)
                .Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;

                    pipeline.AddLast("encoder", new ModbusEncoder());
                    pipeline.AddLast("decoder", new ModbusDecoder(false));

                    pipeline.AddLast(handlerName, new ModbusResponseHandler());
                }));

            connectionState = ConnectionState.Pending;

            Channel = await bootstrap.ConnectAsync(new IPEndPoint(IPAddress.Parse(Ip), Port));

            connectionState = ConnectionState.Connected;
        }
        catch (Exception exception)
        {
            throw exception;
        }
    }

    public async Task Close()
    {
        if (ConnectionState.Connected == connectionState)
        {
            try
            {
                await Channel.CloseAsync();
            }
            finally
            {
                await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));

                connectionState = ConnectionState.NotConnected;
            }
        }
    }

    public ushort CallModbusFunction(ModbusFunction function)
    {
        if (ConnectionState.Connected != connectionState || Channel == null)
        {
            throw new Exception("Not connected!");
        }

        SetTransactionIdentifier();

        ModbusHeader header = new ModbusHeader(transactionIdentifier, UnitIdentifier);
        ModbusFrame frame = new ModbusFrame(header, function);
        Channel.WriteAndFlushAsync(frame);

        return transactionIdentifier;
    }

    public T CallModbusFunctionSync<T>(ModbusFunction function) where T : ModbusFunction
    {
        var transactionIdentifier = CallModbusFunction(function);

        var handler = (ModbusResponseHandler)Channel.Pipeline.Get(handlerName);
        if (handler == null)
        {
            throw new Exception("Not connected!");
        }

        return (T)handler.GetResponse(transactionIdentifier).Function;
    }

    private void SetTransactionIdentifier()
    {
        if (transactionIdentifier < ushort.MaxValue)
        {
            transactionIdentifier++;
        }
        else
        {
            transactionIdentifier = 1;
        }
    }

    public ushort ReadHoldingRegistersAsync(ushort startingAddress, ushort quantity)
    {
        var function = new ReadHoldingRegistersRequest(startingAddress, quantity);
        return CallModbusFunction(function);
    }

    public ReadHoldingRegistersResponse ReadHoldingRegisters(ushort startingAddress, ushort quantity)
    {
        var function = new ReadHoldingRegistersRequest(startingAddress, quantity);
        return CallModbusFunctionSync<ReadHoldingRegistersResponse>(function);
    }
}

public enum ConnectionState
{
    NotConnected = 0,
    Connected = 1,
    Pending = 2,
}

(文中代码仅添加了 0x03 的方法)

在 Client 中封装了 Modbus 请求方法,对同一个功能同时有同步方法(ReadHoldingRegistersAsync)和异步方法(ReadHoldingRegisters)。同步方法仅返回 TransactionIdentifier(传输标识),异步方法返回响应结果。

ModbusResponseHandler 修改为:

public class ModbusResponseHandler : SimpleChannelInboundHandler<ModbusFrame>
{
    private readonly int timeoutMilliseconds = 2000;
    private Dictionary<ushort, ModbusFrame> responses = new Dictionary<ushort, ModbusFrame>();
    protected override void ChannelRead0(IChannelHandlerContext ctx, ModbusFrame msg)
    {
        responses.Add(msg.Header.TransactionIdentifier, msg);
    }

    public ModbusFrame GetResponse(ushort transactionIdentifier)
    {
        ModbusFrame frame = null;
        var timeoutDateTime = DateTime.Now.AddMilliseconds(timeoutMilliseconds);
        do
        {
            Thread.Sleep(1);
            if (responses.ContainsKey(transactionIdentifier))
            {
                frame = responses[transactionIdentifier];
                responses.Remove(transactionIdentifier);
            }
        }
        while (frame == null && DateTime.Now < timeoutDateTime);

        if(frame == null)
        {
            throw new Exception("No Response");
        }
        else if(frame.Function is ExceptionFunction)
        {
            throw new Exception(frame.Function.ToString());
        }

        return frame;
    }

    public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)
    {
        context.CloseAsync();
    }
}

Server

public class ModbusServer
{
    private ModbusResponseService responseService;
    private ServerState serverState;
    public int Port { get; }
    public IChannel Channel { get; private set; }
    private IEventLoopGroup bossGroup;
    private IEventLoopGroup workerGroup;
    public ModbusServer(ModbusResponseService responseService, int port = 502)
    {
        this.responseService = responseService;
        Port = port;
        serverState = ServerState.NotStarted;
    }

    public async Task Start()
    {
        bossGroup = new MultithreadEventLoopGroup(1);
        workerGroup = new MultithreadEventLoopGroup();

        try
        {
            var bootstrap = new ServerBootstrap();
            bootstrap.Group(bossGroup, workerGroup);

            bootstrap
                .Channel<TcpServerSocketChannel>()
                .Option(ChannelOption.SoBacklog, 100)
                .ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;
                    pipeline.AddLast("encoder", new ModbusEncoder());
                    pipeline.AddLast("decoder", new ModbusDecoder(true));

                    pipeline.AddLast("request", new ModbusRequestHandler(responseService));
                }));

            serverState = ServerState.Starting;

            Channel = await bootstrap.BindAsync(Port);

            serverState = ServerState.Started;
        }
        finally
        {

        }
    }

    public async Task Stop()
    {
        if (ServerState.Starting == serverState)
        {
            try
            {
                await Channel.CloseAsync();
            }
            finally
            {
                await Task.WhenAll(
                    bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),
                    workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));

                serverState = ServerState.NotStarted;
            }
        }
    }
}

public enum ServerState
{
    NotStarted = 0,
    Started = 1,
    Starting = 2,
}

实例化 Server 时需要传入 ModbusResponseService 的实现,实现示例:

public class ModbusResponse : ModbusResponseService
{
    public override ModbusFunction ReadHoldingRegisters(ReadHoldingRegistersRequest request)
    {
        var registers = ReadRegisters(request.Quantity);
        var response = new ReadHoldingRegistersResponse(registers);

        return response;
    }

    private ushort[] ReadRegisters(ushort quantity)
    {
        var registers = new ushort[quantity];

        Random ran = new Random();
        for (int i = 0; i < registers.Length; i++)
        {
            registers[i] = (ushort)ran.Next(ushort.MinValue, ushort.MaxValue);
        }

        return registers;
    }
}

(文中代码仅添加了 0x03 的方法)

开源地址:modbus-tcp

原文地址:https://www.cnblogs.com/victorbu/p/10371056.html

时间: 2024-08-24 23:29:04

DotNetty 实现 Modbus TCP 系列 (四) Client & Server的相关文章

Exchange Server 2013系列四:小企业邮件系统部署

Exchange Server 2013 SP1 系列四:小企业部署邮件服务器 杜飞 Exchange 服务器功能强大,不再只是一个邮件系统,还是一个复杂的消息传递平台,它通过相关组件协同工作以提供一个全面的解决方案,包括邮件传递.邮件访问.语音邮件.传真.联系人.日历等.今天咱们就看一下如何在小规模企业中部署Exchange Server2013 Sp1.一般小规模企业预算有限,本着经济实用的方针,会考虑多角色并存的部署方式,基本拓扑如下图所示: 硬件要求: 处理器:支持 Intel 64 位

Modbus库开发笔记之三:Modbus TCP Server开发

在完成了前面的工作后,我们就可以实现有针对性的应用了,首先我们来实现Modbus TCP的服务器端应用.当然我们不是做具体的应用,而是对Modbus TCP的服务器端应用进行封装以供有需要时调用. 这里我们不涉及TCP的协议,这部分与Modbus没有必然联系,我们只是在其应用层运行Modbus协议而已. 对于Modbus TCP的服务器我们需要实现几个功能:首先是对接收到客户端命令进行解析,我们只实现前面提到的8中常用的功能吗的支持.其次在解析完成后,我们要实现对应各种功能码的操作.具体架构如下

Lync Server 2010 安装部署系列四:安装&配置证书服务器

1.打开"服务器管理器" 2.添加角色 3.单击下一步按钮 4.勾选"Active Directory证书服务" 5.单击"下一步"按钮: 6.勾选"证书颁发机构"和"证书颁发机构Web注册",单击"下一步"按钮: 7.勾选"企业",单击"下一步"按钮: 8.勾选"根CA",单击"下一步"按钮: 9.勾选&q

Modbus库开发笔记之四:Modbus TCP Client开发

这一次我们封装Modbus TCP Client应用.同样的我们也不是做具体的应用,而是实现TCP客户端的基本功能.我们将TCP客户端的功能封装为函数,以便在开发具体应用时调用. 对于TCP客户端我们主要实现的功能有两个:其一是生成访问TCP服务器的命令,总共支持8中功能码.其二是对TCP服务器端返回的信息进行解析并根据结果进行各种操作,同样也是支持8中功能吗的操作.具体软件访问结构如下: 1.访问命令的生成 客户端作为主动交互端,需要向服务器发各种操作请求命令.所以对于TCP客户端来说,首先要

SQL Server 2008空间数据应用系列四:基础空间对象与函数应用

原文:SQL Server 2008空间数据应用系列四:基础空间对象与函数应用 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Server 2008 R2调测. 2.具备 Transact-SQL 编程经验和使用 SQL Server Management Studio 的经验. 3.熟悉或了解Microsoft SQL Server 2008中的空间数据类型. 4.具备相应(比如OGC)的GIS专业理论知识. 5.其他相关知识. SQL Server 2

Modbus库开发笔记之九:利用协议栈开发Modbus TCP Server应用

前面我们已经完成了Modbus协议栈的开发,但这不是我们的目的.我们开发它的目的当然是要使用它来解决我们的实际问题.接下来我们就使用刚开发的Modbus协议栈开发一个Modbus TCP Server应用. 开发Modbus TCP Server首先需要有TCP Server的支持以及网络的配置等,但这些与Modbus本身没有什么关系,我们再次不作讨论.我们规定网络和TCP Server已经配置妥当.接下来我们讨论Modbus TCP Server的实现过程. 根据前面对协议栈的封装,我们需要引

TCP系列36—窗口管理&流控—10、linux下的异常报文系列接收

在这篇文章中我们看一下server端在接收到异常数据系列时的处理,主要目的是通过wireshark示例对这些异常数据系列的处理有一个直观的认识,感兴趣的自行阅读相关代码和协议,这里不再进行详细介绍 在进行下面的测试前,首先如下设置相关的参数,其中window参数指定了到127.0.0.2的tcp连接的最大接收窗口. [email protected]:/home/******/tcp12# ip route change local 127.0.0.2 dev lo window 40 一.wi

So Easy! Oracle在Linux上的安装配置系列四

So Easy! Oracle在Linux上的安装配置系列四  监听器的配置 在创建了数库和各种数据库对象并装载了数据后,下一步是在数据库服务器与使用它的用户之间建立连 接,Oracle Net Services使这种连接成为可能.Oracle Net Services组件必须"存活"在客户机和服务器上,它们一般使用TCP/IP网络协议来建立客户机和数据库服务器之间的网络连接. 本文官方文档位置: http://docs.oracle.com/cd/E11882_01/network.

TCP系列33—窗口管理&流控—7、Silly Window Syndrome(SWS)

一.SWS介绍 前面我们已经通过示例看到如果接收端的应用层一直没有读取数据,那么window size就会慢慢变小最终可能变为0,此时我们假设一种场景,如果应用层读取少量数据(比如十几bytes),接收端TCP有了少量的新的接收缓存后如果立即进行window update把新的window size通告发送端的话,发送端如果立即发送数据,那么接收端缓存可能又会立即耗尽,window size又变为0,接着应用层重复读取少量数据,这个过程重复的话,那么发送端就会频繁的发送大量的小包,这种场景我们就