本文已收錄至:開源 DotNetty 實現的 Modbus TCP/IP 協議
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<object> 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<ModbusFrame>
{
private Dictionary<ushort, ModbusFrame> responses = new Dictionary<ushort, ModbusFrame>();
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<ModbusFrame>
{
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。
開源地址:modbus-tcp