dotnetty android 交互,DotNetty 实现 Modbus TCP 系列 (三) Codecs & Handler

DotNetty 作为一个半成品,我们不需要关注细节的实现,只需要关注自己的业务即可,所以最主要的就是处理 Codecs 和 Handler。

所有的 Codecs 和 Handler 均直接或间接继承自 ChannelHandlerAdapter。为什么要分为 Codecs 和 Handler,个人理解是 Codecs 负责将消息解码为我们所需的对象或者将处理的结果编码,Handler 对解码得到的对象进行逻辑处理,达到职责分离的目的。

DotNetty 中可以注册多个 Codecs/Handler,入站消息按照注册的先后顺序执行,出站消息按照注册的先后逆序执行。

对于 Client 端:

入站:ModbusDecoder --> ModbusResponseHandler

出站:ModbusEncoder

对于 Server 端:

入站:ModbusDecoder --> ModbusRequestHandler

出站:ModbusEncoder

ModbusDecoder

public class ModbusDecoder : ByteToMessageDecoder

{

private bool isServerMode;

private readonly short maxFunctionCode = 0x80;

private readonly string typeName = "Karonda.ModbusTcp.Entity.Function.{0}.{1}{0}";

public ModbusDecoder(bool isServerMode)

{

this.isServerMode = isServerMode;

}

protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List output)

{

//Transaction Identifier + Protocol Identifier + Length + Unit Identifier + Function Code

if (input.Capacity < 2 + 2 + 2 + 1 + 1)

{

return;

}

ModbusHeader header = new ModbusHeader(input);

short functionCode = input.ReadByte();

ModbusFunction function = null;

if(Enum.IsDefined(typeof(ModbusCommand), functionCode))

{

var command = Enum.GetName(typeof(ModbusCommand), functionCode);

function = (ModbusFunction)Activator.CreateInstance(Type.GetType(string.Format(typeName, isServerMode ? "Request" : "Response", command)));

}

if (functionCode >= maxFunctionCode)

{

function = new ExceptionFunction(functionCode);

}

else if(function == null)

{

function = new ExceptionFunction(functionCode, 0x01);

}

function.Decode(input);

ModbusFrame frame = new ModbusFrame(header, function);

output.Add(frame);

}

}

ModbusDecoder 继承了 ByteToMessageDecoder。继承了 ByteToMessageDecoder 的类必须实现的唯一的抽象方法:Decode,该方法将 ByteBuffer 解析为 List,如果 List 不为空则会将该 List 传递给下一个 ChannelHandlerAdapter。

ModbusDecoder 同时为 Client 端和 Server 端使用,如果是 Server 端则将消息解析成请求类,反之如果是 Client 端则将消息解析成响应类。

ModbusResponseHandler

public class ModbusResponseHandler : SimpleChannelInboundHandler

{

private Dictionary responses = new Dictionary();

protected override void ChannelRead0(IChannelHandlerContext ctx, ModbusFrame msg)

{

responses.Add(msg.Header.TransactionIdentifier, msg);

}

public override void ExceptionCaught(IChannelHandlerContext context, Exception exception)

{

context.CloseAsync();

}

}

将接收到的响应信息加入 responses 供后续处理。

ModbusRequestHandler

public class ModbusRequestHandler : SimpleChannelInboundHandler

{

private ModbusResponseService responseService;

public ModbusRequestHandler(ModbusResponseService responseService)

{

this.responseService = responseService;

}

protected override void ChannelRead0(IChannelHandlerContext ctx, ModbusFrame msg)

{

var function = msg.Function;

var response = responseService.Execute(function);

var header = msg.Header;

var frame = new ModbusFrame(header, response);

ctx.WriteAndFlushAsync(frame);

}

}

responseService 为一个抽象类,用来自定义处理接收到的请求并返回结果,需要在实现 Server 端时继承并实现。

public abstract class ModbusResponseService

{

public ModbusFunction Execute(ModbusFunction function)

{

if (function is ReadHoldingRegistersRequest)

{

var request = (ReadHoldingRegistersRequest)function;

return ReadHoldingRegisters(request);

}

throw new Exception("Function Not Support");

}

public abstract ModbusFunction ReadHoldingRegisters(ReadHoldingRegistersRequest request);

}

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

ModbusEncoder

public class ModbusEncoder : ChannelHandlerAdapter

{

public override Task WriteAsync(IChannelHandlerContext context, object message)

{

if (message is ModbusFrame)

{

var frame = (ModbusFrame)message;

return context.WriteAndFlushAsync(frame.Encode());

}

return context.WriteAsync(message);

}

}

如果是 ModbusFrame 消息则 Flush,否则传递到下一个 ChannelHandlerAdapter。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
用户可以根据需求在驱动库里选择相对应的通讯驱动程序,配置相应的通讯握手参数,自由定义需要控制和采集的变量名、通讯地址、数据类型和初始值。功能说明及图片展示: 1.安装完成首次进入将会显示三页功能简要展示页面。 2.点击&ldquo;立即使用&rdquo;按钮,进入登录界面(公测账户为admin,密码admin)。 3.登录成功进入APP主页,此时主页所有状态为初始未配置状态。 4.在主页状态下向右滑动,可打开功能模块选择列表。 5.点击&ldquo;WIFI连接&rdquo;进入连接现场设备网络页面,如果此时用户已通过手机WIFI页面连接现场设备,则直接跳过此步骤。 6.连接成功后返回功能模块选择列表,点击&ldquo;参数设置&rdquo;进入通讯相关参数配置页面(首先进入驱动配置页面),目前驱动库中只有支持Modbus TCP的驱动,后续会持续更新,用户通过在驱动库列表中长按操作将选中的驱动挑选至已选列表中,如果要取消,可以在已选列表中通过同样的长按操作完成。 7.配置要访问的设备通讯参数,与主页的操作逻辑一致,在驱动配置页面向右滑动打开配置功能列表,点击&ldquo;通讯参数配置&rdquo;进入通讯参数配置页,然后点击右上角的加号,此时软件会根据已选的驱动类型自动添加一条相对应的通讯参数,用户可以通过双击的方式打开修改列表,然后在列表的某一条参数处通过长按的方式进入最终的修改对话框,修改完成后,可以通过长按的方式选定当前需要的参数配置信息到已选区域。 8.通讯参数配置完成后,向右滑动进入配置功能列表,点击&ldquo;IO参数配置&rdquo;进入地址段的分配,点击右上角的加号,在弹出的对话框中根据实际需求分配响应的起始地址以及地址数量,分配完成后,可在屏幕右侧边缘向左滑动调出隐藏功能菜单,通过功能菜单可查看和修改配置地址段的功能对应的IO点信息。 9.所有配置完成后,可返回主页,此时主页显示当前配置的信息,其中&ldquo;控制操作&rdquo;功能可以通过按钮发送布尔值,操作逻辑为当前值为0时发送1,当前值为1时发送0,而&ldquo;参数设置&rdquo;功能通过对子项长按可以设置调出输入框设置相应的参数值。
以下是使用C# dotnetty实现分包解决方案的客户端和服务端示例代码: ### 服务端代码 ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Common.Utilities; using DotNetty.Transport.Channels; namespace Server { public class MessageDecoder : LengthFieldBasedFrameDecoder { public MessageDecoder() : base(ByteOrder.LittleEndian, 1024, 0, 4, 0, 4) { } protected override Object Decode(IChannelHandlerContext ctx, IByteBuffer input) { var buffer = (IByteBuffer)base.Decode(ctx, input); if (buffer == null) { return null; } var length = buffer.ReadInt(); var body = buffer.ReadBytes(length); return new Request() { Length = length, Body = body }; } } public class MessageEncoder : MessageToByteEncoder&lt;Response&gt; { protected override void Encode(IChannelHandlerContext context, Response message, IByteBuffer output) { output.WriteInt(message.Length); output.WriteBytes(message.Body); } } public class ServerHandler : SimpleChannelInboundHandler&lt;Request&gt; { protected override void ChannelRead0(IChannelHandlerContext ctx, Request request) { Console.WriteLine($&quot;Received message of length {request.Length}: {Encoding.UTF8.GetString(request.Body)}&quot;); var response = new Response() { Length = request.Length, Body = request.Body }; ctx.WriteAsync(response); } public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) { Console.WriteLine($&quot;Exception: {exception}&quot;); context.CloseAsync(); } } public class Request { public int Length { get; set; } public byte[] Body { get; set; } } public class Response { public int Length { get; set; } public byte[] Body { get; set; } } } ``` ### 客户端代码 ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DotNetty.Buffers; using DotNetty.Codecs; using DotNetty.Common.Utilities; using DotNetty.Transport.Channels; namespace Client { public class MessageDecoder : LengthFieldBasedFrameDecoder { public MessageDecoder() : base(ByteOrder.LittleEndian, 1024, 0, 4, 0, 4) { } protected override Object Decode(IChannelHandlerContext ctx, IByteBuffer input) { var buffer = (IByteBuffer)base.Decode(ctx, input); if (buffer == null) { return null; } var length = buffer.ReadInt(); var body = buffer.ReadBytes(length); return new Response() { Length = length, Body = body }; } } public class MessageEncoder : MessageToByteEncoder&lt;Request&gt; { protected override void Encode(IChannelHandlerContext context, Request message, IByteBuffer output) { output.WriteInt(message.Length); output.WriteBytes(message.Body); } } public class ClientHandler : SimpleChannelInboundHandler&lt;Response&gt; { private readonly TaskCompletionSource&lt;Response&gt; _responsePromise; public ClientHandler(TaskCompletionSource&lt;Response&gt; responsePromise) { _responsePromise = responsePromise; } protected override void ChannelRead0(IChannelHandlerContext ctx, Response response) { Console.WriteLine($&quot;Received message of length {response.Length}: {Encoding.UTF8.GetString(response.Body)}&quot;); _responsePromise.TrySetResult(response); } public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) { Console.WriteLine($&quot;Exception: {exception}&quot;); context.CloseAsync(); } } public class Request { public int Length { get; set; } public byte[] Body { get; set; } } public class Response { public int Length { get; set; } public byte[] Body { get; set; } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值