[Modbus] 如何 用 C# 開發 Modbus Master Protocol - (07) 實作 TcpModbusClient
續上篇,接下來要實作 TcpModbusClient,在這個類別裡主要是處理 Socket 類別中的 Send/Receive 方法,也就是要將 TcpModbusRequest 以及 TcpModbusResponse 組合起來
如下圖紅框:
TcpModbusClient 類別實作AbsModbusClient 抽像類別
- 檢查資料格式的動作都交給 TcpModbusRequest 還有 TcpModbusResponse 了,在這裡就不需要考慮資料檢查的問題,只需要專心對付真實設備的響應情況,比如 Retry次數的設定、Timeout 的設定,更清楚的分工,可以讓程式碼看起來更簡單。
- 這個類別的 Connect 泛型方法,主要是吃 TcpModbusConnectConfig 類別
程式碼如下:
{
private AbsModbusRequest _modbusRequest = new TcpModbusRequest();
private AbsModbusResponse _modbusResponse = new TcpModbusResponse();
private AbsModbusDataConvert _modbusDataConvert = new HexModbusDataConvert();
private Socket ModbusClient { get; set; }
private IPEndPoint ModbusEndPoint { get; set; }
public override bool Connect<T>(T ConnectConfig)
{
if (ConnectConfig == null)
{
throw new ArgumentNullException("ConnectConfig");
}
TcpModbusConnectConfig connectConfig = ConnectConfig as TcpModbusConnectConfig;
if (connectConfig == null)
{
throw new NotSupportedException("ConnectConfig");
}
if (this.ModbusClient == null)
{
this.ModbusClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
this.ModbusEndPoint = new IPEndPoint(IPAddress.Parse(connectConfig.IpAddress), connectConfig.Port);
this.ModbusClient.Connect((EndPoint)ModbusEndPoint);
this.IsConnected = this.ModbusClient.Connected;
return this.IsConnected;
}
internal override AbsModbusRequest ModbusRequest
{
get { return _modbusRequest; }
set { _modbusRequest = value; }
}
internal override AbsModbusResponse ModbusResponse
{
get { return _modbusResponse; }
set { _modbusResponse = value; }
}
internal override AbsModbusDataConvert ModbusDataConvert
{
get { return _modbusDataConvert; }
set { _modbusDataConvert = value; }
}
public override bool Disconnect()
{
if (!this.IsConnected)
{
return false;
}
this.ModbusClient.Shutdown(SocketShutdown.Both);
this.ModbusClient.Disconnect(false);
this.IsConnected = this.ModbusClient.Connected;
return !this.IsConnected;
}
public override bool Send(byte[] RequestArray)
{
if (!this.connectVerify())
{
return false;
}
this.ModbusClient.Send(RequestArray);
return true;
}
public override byte[] Receive()
{
if (!this.connectVerify())
{
return null;
}
var tempTimeOut = this.ModbusClient.ReceiveTimeout;
this.ModbusClient.ReceiveTimeout = this.ReceiveTimeout;
var bufferArray = new byte[256];
var socketError = new SocketError();
byte[] responseArray = null;
var retryCount = 0;
Stopwatch sw = new Stopwatch();
using (MemoryStream memory = new MemoryStream())
{
while (true)
{
if (this.ModbusClient.Available > 0)
{
var receiveCount = this.ModbusClient.Receive(bufferArray, 0, bufferArray.Length, SocketFlags.None, out socketError);
if (receiveCount == 0 || socketError != SocketError.Success)
{
break;
}
memory.Write(bufferArray, 0, receiveCount);
sw.Restart();
retryCount = 0;
}
//if (retryCount > this.RetryTime)
//{
// break;
//}
//if (sw.ElapsedMilliseconds >= this.ReceiveTimeout)
//{
// break;
//}
retryCount++;
//空轉
SpinWait.SpinUntil(() => retryCount > this.RetryTime, this.ReceiveTimeout);
}
this.ModbusClient.ReceiveTimeout = tempTimeOut;
responseArray = memory.ToArray();
}
if (responseArray == null || responseArray.Length == 0)
{
throw new ModbusException("Receive reponse timeout");
}
return responseArray;
}
public override void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
~TcpModbusClient()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (this.Disposed)
return;
this.IsConnected = false;
if (disposing)
{
//clean management resource
if (this.ModbusClient != null)
{
this.ModbusClient.Shutdown(SocketShutdown.Both);
this.ModbusClient.Disconnect(false);
this.ModbusClient.Dispose();
this.ModbusClient = null;
}
}
//clean unmanagement resource
//change flag
this.Disposed = true;
}
private bool connectVerify()
{
if (!this.IsConnected)
{
throw new Exception("No Connect");
}
if (this.Disposed)
{
return false;
}
return true;
}
}
定義連線參數。
public class TcpModbusConnectConfig : INotifyPropertyChanged
{
private string _ipAddress = "127.0.0.1";
private int _port = 502;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public string IpAddress
{
get { return _ipAddress; }
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentNullException();
}
IPAddress result;
if (IPAddress.TryParse(value, out result))
{
_ipAddress = value;
}
OnPropertyChanged("IpAddress");
}
}
public int Port
{
get { return _port; }
set
{
_port = value;
OnPropertyChanged("Port");
}
}
}
調用方法
client.Connect(new TcpModbusConnectConfig() { IpAddress = "127.0.0.1", Port = 502 });
var result = client.ReadCoilsToDecimal(1, 0, 10);
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET