天天看點

DotNetty 實作 Modbus TCP 系列 (三) Codecs & Handler

本文已收錄至:開源 DotNetty 實作的 Modbus TCP/IP 協定

DotNetty 作為一個半成品,我們不需要關注細節的實作,隻需要關注自己的業務即可,是以最主要的就是處理 Codecs 和 Handler。

所有的 Codecs 和 Handler 均直接或間接繼承自 ChannelHandlerAdapter。為什麼要分為 Codecs 和 Handler,個人了解是 Codecs 負責将消息解碼為我們所需的對象或者将處理的結果編碼,Handler 對解碼得到的對象進行邏輯處理,達到職責分離的目的。

DotNetty 中可以注冊多個 Codecs/Handler,入站消息按照注冊的先後順序執行,出站消息按照注冊的先後逆序執行。

對于 Client 端:

  • 入站:ModbusDecoder --> ModbusResponseHandler
  • 出站:ModbusEncoder

對于 Server 端:

  • 入站:ModbusDecoder --> ModbusRequestHandler

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

繼續閱讀