212 lines
8.2 KiB
C#
212 lines
8.2 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Net.Sockets;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
|
||
namespace DOAN.Common.SocketHelper
|
||
{
|
||
public static class SocketSenderHelper
|
||
{
|
||
// 消息头长度:4字节,用于存储消息体的长度(int)
|
||
private const int HeaderSize = 4;
|
||
|
||
/// <summary>
|
||
/// 将字符串消息打包为带长度头的字节数组(推荐用于文本协议)
|
||
/// </summary>
|
||
/// <param name="message">要发送的字符串消息</param>
|
||
/// <returns>完整的报文字节数组(含长度头)</returns>
|
||
public static byte[] PackStringMessage(string message)
|
||
{
|
||
byte[] body = Encoding.UTF8.GetBytes(message);
|
||
return PackBytesMessage(body);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将任意字节数组打包为带长度头的完整报文(通用)
|
||
/// </summary>
|
||
/// <param name="body">消息内容的字节数组</param>
|
||
/// <returns>完整报文字节数组(含长度头)</returns>
|
||
public static byte[] PackBytesMessage(byte[] body)
|
||
{
|
||
if (body == null)
|
||
throw new ArgumentNullException(nameof(body));
|
||
|
||
int bodyLength = body.Length;
|
||
|
||
// 总长度 = 4字节头部 + body长度
|
||
byte[] packet = new byte[HeaderSize + bodyLength];
|
||
|
||
// 1. 先写入消息长度(4字节,int)
|
||
byte[] lengthBytes = BitConverter.GetBytes(bodyLength);
|
||
Buffer.BlockCopy(lengthBytes, 0, packet, 0, HeaderSize);
|
||
|
||
// 2. 再写入消息内容
|
||
Buffer.BlockCopy(body, 0, packet, HeaderSize, bodyLength);
|
||
|
||
return packet;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 Socket 发送一整个报文(自动打包为带长度头的格式)
|
||
/// </summary>
|
||
/// <param name="socket">已连接的 Socket</param>
|
||
/// <param name="message">要发送的字符串消息</param>
|
||
public static void SendStringMessage(Socket socket, string message)
|
||
{
|
||
byte[] packet = PackStringMessage(message);
|
||
SendRawPacket(socket, packet);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 Socket 发送一整个二进制报文(自动打包为带长度头的格式)
|
||
/// </summary>
|
||
/// <param name="socket">已连接的 Socket</param>
|
||
/// <param name="body">要发送的字节数组</param>
|
||
public static void SendBytesMessage(Socket socket, byte[] body)
|
||
{
|
||
byte[] packet = PackBytesMessage(body);
|
||
SendRawPacket(socket, packet);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 直接通过 Socket 发送原始报文数据(不打包,适用于已处理好的报文)
|
||
/// </summary>
|
||
/// <param name="socket">已连接的 Socket</param>
|
||
/// <param name="packet">完整的报文数据(含长度头,或者自定义格式)</param>
|
||
public static void SendRawPacket(Socket socket, byte[] packet)
|
||
{
|
||
if (socket == null)
|
||
throw new ArgumentNullException(nameof(socket));
|
||
if (packet == null)
|
||
throw new ArgumentNullException(nameof(packet));
|
||
|
||
try
|
||
{
|
||
// 发送全部字节
|
||
int totalSent = 0;
|
||
int len = packet.Length;
|
||
|
||
while (totalSent < len)
|
||
{
|
||
int sent = socket.Send(packet, totalSent, len - totalSent, SocketFlags.None);
|
||
if (sent == 0)
|
||
throw new SocketException((int)SocketError.ConnectionReset);
|
||
|
||
totalSent += sent;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception("发送报文失败: " + ex.Message, ex);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// 通过 Socket 发送一个 16 进制格式的原始报文(自动将16进制字符串转为字节数组后发送,不打包长度头)
|
||
/// </summary>
|
||
/// <param name="socket">已连接的 Socket</param>
|
||
/// <param name="hexString">16进制格式的字符串,例如 "010203" 或 "01 02 03" 或 "01-02-03"</param>
|
||
/// <exception cref="ArgumentException">当 hexString 格式不正确时抛出</exception>
|
||
public static void SendHexStringMessage(Socket socket, string hexString)
|
||
{
|
||
if (socket == null)
|
||
throw new ArgumentNullException(nameof(socket));
|
||
if (string.IsNullOrWhiteSpace(hexString))
|
||
throw new ArgumentException("Hex 字符串不能为空或空白。", nameof(hexString));
|
||
|
||
// 移除所有非十六进制字符(只保留 0-9, A-F, a-f)
|
||
string cleanHex = new string(hexString
|
||
.Where(c => "0123456789ABCDEFabcdef".Contains(c))
|
||
.ToArray());
|
||
|
||
if (cleanHex.Length == 0)
|
||
throw new ArgumentException("Hex 字符串中不包含有效的十六进制字符。");
|
||
|
||
if (cleanHex.Length % 2 != 0)
|
||
throw new ArgumentException("无效的 16 进制字符串:字符个数必须为偶数(每两个字符表示一个字节)。");
|
||
|
||
byte[] bytes = new byte[cleanHex.Length / 2];
|
||
for (int i = 0; i < bytes.Length; i++)
|
||
{
|
||
string byteStr = cleanHex.Substring(i * 2, 2);
|
||
bytes[i] = Convert.ToByte(byteStr, 16);
|
||
}
|
||
|
||
// 调用现有的发送原始报文方法
|
||
SendRawPacket(socket, bytes);
|
||
}
|
||
/// <summary>
|
||
/// 通过 NetworkStream 发送一整个报文(字符串)
|
||
/// </summary>
|
||
/// <param name="stream">网络流</param>
|
||
/// <param name="message">字符串消息</param>
|
||
public static void SendStringMessage(NetworkStream stream, string message)
|
||
{
|
||
byte[] packet = PackStringMessage(message);
|
||
SendRawPacket(stream, packet);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 NetworkStream 发送一整个报文(字节数组)
|
||
/// </summary>
|
||
/// <param name="stream">网络流</param>
|
||
/// <param name="body">字节数组消息</param>
|
||
public static void SendBytesMessage(NetworkStream stream, byte[] body)
|
||
{
|
||
byte[] packet = PackBytesMessage(body);
|
||
SendRawPacket(stream, packet);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过 NetworkStream 发送原始报文数据
|
||
/// </summary>
|
||
/// <param name="stream">网络流</param>
|
||
/// <param name="packet">完整报文</param>
|
||
public static void SendRawPacket(NetworkStream stream, byte[] packet)
|
||
{
|
||
if (stream == null)
|
||
throw new ArgumentNullException(nameof(stream));
|
||
if (packet == null)
|
||
throw new ArgumentNullException(nameof(packet));
|
||
|
||
try
|
||
{
|
||
stream.Write(packet, 0, packet.Length);
|
||
stream.Flush(); // 确保数据发送出去
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw new Exception("通过 NetworkStream 发送报文失败: " + ex.Message, ex);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 计算多个 byte[] 数组中所有字节之和,然后对 256 取余,返回校验字节(0~255)
|
||
/// </summary>
|
||
/// <param name="byteArrays">多个 byte[] 数组,可以是任意数量</param>
|
||
/// <returns>校验和,一个字节(0~255),即:所有字节之和 % 256</returns>
|
||
public static byte CalculateChecksum(params byte[][] byteArrays)
|
||
{
|
||
int sum = 0;
|
||
|
||
// 遍历每一个 byte[] 数组
|
||
foreach (byte[] bytes in byteArrays)
|
||
{
|
||
if (bytes == null)
|
||
continue; // 如果某个数组为空,跳过(你也可以选择抛异常)
|
||
|
||
// 累加该数组中的每一个字节
|
||
foreach (byte b in bytes)
|
||
{
|
||
sum += b; // byte 自动提升为 int,安全累加
|
||
}
|
||
}
|
||
|
||
// 计算校验和:总和 % 256,然后转为 byte(范围保证是 0~255)
|
||
return (byte)(sum % 256);
|
||
}
|
||
}
|
||
}
|