Files
wcs/WCS.BLL/Tool/TCPClient.cs
2024-12-09 10:26:39 +08:00

366 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Concurrent;
using System.ComponentModel.Design;
using System.Text;
using TouchSocket.Core;
using TouchSocket.Sockets;
using WCS.BLL.Tool;
namespace WCS.BLL
{
/// <summary>
/// 对TouchSocket的封装 主要完成TCP的连接 状态更新 发送接收通信
/// </summary>
public class TCPClient
{
public string RemoteIPHost { get; set; } = "127.0.0.1:20002";
public string BindIPHost { get; set; } = "127.0.0.1:20003";
public string ShelfTypeName { get; set; }
public bool IsOnline { get; set; } = false;
//第一次连接是否已连接
public bool IsFirstConnected { get; set; } = false;
//can模块协议前缀
public readonly byte[] Prefix = new byte[] { 0x08, 0x00, 0x00 };
//can模块协议前缀长度
public readonly int PreFixLength = 3;
//协议数据部分长度
public readonly int DataLength = 10;
public ConcurrentDictionary<int, MessageDto> MessageList { get; set; } = new ConcurrentDictionary<int, MessageDto>();
public TcpClient tcpClient { get; set; }
//同步发送等待客户端 等待上一条指令发送成功并收到响应之后才发送下一条指令
public IWaitingClient<TcpClient> waitClient;
public object receivdLockObject = new object();
public object sendLockObject = new object();
public bool Connect()
{
try
{
tcpClient.Connect();//调用连接,当连接不成功时,会抛出异常。
return true;
}
catch (Exception e)
{
//连接失败
return false;
}
}
/// <summary>
/// 初始化配置连接
/// </summary>
/// <param name="remoteIPHost"></param>
public TCPClient(string remoteIPHost, string bindIPHost, string shelfTypeName)
{
try
{
RemoteIPHost = remoteIPHost;
BindIPHost = bindIPHost;
ShelfTypeName = shelfTypeName;
tcpClient = new TcpClient();
if (string.IsNullOrEmpty(BindIPHost))
{
//载入配置
tcpClient.Setup(new TouchSocketConfig()
.SetRemoteIPHost(new IPHost(RemoteIPHost))
//.SetBindIPHost(BindIPHost)
.ConfigurePlugins(a =>
{
//配置断线重连
a.UseReconnection(-1, true, 1000);
a.Add<HeartbeatAndReceivePlugin>();
})
.ConfigureContainer(a =>
{
//添加控制台日志注入
a.AddConsoleLogger();
}));
var waitClient = tcpClient.CreateWaitingClient(new WaitingOptions()
{
FilterFunc = response => //设置用于筛选的fun委托当返回为true时才会响应返回
{
if (response.Data.Length == 13)
{
return true;
}
return false;
}
});
}
else
{
//载入配置
tcpClient.Setup(new TouchSocketConfig()
.SetRemoteIPHost(new IPHost(RemoteIPHost))
.SetBindIPHost(new IPHost(BindIPHost))
.ConfigurePlugins(a =>
{
//配置断线重连
a.UseReconnection(-1, true, 1000);
a.Add<HeartbeatAndReceivePlugin>();
})
.ConfigureContainer(a =>
{
//添加控制台日志注入
a.AddConsoleLogger();
}));
}
//添加接收事件 匹配已发送的指令
tcpClient.Received += (client, e) =>
{
var data = e.ByteBlock.Buffer.Take((int)e.ByteBlock.Length).ToArray();
Task.Run(() =>
{
Logs.Write($"【校验发送接收 开始】{ShelfTypeName}" + BitConverter.ToString(data), LogsType.InstructionResend);
if (ShelfTypeName == "信息化货架")
{
//协议拆包
var len = data.Length;
//灯控制统一返回的是 FF-00-00-0A-00-02-D7-B5
if (len == 8)
{
if (data[4] == 0x00 && data[0] == 0xFF && data[1] == 0x00 && (data[5] == 0x01 || data[5] == 0x02))
{
//查询当前板子是否有待验证的指令
var message = new MessageDto();
var firstMessage = MessageList.Select(t => new { Id = t.Key, Value = t.Value })
.OrderBy(t => t.Value.CreateTime)
.FirstOrDefault();
if (firstMessage != null)
{
MessageList.TryRemove(firstMessage.Id, out message);
Logs.Write($"【信息化货架】以下指令已不重发:{BitConverter.ToString(firstMessage.Value.Message)}", LogsType.InstructionResend);
}
}
}
}
else if (ShelfTypeName == "液晶货架")
{
Logs.Write($"【液晶货架】接收到指令{BitConverter.ToString(data)}", LogsType.InstructionResend);
}
//智能货架
else
{
var len = data.Length;
for (int index = 0; index < data.Length - PreFixLength; index++)
{
//协议拆包 通过前缀校验是否为完整数据包
var prefixInData = data.Skip(index).Take(PreFixLength);
var isEqual = prefixInData.SequenceEqual(Prefix);
if (isEqual)
{
var dataTemp = data.Skip(index).Take(PreFixLength + DataLength).ToArray();
if (dataTemp.Length < PreFixLength + DataLength)//拆包后不满足一条指令的长度
{
continue;
}
//获取返回指令的板子ID
var boardId = (dataTemp[PreFixLength + 0] << 8) + dataTemp[PreFixLength + 1];
//查询当前板子是否有待验证的指令
var message = new MessageDto();
MessageList.TryGetValue(boardId, out message);
//功能位校验 功能位相同视为已响应指令 删除对应的指令
if (message?.Message[PreFixLength + 2] == dataTemp[PreFixLength + 2])
{
MessageList.TryRemove(boardId, out message);
}
index += (PreFixLength + DataLength - 2);//每次循环index会+1 所以这里-1
}
}
}
Logs.Write($"【校验发送接收 结束】{ShelfTypeName}" + BitConverter.ToString(data), LogsType.InstructionResend);
});
return null;
};
tcpClient.Connected += (client, e) =>
{
this.IsOnline = true;
return EasyTask.CompletedTask;
};
tcpClient.Disconnected += (client, e) =>
{
this.IsOnline = false;
return EasyTask.CompletedTask;
};
////配置数据重发机制
//Task.Run(async () =>
//{
// while (true)
// {
// try
// {
// //TODO如果指令已发两次 则取消重发
// await Task.Delay(100);
// if (MessageList.Count > 0)
// {
// var failedMessage = MessageList.Where(t => t.Value.LastSendTime < DateTime.Now.AddSeconds(-1))
// .ToList();
// foreach (var message in failedMessage)
// {
// Logs.Write("【指令重发】" + BitConverter.ToString(message.Value.Message) + "指令超时1s未响应", LogsType.InstructionResend);
// }
// MessageList.RemoveWhen(t => t.Value.SendTimes >= 2);
// foreach (var item in MessageList)
// {
// if (item.Value.LastSendTime < DateTime.Now.AddSeconds(-1))
// {
// Send(item.Value.Message);
// item.Value.SendTimes++;
// item.Value.LastSendTime = DateTime.Now;
// Logs.Write("【指令重发】" + BitConverter.ToString(item.Value.Message) + "已进行重发", LogsType.InstructionResend);
// }
// }
// }
// }
// catch
// {
// }
// }
//});
}
catch (Exception ex)
{
}
}
public void ReConnectAsync()
{
tcpClient.TryConnectAsync();
}
public void Send(byte[] message, bool IsReSend = false)
{
try
{
var boardId = (message[3] << 8) + message[4];
if (boardId != 2047 && IsReSend == false)
{
MessageList.TryAdd(boardId, new MessageDto()
{
ID = boardId,
Message = message,
SendTimes = 1
});
}
lock (sendLockObject)
{
tcpClient.Send(message);
var clientIpHost = tcpClient.IP + ":" + tcpClient.Port;
Logs.Write($"【发送{clientIpHost}】{BitConverter.ToString(message)}", LogsType.Instructions);
//发送自带15ms间隔
Thread.Sleep(18);
}
}
catch (Exception ex)
{
Logs.Write("【发送指令时发生异常】" + ex.Message, LogsType.Instructions);
//因异常断连时(网线已经被断了) 手动重连一次
if (ex is NotConnectedException)
{
Task.Run(() =>
{
ReConnectAsync();
});
}
throw ex;
}
}
public void SendThenReturn(byte[] message, bool IsReSend = false)
{
try
{
var boardId = (message[3] << 8) + message[4];
lock (sendLockObject)
{
var clientIpHost = tcpClient.IP + ":" + tcpClient.Port;
Logs.Write($"【同步等待发送{clientIpHost}】{BitConverter.ToString(message)}", LogsType.Instructions);
waitClient.SendThenReturn(message);
Thread.Sleep(3);
}
}
catch (Exception ex)
{
Logs.Write("【同步等待发送指令时发生异常】" + ex.Message, LogsType.Instructions);
//因异常断连时(网线已经被断了) 手动重连一次
if (ex is NotConnectedException)
{
Task.Run(() =>
{
ReConnectAsync();
});
}
throw ex;
}
}
//生成协议明细
public byte[] GenerateMessage(int boardId, byte[] data)
{
var message = new byte[Prefix.Length + 2 + data.Length];
var boardIdData = BitConverter.GetBytes(unchecked((short)boardId));
// 检查是否需要交换字节
if (BitConverter.IsLittleEndian)
{
// 如果是小端序系统,则交换字节
byte temp = boardIdData[0];
boardIdData[0] = boardIdData[1];
boardIdData[1] = temp;
}
Buffer.BlockCopy(Prefix, 0, message, 0, Prefix.Length);
Buffer.BlockCopy(boardIdData, 0, message, Prefix.Length, boardIdData.Length);
Buffer.BlockCopy(data, 0, message, Prefix.Length + boardIdData.Length, data.Length);
return message;
}
}
//发送指令的记录
public class MessageDto
{
public int ID { get; set; }
/// <summary>
/// 最后一次发送时间
/// </summary>
public DateTime LastSendTime { get; set; } = DateTime.Now;
public DateTime CreateTime { get; set; } = DateTime.Now;
/// <summary>
/// 发送内容
/// </summary>
public byte[] Message { get; set; }
/// <summary>
/// 发送次数
/// </summary>
public int SendTimes { get; set; } = 0;//第几次发送
}
}