Commit b92d8fb2 authored by 潘栩锋's avatar 潘栩锋 🚴

添加 Modbus.ServerTCP.cs 的线程版本

parent 80afe529
......@@ -61,7 +61,9 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WithThread\ClientTCP.cs" />
<Compile Include="WithThread\IModbusClient.cs" />
<Compile Include="WithThread\IModbusServer.cs" />
<Compile Include="WithThread\ModbusMapper_Client.cs" />
<Compile Include="WithThread\ServerTCP.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Project.FLY.FObjSys\FObjSys\FObjBase.csproj">
......
......@@ -21,7 +21,7 @@ namespace FLY.Modbus.WithThread
/// </summary>
public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(2);
/// <summary>
/// socket 错误信息,带时间
/// socket 错误信息
/// </summary>
public string ErrMsg { get; set; }
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace FLY.Modbus.WithThread
......@@ -16,6 +17,31 @@ namespace FLY.Modbus.WithThread
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// socket 错误信息
/// </summary>
string ErrMsg { get; set; }
/// <summary>
/// 连接成功
/// </summary>
bool IsConnected { get; set; }
/// <summary>
/// 远端地址
/// </summary>
IPEndPoint RemoteEP { get; set; }
/// <summary>
/// 建立连接
/// </summary>
bool Connect();
/// <summary>
/// 停止
/// </summary>
void Close();
/// <summary>
/// 读多个 COIL
/// </summary>
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
namespace FLY.Modbus.WithThread
{
public interface IModbusServer
{
/// <summary>
/// 通信超时, client 长时间连接上,但不通讯,会被断开
/// </summary>
TimeSpan Timeout { get; set; }
/// <summary>
/// 本地地址
/// </summary>
IPEndPoint LocalEP { get; set; }
/// <summary>
/// 当前连接数
/// </summary>
int ConnectedCnt { get; }
/// <summary>
/// 工作中
/// </summary>
bool IsRunning { get; set; }
/// <summary>
/// socket 错误信息
/// </summary>
string ErrMsg { get; set; }
GetValueHander GetValue { get; }
SetValueHander SetValue { get; }
void Start();
void Stop();
}
public delegate void GetValueHander(int addr, object values);
public delegate void SetValueHander(int addr, object values);
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FLY.Modbus.WithThread
{
public class ServerTCP : IModbusServer,INotifyPropertyChanged
{
/// 通信超时, client 长时间连接上,但不通讯,会被断开
/// </summary>
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
/// <summary>
/// 本地地址
/// </summary>
public IPEndPoint LocalEP { get; set; }
/// <summary>
/// 当前连接数
/// </summary>
public int ConnectedCnt { get; private set; }
/// <summary>
/// 工作中
/// </summary>
public bool IsRunning { get; set; }
/// <summary>
/// socket 错误信息
/// </summary>
public string ErrMsg { get; set; }
public GetValueHander GetValue { get; private set; }
public SetValueHander SetValue { get; private set; }
Socket listen_socket;
List<Client> clients = new List<Client>();
CancellationTokenSource cts;
public ServerTCP(IPEndPoint ep, GetValueHander getValue, SetValueHander setValue)
{
LocalEP = ep;
GetValue = getValue;
SetValue = setValue;
}
public void Start()
{
lock (this)
{
if (IsRunning)
return;
IsRunning = true;
cts = new CancellationTokenSource();
Task.Factory.StartNew(OnPoll_listen, cts.Token);
}
}
public void Stop()
{
lock (this)
{
if (!IsRunning)
return;
cts.Cancel();
listen_socket.Close();
while (true)
{
Client client;
lock (clients)
{
if (clients.Count() == 0)
break;
client = clients.First();
clients.Remove(client);
}
client.Close();
}
}
}
void OnPoll_listen()
{
listen_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listen_socket.Bind(LocalEP);
listen_socket.Listen(5);
while (true)
{
if (cts.IsCancellationRequested)
goto _end;
try
{
Socket socket = listen_socket.Accept();
Client client = new Client(socket, this);
client.Closed += Client_Closed;
client.Start();
lock (client)
{
clients.Add(client);
ConnectedCnt = clients.Count();
}
}
catch (Exception e)
{
goto _end;
}
}
_end:
IsRunning = false;
}
private void Client_Closed(object obj)
{
lock (clients)
{
clients.Remove(obj as Client);
}
}
public delegate void ClosedHandler(object obj);
class Client
{
public Socket socket;
public CancellationTokenSource cts;
ServerTCP server;
public Client(Socket socket, ServerTCP server)
{
this.socket = socket;
this.server = server;
}
public void Start()
{
cts = new CancellationTokenSource();
Task.Factory.StartNew(OnPoll_client, cts.Token);
}
bool PullRequest(out Pack_Proto request)
{
//格式——————————————
//MBAP
//域 长度 描述
//事务标识符 2byte MODBUS请求/响应的识别码
//协议标识符 2byte 0=MODBUS协议
//——长度— 2byte 以下字节的数量
//单元标识符 1byte 串行链路或其它总线过来的识别码
//PDU
//功能码—— 1byte
//数据 nbyte
byte[] recvBytes = new byte[socket.ReceiveBufferSize];
socket.ReceiveTimeout = (int)server.Timeout.TotalMilliseconds;
request = new Pack_Proto();
int offset = 0;
//读取数据
while (true)
{
int size = recvBytes.Length - offset;
if (size <= 0)
{
//异常
server.ErrMsg = "client error: rec to many!!!";
return false;
}
int bytes = 0;
try
{
bytes = socket.Receive(recvBytes, offset, size, SocketFlags.None);//从客户端接受信息,可能会分了多次接收
}
catch (SocketException e)
{
//异常
server.ErrMsg = $"client error: {e.Message}";
return false;
}
if (bytes == 0)
{
//超时,或已经断开连接
server.ErrMsg = "client error: TimeOut";
return false;
}
//肯定只回复一条,只需要翻译一条
if (!request.TryParse(recvBytes, 0, bytes, out int rlen))
{
//处理失败,数据不够,继续接收
offset = bytes;
}
else
{
//完成!!!!
return true;
}
}
}
bool PushReponse(Pack_Proto reponse)
{
try
{
socket.Send(reponse.ToBytes());
}
catch (Exception e)
{
server.ErrMsg = e.Message;
return false;
}
return true;
}
void OnPoll_client()
{
while (true)
{
if(cts.IsCancellationRequested)
goto _error;
bool ret = PullRequest(out Pack_Proto request);
if(!ret)
goto _error;
ParsePacket(request, out Pack_Proto reponse);
ret = PushReponse(reponse);
if (!ret)
goto _error;
}
_error:
Close();
}
public void Close()
{
cts.Cancel();
socket.Close();
Closed?.Invoke(this);
}
public event ClosedHandler Closed;
void ParsePacket(Pack_Proto request, out Pack_Proto reponse)
{
switch (request.func)
{
case 0x03: // read holding registers
Do_03(request, out reponse);
break;
case 0x01: // read coils
Do_01(request, out reponse);
break;
case 0x10: // write multiple registers
Do_10(request, out reponse);
break;
case 0x06: // Write single register
Do_06(request, out reponse);
break;
case 0x05: // Write single coil
Do_05(request, out reponse);
break;
case 0x0f: // Write mutiple coils
Do_0f(request, out reponse);
break;
default:
Do_Exception(request, 0x01, out reponse);
break;
}
}
#region 处理
/// <summary>
/// 异常处理
/// </summary>
void Do_Exception(Pack_Proto request, byte e_code, out Pack_Proto reponse)
{
byte func = request.func;
Misc.MyBase.SIGNBIT(ref func, 7);
reponse = new Pack_Proto()
{
tranid = request.tranid,
unitid = request.unitid,
func = func,
buf = new byte[1] { e_code }
};
}
#region 功能
/// <summary>
/// 读多个 COIL
/// </summary>
void Do_01(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
if ((num < 0x01) || (num > 0x07D0))
{
Do_Exception(request, 0x03, out reponse);
return;
}
bool[] value = new bool[num];
server.GetValue(start, value);
List<byte> data = new List<byte>();
data.Add((byte)(Math.Ceiling(num / 8.0)));//返回的字节数
for (int i = 0, j = 0; i < num; i++)
{
if (j == 0)
data.Add(0);
if (value[i])
{
data[data.Count - 1] = (byte)(data[data.Count - 1] | Misc.MyBase.BIT(i % 8));
}
j++;
if (j == 8)
{
j = 0;
}
}
reponse = new Pack_Proto()
{
tranid = request.tranid,
unitid = request.unitid,
func = request.func,
buf = data.ToArray()
};
}
/// <summary>
/// 读多个REG
/// </summary>
/// <param name="ptran"></param>
void Do_03(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
if ((num < 0x01) || (num > 0x007D))
{
Do_Exception(request, 0x03, out reponse);
return;
}
UInt16[] value = new UInt16[num];
server.GetValue(start, value);
List<byte> data = new List<byte>();
data.Add((byte)(num * 2));
for (int i = 0; i < num; i++)
{
data.AddRange(value[i].GetBytes_Big_endian());
}
reponse = new Pack_Proto()
{
tranid = request.tranid,
unitid = request.unitid,
func = request.func,
buf = data.ToArray()
};
}
/// <summary>
/// 写单个coil
/// </summary>
void Do_05(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 value = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
if ((value != 0xff00) && (value != 0x0000))
{
Do_Exception(request, 0x03, out reponse);
return;
}
bool[] v = new bool[1];
if (value == 0xff00)
v[0] = true;
else
v[0] = false;
server.SetValue(start, v);
reponse = request;
}
/// <summary>
/// 写单个REG
/// </summary>
void Do_06(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 value = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
UInt16[] v = new UInt16[1];
v[0] = value;
server.SetValue(start, v);
reponse = request;
}
/// <summary>
/// 写多个coil
/// </summary>
/// <param name="ptran"></param>
void Do_0f(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
if ((num < 0x01) || (num > 0x007D))
{
Do_Exception(request, 0x03, out reponse);
return;
}
byte byte_cnt = request.buf[index];
index++;
bool[] v = new bool[num];
for (int i = 0, j = 0; i < num; i++)
{
if (Misc.MyBase.CHECKBIT(request.buf[index], j))
v[i] = true;
else
v[i] = false;
j++;
if (j == 8)
{
j = 0;
index++;
}
}
server.SetValue(start, v);
List<byte> data = new List<byte>();
data.AddRange(start.GetBytes_Big_endian());
data.AddRange(num.GetBytes_Big_endian());
reponse = new Pack_Proto()
{
tranid = request.tranid,
unitid = request.unitid,
func = request.func,
buf = data.ToArray()
};
}
/// <summary>
/// 写多个REG
/// </summary>
/// <param name="ptran"></param>
void Do_10(Pack_Proto request, out Pack_Proto reponse)
{
int index = 0;
//开始地址
UInt16 start = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
//数量
UInt16 num = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
byte byte_cnt = request.buf[index];
index++;
if ((num < 0x01) || (num > 0x007D))
{
Do_Exception(request, 0x03, out reponse);
return;
}
UInt16[] v = new UInt16[num];
for (int i = 0; i < num; i++)
{
v[i] = COMMON.ToUInt16_Big_endian(request.buf, index);
index += 2;
}
server.SetValue(start, v);
List<byte> data = new List<byte>();
data.AddRange(start.GetBytes_Big_endian());
data.AddRange(num.GetBytes_Big_endian());
reponse = new Pack_Proto()
{
tranid = request.tranid,
unitid = request.unitid,
func = request.func,
buf = data.ToArray()
};
}
#endregion
#endregion
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment