using System;
|
using System.Collections.Generic;
|
using System.Net;
|
using System.Net.Sockets;
|
using System.Text;
|
|
using Newtonsoft.Json;
|
|
namespace HH.WCS.Mobox3.DSZSH.device {
|
public class TcpClientHelper {
|
|
private static Socket _clientSocket;
|
private static byte[] _buffer = new byte[1024];
|
public static Dictionary<string, byte[]> _receivedDataQueue = new Dictionary<string, byte[]>();
|
public static string _ip { get; set; }
|
public static int _port { get; set; }
|
|
private static bool _isConnecting = false; // 标记是否正在连接中
|
private static readonly object _connectLock = new object(); // 连接操作的同步锁
|
|
/// <summary>
|
/// 重连的话调用方再实例化一个就行了
|
/// </summary>
|
/// <param name="ip"></param>
|
/// <param name="port"></param>
|
public static bool Init(string ip, int port) {
|
lock (_connectLock) {
|
try {
|
// 若正在连接中,直接返回
|
if (_isConnecting) {
|
LogHelper.Info("已有连接正在尝试中,禁止重复操作");
|
return false;
|
}
|
|
_isConnecting = true; // 标记为连接中
|
|
// 释放旧 Socket(仅在未连接时)
|
if (_clientSocket != null && !_clientSocket.Connected) {
|
SafeCloseSocket();
|
}
|
|
// 创建新 Socket 并连接
|
_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
IPAddress ipAdd = IPAddress.Parse(ip);
|
IPEndPoint endPoint = new IPEndPoint(ipAdd, port);
|
_clientSocket.BeginConnect(endPoint, ConnectCallback, null);
|
|
// 更新 IP 和端口
|
_ip = ip;
|
_port = port;
|
return true;
|
}
|
catch (SocketException ex) {
|
_isConnecting = false;
|
LogHelper.Error($"初始化连接失败: {ex.Message}", ex);
|
return false;
|
}
|
}
|
}
|
|
private static readonly object _linkLock = new object();
|
|
public static bool Link(string ip, int port) {
|
lock (_linkLock) {
|
try {
|
|
// 若Socket存在但实际已断开,强制清理
|
if (_clientSocket != null && (_clientSocket.Poll(0, SelectMode.SelectRead) && _clientSocket.Available == 0)) {
|
SafeCloseSocket();
|
}
|
|
// 原有逻辑
|
if (_clientSocket != null && _clientSocket.Connected) {
|
//if (ip == _ip && port == _port) {
|
LogHelper.Info($"产线已连接,无需重连,IP:{ip},端口:{port}");
|
return false;
|
//}
|
|
//LogHelper.Info($"oldIP={_ip};newIP={ip};oldPort={_port};newPort={port}");
|
//SafeCloseSocket();
|
}
|
return Init(ip, port);
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"产线重连失败,IP:{ip},端口:{port},异常:{ex.Message}", ex);
|
return false;
|
}
|
}
|
}
|
|
|
public static bool TcpClose() {
|
try {
|
_clientSocket?.Close();
|
return true;
|
}
|
catch {
|
return false;
|
}
|
}
|
|
public static void SendMsg(string ip, int port, string message) {
|
try {
|
if (_clientSocket?.Connected == true) {
|
byte[] data = Encoding.UTF8.GetBytes(message);
|
_clientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallback, null);
|
}
|
else {
|
Link(ip, port);
|
}
|
}
|
catch {
|
/* 异常处理 */
|
}
|
}
|
private static void SendCallback(IAsyncResult ar) {
|
try {
|
_clientSocket.EndSend(ar);
|
}
|
catch { /* 发送异常处理 */ }
|
}
|
|
private static void ConnectCallback(IAsyncResult ar) {
|
try {
|
lock (_connectLock) {
|
// 检查 Socket 是否有效
|
if (_clientSocket == null || !ar.IsCompleted) {
|
LogHelper.Info("连接已取消或Socket无效");
|
return;
|
}
|
|
// 完成连接
|
_clientSocket.EndConnect(ar);
|
|
// 仅在连接成功时启动接收
|
if (_clientSocket.Connected) {
|
LogHelper.Info($"成功连接到服务端:{_ip}:{_port}");
|
_clientSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
|
}
|
else {
|
LogHelper.Info("连接未成功,关闭Socket");
|
SafeCloseSocket();
|
}
|
}
|
}
|
catch (ObjectDisposedException) {
|
LogHelper.Info("连接过程中Socket被释放");
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"连接失败:{ex.Message}", ex);
|
SafeCloseSocket();
|
}
|
finally {
|
_isConnecting = false; // 重置连接状态
|
}
|
}
|
|
// 安全的Socket关闭方法
|
private static void SafeCloseSocket() {
|
try {
|
if (_clientSocket != null) {
|
// 避免重复关闭
|
if (_clientSocket.Connected) {
|
_clientSocket.Shutdown(SocketShutdown.Both);
|
}
|
_clientSocket.Close();
|
_clientSocket.Dispose();
|
|
// 断开后:清除对应IP:Port的接收数据
|
string key = $"{_ip}:{_port}";
|
if (_receivedDataQueue.ContainsKey(key)) {
|
_receivedDataQueue.Remove(key);
|
LogHelper.Info($"已清理队列数据,Key:{key}");
|
}
|
}
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"释放Socket资源异常:{ex.Message}", ex);
|
}
|
finally {
|
_clientSocket = null;
|
_isConnecting = false; // 确保重置连接标记
|
}
|
}
|
|
private static void ReceiveCallback(IAsyncResult ar) {
|
try {
|
if (_clientSocket == null) {
|
return;
|
}
|
int bytesRead = _clientSocket.EndReceive(ar);
|
if (bytesRead > 0) {
|
// 复制有效数据到新数组
|
byte[] receivedBytes = new byte[bytesRead];
|
Array.Copy(_buffer, 0, receivedBytes, 0, bytesRead);
|
|
// 存入队列
|
string key = $"{_ip}:{_port}";
|
string receivedMessage = Encoding.UTF8.GetString(receivedBytes);
|
_receivedDataQueue[key] = receivedBytes;
|
|
// 继续接收下一批数据
|
_clientSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
|
}
|
else {
|
// 服务端主动关闭连接,触发清理
|
LogHelper.Info("连接已被服务端关闭");
|
SafeCloseSocket();
|
}
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"接收数据异常:{ex.Message}", ex);
|
SafeCloseSocket(); // 异常时主动关闭
|
}
|
}
|
|
/// <summary>
|
/// {\"item_code\":\"CG1001\",\"batch_no\":\"BN1001\",\"cntr_code\":\"CN2505111\"}<br/>
|
/// {"item_code":"CG1001","batch_no":"BN1001","cntr_code":"CN2505111"}
|
/// </summary>
|
/// <param name="read"></param>
|
/// <returns></returns>
|
public static bool TryReadProductionLine(out byte[] read) {
|
read = null;
|
try {
|
if (_clientSocket != null && _clientSocket?.Connected == true) {
|
if (!_receivedDataQueue.TryGetValue($"{_ip}:{_port}", out byte[] result)) {
|
LogHelper.Info($"读产线托盘下线数据失败");
|
//read = result;
|
return false;
|
}
|
LogHelper.Info($"读产线托盘下线数据成功:{BitConverter.ToString(result)}");
|
read = result;
|
return true;
|
}
|
else {
|
//LogHelper.Info($"_clientSocket={_clientSocket} connected={_clientSocket?.Connected}");
|
Link(_ip, _port);
|
LogHelper.Info($"读产线托盘下线数据失败(未连接),准备重连");
|
return false;
|
}
|
}
|
catch (Exception ex) {
|
//LogHelper.Error($"读产线托盘下线数据失败(发生了异常:{JsonConvert.SerializeObject(ex)}):{ex.Message}", ex);
|
LogHelper.InfoEx(ex);
|
return false;
|
/* 异常处理 */
|
}
|
|
//return false;
|
}
|
|
/// <summary>
|
/// 将Modbus寄存器数组转换为字节数组
|
/// </summary>
|
/// <param name="registers">Modbus寄存器数组</param>
|
/// <returns>字节数组</returns>
|
public static byte[] ConvertRegistersToBytes(ushort[] registers) {
|
// 每个寄存器是16位(2字节),所以总字节数是寄存器数量的2倍
|
byte[] bytes = new byte[registers.Length * 2];
|
|
for (int i = 0; i < registers.Length; i++) {
|
// Modbus使用大端序,高位字节在前
|
bytes[i * 2] = (byte)(registers[i] >> 8); // 高位字节
|
bytes[i * 2 + 1] = (byte)(registers[i] & 0xFF); // 低位字节
|
}
|
|
return bytes;
|
}
|
|
/// <summary>
|
/// 将字节数组转换为字符串
|
/// </summary>
|
/// <param name="bytes">字节数组</param>
|
/// <returns>转换后的字符串</returns>
|
public static string ConvertBytesToString(byte[] bytes) {
|
// 查找第一个0x00字节(字符串结束符)的位置
|
int length = Array.IndexOf(bytes, (byte)0);
|
if (length < 0) length = bytes.Length; // 如果没有结束符,使用全部字节
|
|
// 根据设备使用的编码转换(常见的有ASCII或UTF-8)
|
// 这里使用ASCII编码作为示例,实际应根据设备文档确定编码方式
|
return Encoding.ASCII.GetString(bytes, 0, length);
|
|
// 如果是UTF-8编码,使用下面这行代替上面那行
|
// return Encoding.UTF8.GetString(bytes, 0, length);
|
}
|
|
public static bool WriteElevatorDownOk(byte[] sends) {
|
try {
|
if (_clientSocket?.Connected == true) {
|
_clientSocket.BeginSend(sends, 0, sends.Length, SocketFlags.None, SendCallback, null);
|
return true;
|
}
|
else {
|
Link(_ip, _port);
|
LogHelper.Info($"写电梯入货数据失败(未连接):{Encoding.UTF8.GetString(sends)}");
|
return false;
|
}
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"写电梯入货数据失败(发送了异常):{ex.Message}", ex);
|
return false;
|
}
|
}
|
|
public static byte[] ReadElevatorOutOk() {
|
try {
|
if (_clientSocket != null && _clientSocket?.Connected == true) {
|
_receivedDataQueue.TryGetValue($"{_ip}:{_port}", out byte[] result);
|
LogHelper.Info($"读电梯出货数据成功:{BitConverter.ToString(result)}");
|
return result;
|
}
|
else {
|
Link(_ip, _port);
|
LogHelper.Info($"读电梯出货数据失败(未连接),准备重连");
|
return null;
|
}
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"读电梯出货数据失败(发生了异常:{JsonConvert.SerializeObject(ex)}):{ex.Message}", ex);
|
return null;
|
/* 异常处理 */
|
}
|
}
|
|
public static string ChekElevator() {
|
try {
|
var res = "读取电梯数据的model,索引从1开始,满足以下条件才能发任务 \r\n " +
|
"字段,isNormal ,是否正常模式,1:正常模式,第7个Byte右侧第一位Bit \r\n" +
|
"字段,isValid,当前位置是否有效,1:有效,0:不用管,第9个Byte右侧第一位Bit \r\n" +
|
"字段,runMode,电梯运行模式,9=空闲泊停,7=自动运行,第10个Byte\r\n" +
|
"字段,isLock_1_Out,一层出口是否占用,1 = 占用,第14个Byte右侧第一位Bit\r\n" +
|
"字段,isLock_2_Out,二层出口是否占用,1 = 占用,第14个Byte右侧第二位Bit\r\n" +
|
"字段,taskNO,任务号\r\n" +
|
"判断电梯是否符合2楼到1楼搬送条件:isNormal 且 (runMode == 9 或 runMode == 7) 且 !isLock_1_Out \r\n" +
|
"判断电梯是否符合1楼到成品库区条件:isNormal 且 (runMode == 9 或 runMode == 7) 且 isLock_1_Out\r\n";
|
|
|
var isRead = ReadElevatorOutOk();
|
var log = BitConverter.ToString(isRead);
|
res += "读取到的电梯byte数组:" + log + "\r\n";
|
//if (isRead != null && isRead.Length >= 14) {
|
// var elevatorReadInfo = new ElevatorReadInfo() {
|
// isNormal = (isRead[6] & 1) == 1,
|
// isValid = (isRead[8] & 1) == 1,
|
// runMode = isRead[9],
|
// isLock_1_Out = (isRead[13] & 1) == 1,
|
// isLock_2_Out = (isRead[13] & (1 << 1)) == 1,
|
// };
|
// log = JsonConvert.SerializeObject(elevatorReadInfo);
|
// res += "解析后的电梯信息" + log + "\r\n";
|
|
// var res1 = elevatorReadInfo.is2To1Ok();
|
|
// res += "判断电梯是否符合2楼到1楼搬送条件,如果符合则返回true,结果" + res1 + "\r\n";
|
|
// var res2 = elevatorReadInfo.is1ToOk();
|
|
// res += "判断电梯是否符合1楼到成品库区条件,如果符合则返回true,结果" + res2 + "\r\n";
|
//}
|
//else {
|
// return "读取电梯状态失败,byte数组要求大于等于14个且不为空";
|
//}
|
return res;
|
}
|
catch (Exception ex) {
|
return ex.Message;
|
}
|
|
}
|
|
public static bool IsDuanDian() {
|
try {
|
var isRead = ReadElevatorOutOk();
|
//if (isRead != null && isRead.Length >= 14) {
|
// var elevatorReadInfo = new ElevatorReadInfo() {
|
// isNormal = (isRead[6] & 1) == 1,
|
// isValid = (isRead[8] & 1) == 1,
|
// runMode = isRead[9],
|
// isLock_1_Out = (isRead[13] & 1) == 1,
|
// isLock_2_Out = (isRead[13] & (1 << 1)) == 1,
|
// };
|
// if (elevatorReadInfo.runMode == 5)//5=断电重连
|
// {
|
// SafeCloseSocket();
|
// return true;
|
// }
|
//}
|
return false;
|
}
|
catch (Exception ex) {
|
LogHelper.Error($"判断电梯是否断电(发生了异常:{JsonConvert.SerializeObject(ex)}):{ex.Message}", ex);
|
return false;
|
}
|
}
|
}
|
}
|