diff --git a/server/src/Common.cs b/server/src/Common.cs deleted file mode 100644 index ee7cd7a..0000000 --- a/server/src/Common.cs +++ /dev/null @@ -1,731 +0,0 @@ -using System.Collections; -using DotNext; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.PixelFormats; -using System.Text; - -namespace Common -{ - /// - /// 数字处理工具 - /// - public class Number - { - private static readonly byte[] BitReverseTable = new byte[] { - 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, - 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, - 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, - 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, - 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, - 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, - 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, - 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, - 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, - 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, - 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, - 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, - 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, - 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, - 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, - 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, - 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, - 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, - 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, - 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, - 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, - 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, - 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, - 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, - 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, - 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, - 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, - 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, - 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff - }; - - /// - /// 整数转成二进制字节数组 - /// - /// 整数 - /// 整数长度 - /// 是否高位在数组索引低 - /// 二进制字节数组 - public static Result NumberToBytes(ulong num, uint length, bool isLowNumHigh = false) - { - if (length > 8) - { - return new(new ArgumentException( - "Unsigned long number can't over 8 bytes(64 bits).", - nameof(length) - )); - } - - var arr = new byte[length]; - - if (isLowNumHigh) - { - for (var i = 0; i < length; i++) - { - arr[length - 1 - i] = Convert.ToByte((num >> (i << 3)) & (0xFF)); - } - } - else - { - for (var i = 0; i < length; i++) - { - arr[i] = Convert.ToByte((num >> ((int)(length - 1 - i) << 3)) & (0xFF)); - } - } - return arr; - } - - /// - /// 二进制字节数组转成64bits整数 - /// - /// 二进制字节数组 - /// 是否高位在数组索引低 - /// 整数 - public static Result BytesToUInt64(byte[] bytes, bool isLowNumHigh = false) - { - if (bytes.Length > 8) - { - return new(new ArgumentException( - "Unsigned long number can't over 8 bytes(64 bits).", - nameof(bytes) - )); - } - - UInt64 num = 0; - int len = bytes.Length; - - try - { - if (isLowNumHigh) - { - for (var i = 0; i < len; i++) - { - num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3)); - } - } - else - { - for (var i = 0; i < len; i++) - { - num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3)); - } - } - - return num; - } - catch (Exception error) - { - return new(error); - } - } - - /// - /// 二进制字节数组转成32bits整数 - /// - /// 二进制字节数组 - /// 是否高位在数组索引低 - /// 整数 - public static Result BytesToUInt32(byte[] bytes, bool isLowNumHigh = false) - { - if (bytes.Length > 4) - { - return new(new ArgumentException( - "Unsigned long number can't over 4 bytes(32 bits).", - nameof(bytes) - )); - } - - UInt32 num = 0; - int len = bytes.Length; - - try - { - if (isLowNumHigh) - { - for (var i = 0; i < len; i++) - { - num += Convert.ToUInt32((UInt32)bytes[len - 1 - i] << (i << 3)); - } - } - else - { - for (var i = 0; i < len; i++) - { - num += Convert.ToUInt32((UInt32)bytes[i] << ((int)(len - 1 - i) << 3)); - } - } - - return num; - } - catch (Exception error) - { - return new(error); - } - - } - - /// - /// [TODO:description] - /// - /// [TODO:parameter] - /// [TODO:return] - public static Result UInt32ArrayToBytes(UInt32[] uintArray) - { - byte[] byteArray = new byte[uintArray.Length * 4]; - try - { - Buffer.BlockCopy(uintArray, 0, byteArray, 0, uintArray.Length * 4); - return byteArray; - } - catch (Exception error) - { - return new(error); - } - } - - - - /// - /// 比特合并成二进制字节 - /// - /// 第一个比特值 - /// 第一个比特值的长度(位数) - /// 第二个比特值 - /// 第二个比特值的长度(位数) - /// 合并后的二进制字节数组 - public static Result MultiBitsToBytes(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len) - { - return NumberToBytes(MultiBitsToNumber(bits1, bits1Len, bits2, bits2Len).Value, - (bits1Len + bits2Len) % 8 != 0 ? (bits1Len + bits2Len) / 8 + 1 : (bits1Len + bits2Len) / 8); - } - - /// - /// 比特合并成整型 - /// - /// 第一个比特值 - /// 第一个比特值的长度(位数) - /// 第二个比特值 - /// 第二个比特值的长度(位数) - /// 合并后的整型值 - public static Result MultiBitsToNumber(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len) - { - if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits")); - - ulong num = (bits1 << Convert.ToInt32(bits2Len)) | bits2; - return num; - } - - /// - /// 比特合并成整型 - /// - /// 第一个比特值 - /// 第一个比特值的长度(位数) - /// 第二个比特值 - /// 第二个比特值的长度(位数) - /// 合并后的整型值 - public static Result MultiBitsToNumber(uint bits1, uint bits1Len, uint bits2, uint bits2Len) - { - if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits")); - - uint num = (bits1 << Convert.ToInt32(bits2Len)) | bits2; - return num; - } - - /// - /// 比特位检查 - /// - /// 源比特值 - /// 目标比特值 - /// 掩码(默认为全1) - /// 检查结果(是否匹配) - public static bool BitsCheck(ulong srcBits, ulong dstBits, ulong mask = 0xFFFF_FFFF_FFFF_FFFF) - { - return (srcBits & mask) == dstBits; - } - - /// - /// 比特位检查 - /// - /// 源比特值 - /// 目标比特值 - /// 掩码(默认为全1) - /// 检查结果(是否匹配) - public static bool BitsCheck(uint srcBits, uint dstBits, uint mask = 0xFFFF_FFFF) - { - return (srcBits & mask) == dstBits; - } - - /// - /// 获取整型对应位置的比特 - /// - /// 整型数字 - /// 位置 - /// 比特 - public static Result ToBit(UInt32 srcBits, int location) - { - if (location < 0) - return new(new ArgumentException( - "Location can't be negetive", nameof(location))); - - return ((srcBits >> location) & ((UInt32)0b1)) == 1; - } - - /// - /// 将BitArray转化为32bits无符号整型 - /// - /// BitArray比特数组 - /// 32bits无符号整型 - public static Result BitsToNumber(BitArray bits) - { - if (bits.Length > 32) - throw new ArgumentException("Argument length shall be at most 32 bits."); - - var array = new UInt32[1]; - bits.CopyTo(array, 0); - return array[0]; - } - - - /// - /// 字符串转二进制字节数组 - /// - /// 输入的字符串 - /// 进制(默认为16进制) - /// 转换后的二进制字节数组 - public static byte[] StringToBytes(string str, int numBase = 16) - { - var len = str.Length; - var bytesLen = len / 2; - var bytes = new byte[bytesLen]; - - for (var i = 0; i < bytesLen; i++) - { - bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16); - } - - return bytes; - } - - /// - /// 反转字节数组中的子数组 - /// - /// 源字节数组 - /// 子数组的长度(反转的步长) - /// 反转后的字节数组 - public static Result ReverseBytes(byte[] srcBytes, int distance) - { - if (distance <= 0) - return new(new ArgumentException("Distance can't be negetive", nameof(distance))); - - var srcBytesLen = srcBytes.Length; - if (distance > srcBytesLen) - return new(new ArgumentException( - "Distance is larger than bytesArray", nameof(distance))); - if (srcBytesLen % distance != 0) - return new(new ArgumentException( - "The length of bytes can't be divided by distance without reminder", nameof(distance))); - - var dstBytes = new byte[srcBytesLen]; - var buffer = new byte[distance]; - - for (int i = 0; i < srcBytesLen; i += distance) - { - var end = i + distance; - buffer = srcBytes[i..end]; - Array.Reverse(buffer); - Array.Copy(buffer, 0, dstBytes, i, distance); - } - - return dstBytes; - } - - /// - /// 反转字节内比特顺序(使用查找表的方法) - /// - /// 字节 - /// 反转后的字节 - public static byte ReverseBits(byte srcByte) - { - return BitReverseTable[srcByte]; - } - - /// - /// 反转字节数组的字节内比特顺序(使用查找表的方法) - /// - /// 字节数组 - /// 反转后的字节字节数组 - public static byte[] ReverseBits(byte[] srcBytes) - { - var bytesLen = srcBytes.Length; - var dstBytes = new byte[bytesLen]; - for (int i = 0; i < bytesLen; i++) - { - dstBytes[i] = BitReverseTable[srcBytes[i]]; - } - return dstBytes; - } - } - - /// - /// 字符串处理工具 - /// - public class String - { - /// - /// 反转字符串 - /// - /// 输入的字符串 - /// 反转后的字符串 - public static string Reverse(string s) - { - char[] charArray = s.ToCharArray(); - Array.Reverse(charArray); - return new string(charArray); - } - - } - - /// - /// 图像处理工具 - /// - public class Image - { - /// - /// 将 RGB565 格式转换为 RGB24 格式 - /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节) - /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节) - /// - /// RGB565格式的原始数据 - /// 图像宽度 - /// 图像高度 - /// 是否为小端序(默认为true) - /// RGB24格式的转换后数据 - public static Result ConvertRGB565ToRGB24(byte[] rgb565Data, int width, int height, bool isLittleEndian = true) - { - if (rgb565Data == null) - return new(new ArgumentNullException(nameof(rgb565Data))); - - if (width <= 0 || height <= 0) - return new(new ArgumentException("Width and height must be positive")); - - // 计算像素数量 - var expectedPixelCount = width * height; - var actualPixelCount = rgb565Data.Length / 2; - - if (actualPixelCount < expectedPixelCount) - { - return new(new ArgumentException( - $"RGB565 data length insufficient. Expected: {expectedPixelCount * 2} bytes, Actual: {rgb565Data.Length} bytes")); - } - - try - { - var pixelCount = Math.Min(actualPixelCount, expectedPixelCount); - var rgb24Data = new byte[pixelCount * 3]; - - for (int i = 0; i < pixelCount; i++) - { - // 读取 RGB565 数据 - var rgb565Index = i * 2; - if (rgb565Index + 1 >= rgb565Data.Length) break; - - // 组合成16位值 - UInt16 rgb565; - if (isLittleEndian) - { - rgb565 = (UInt16)(rgb565Data[rgb565Index] | (rgb565Data[rgb565Index + 1] << 8)); - } - else - { - rgb565 = (UInt16)((rgb565Data[rgb565Index] << 8) | rgb565Data[rgb565Index + 1]); - } - - // 提取各颜色分量 - var r5 = (rgb565 >> 11) & 0x1F; // 高5位为红色 - var g6 = (rgb565 >> 5) & 0x3F; // 中间6位为绿色 - var b5 = rgb565 & 0x1F; // 低5位为蓝色 - - // 转换为8位颜色值 - var r8 = (byte)((r5 * 255) / 31); // 5位扩展到8位 - var g8 = (byte)((g6 * 255) / 63); // 6位扩展到8位 - var b8 = (byte)((b5 * 255) / 31); // 5位扩展到8位 - - // 存储到 RGB24 数组 - var rgb24Index = i * 3; - rgb24Data[rgb24Index] = r8; // R - rgb24Data[rgb24Index + 1] = g8; // G - rgb24Data[rgb24Index + 2] = b8; // B - } - - return rgb24Data; - } - catch (Exception ex) - { - return new(ex); - } - } - - /// - /// 将 RGB24 格式转换为 RGB565 格式 - /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节) - /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节) - /// - /// RGB24格式的原始数据 - /// 图像宽度 - /// 图像高度 - /// 是否为小端序(默认为true) - /// RGB565格式的转换后数据 - public static Result ConvertRGB24ToRGB565(byte[] rgb24Data, int width, int height, bool isLittleEndian = true) - { - if (rgb24Data == null) - return new(new ArgumentNullException(nameof(rgb24Data))); - - if (width <= 0 || height <= 0) - return new(new ArgumentException("Width and height must be positive")); - - var expectedPixelCount = width * height; - var actualPixelCount = rgb24Data.Length / 3; - - if (actualPixelCount < expectedPixelCount) - { - return new(new ArgumentException( - $"RGB24 data length insufficient. Expected: {expectedPixelCount * 3} bytes, Actual: {rgb24Data.Length} bytes")); - } - - try - { - var pixelCount = Math.Min(actualPixelCount, expectedPixelCount); - var rgb565Data = new byte[pixelCount * 2]; - - for (int i = 0; i < pixelCount; i++) - { - var rgb24Index = i * 3; - if (rgb24Index + 2 >= rgb24Data.Length) break; - - // 读取 RGB24 数据 - var r8 = rgb24Data[rgb24Index]; - var g8 = rgb24Data[rgb24Index + 1]; - var b8 = rgb24Data[rgb24Index + 2]; - - // 转换为5位、6位、5位 - var r5 = (UInt16)((r8 * 31) / 255); - var g6 = (UInt16)((g8 * 63) / 255); - var b5 = (UInt16)((b8 * 31) / 255); - - // 组合成16位值 - var rgb565 = (UInt16)((r5 << 11) | (g6 << 5) | b5); - - // 存储到 RGB565 数组 - var rgb565Index = i * 2; - if (isLittleEndian) - { - rgb565Data[rgb565Index] = (byte)(rgb565 & 0xFF); - rgb565Data[rgb565Index + 1] = (byte)(rgb565 >> 8); - } - else - { - rgb565Data[rgb565Index] = (byte)(rgb565 >> 8); - rgb565Data[rgb565Index + 1] = (byte)(rgb565 & 0xFF); - } - } - - return rgb565Data; - } - catch (Exception ex) - { - return new(ex); - } - } - - /// - /// 将 RGB24 数据转换为 JPEG 格式 - /// - /// RGB24格式的图像数据 - /// 图像宽度 - /// 图像高度 - /// JPEG质量(1-100,默认80) - /// JPEG格式的字节数组 - public static Result ConvertRGB24ToJpeg(byte[] rgb24Data, int width, int height, int quality = 80) - { - if (rgb24Data == null) - return new(new ArgumentNullException(nameof(rgb24Data))); - - if (width <= 0 || height <= 0) - return new(new ArgumentException("Width and height must be positive")); - - if (quality < 1 || quality > 100) - return new(new ArgumentException("Quality must be between 1 and 100")); - - var expectedDataLength = width * height * 3; - if (rgb24Data.Length < expectedDataLength) - { - return new(new ArgumentException( - $"RGB24 data length insufficient. Expected: {expectedDataLength} bytes, Actual: {rgb24Data.Length} bytes")); - } - - try - { - using var image = new SixLabors.ImageSharp.Image(width, height); - - // 将 RGB 数据复制到 ImageSharp 图像 - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - int index = (y * width + x) * 3; - if (index + 2 < rgb24Data.Length) - { - var pixel = new Rgb24(rgb24Data[index], rgb24Data[index + 1], rgb24Data[index + 2]); - image[x, y] = pixel; - } - } - } - - using var stream = new MemoryStream(); - image.SaveAsJpeg(stream, new JpegEncoder { Quality = quality }); - return stream.ToArray(); - } - catch (Exception ex) - { - return new(ex); - } - } - - /// - /// 将 RGB565 数据直接转换为 JPEG 格式 - /// - /// RGB565格式的图像数据 - /// 图像宽度 - /// 图像高度 - /// JPEG质量(1-100,默认80) - /// 是否为小端序(默认为true) - /// JPEG格式的字节数组 - public static Result ConvertRGB565ToJpeg(byte[] rgb565Data, int width, int height, int quality = 80, bool isLittleEndian = true) - { - // 先转换为RGB24 - var rgb24Result = ConvertRGB565ToRGB24(rgb565Data, width, height, isLittleEndian); - if (!rgb24Result.IsSuccessful) - { - return new(rgb24Result.Error); - } - - // 再转换为JPEG - return ConvertRGB24ToJpeg(rgb24Result.Value, width, height, quality); - } - - /// - /// 创建 MJPEG 帧头部 - /// - /// 帧数据长度 - /// 边界字符串(默认为"--boundary") - /// MJPEG帧头部字节数组 - public static byte[] CreateMjpegFrameHeader(int frameDataLength, string boundary = "--boundary") - { - var header = $"{boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {frameDataLength}\r\n\r\n"; - return Encoding.ASCII.GetBytes(header); - } - - /// - /// 创建 MJPEG 帧尾部 - /// - /// MJPEG帧尾部字节数组 - public static byte[] CreateMjpegFrameFooter() - { - return Encoding.ASCII.GetBytes("\r\n"); - } - - /// - /// 创建完整的 MJPEG 帧数据 - /// - /// JPEG数据 - /// 边界字符串(默认为"--boundary") - /// 完整的MJPEG帧数据 - public static Result CreateMjpegFrame(byte[] jpegData, string boundary = "--boundary") - { - if (jpegData == null) - return new(new ArgumentNullException(nameof(jpegData))); - - try - { - var header = CreateMjpegFrameHeader(jpegData.Length, boundary); - var footer = CreateMjpegFrameFooter(); - - var totalLength = header.Length + jpegData.Length + footer.Length; - var frameData = new byte[totalLength]; - - var offset = 0; - Array.Copy(header, 0, frameData, offset, header.Length); - offset += header.Length; - - Array.Copy(jpegData, 0, frameData, offset, jpegData.Length); - offset += jpegData.Length; - - Array.Copy(footer, 0, frameData, offset, footer.Length); - - return frameData; - } - catch (Exception ex) - { - return new(ex); - } - } - - /// - /// 验证图像数据长度是否正确 - /// - /// 图像数据 - /// 图像宽度 - /// 图像高度 - /// 每像素字节数 - /// 验证结果 - public static bool ValidateImageDataLength(byte[] data, int width, int height, int bytesPerPixel) - { - if (data == null || width <= 0 || height <= 0 || bytesPerPixel <= 0) - return false; - - var expectedLength = width * height * bytesPerPixel; - return data.Length >= expectedLength; - } - - /// - /// 获取图像格式信息 - /// - /// 图像格式枚举 - /// 格式信息 - public static ImageFormatInfo GetImageFormatInfo(ImageFormat format) - { - return format switch - { - ImageFormat.RGB565 => new ImageFormatInfo("RGB565", 2, "16-bit RGB format (5R+6G+5B)"), - ImageFormat.RGB24 => new ImageFormatInfo("RGB24", 3, "24-bit RGB format (8R+8G+8B)"), - ImageFormat.RGBA32 => new ImageFormatInfo("RGBA32", 4, "32-bit RGBA format (8R+8G+8B+8A)"), - ImageFormat.Grayscale8 => new ImageFormatInfo("Grayscale8", 1, "8-bit grayscale format"), - _ => new ImageFormatInfo("Unknown", 0, "Unknown image format") - }; - } - } - - /// - /// 图像格式枚举 - /// - public enum ImageFormat - { - RGB565, - RGB24, - RGBA32, - Grayscale8 - } - - /// - /// 图像格式信息 - /// - public record ImageFormatInfo(string Name, int BytesPerPixel, string Description); -} diff --git a/server/src/Common/Image.cs b/server/src/Common/Image.cs new file mode 100644 index 0000000..00e64b1 --- /dev/null +++ b/server/src/Common/Image.cs @@ -0,0 +1,358 @@ +using System.Text; +using DotNext; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.PixelFormats; + +namespace Common; + +/// +/// 图像处理工具 +/// +public class Image +{ + /// + /// 将 RGB565 格式转换为 RGB24 格式 + /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节) + /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节) + /// + /// RGB565格式的原始数据 + /// 图像宽度 + /// 图像高度 + /// 是否为小端序(默认为true) + /// RGB24格式的转换后数据 + public static Result ConvertRGB565ToRGB24(byte[] rgb565Data, int width, int height, bool isLittleEndian = true) + { + if (rgb565Data == null) + return new(new ArgumentNullException(nameof(rgb565Data))); + + if (width <= 0 || height <= 0) + return new(new ArgumentException("Width and height must be positive")); + + // 计算像素数量 + var expectedPixelCount = width * height; + var actualPixelCount = rgb565Data.Length / 2; + + if (actualPixelCount < expectedPixelCount) + { + return new(new ArgumentException( + $"RGB565 data length insufficient. Expected: {expectedPixelCount * 2} bytes, Actual: {rgb565Data.Length} bytes")); + } + + try + { + var pixelCount = Math.Min(actualPixelCount, expectedPixelCount); + var rgb24Data = new byte[pixelCount * 3]; + + for (int i = 0; i < pixelCount; i++) + { + // 读取 RGB565 数据 + var rgb565Index = i * 2; + if (rgb565Index + 1 >= rgb565Data.Length) break; + + // 组合成16位值 + UInt16 rgb565; + if (isLittleEndian) + { + rgb565 = (UInt16)(rgb565Data[rgb565Index] | (rgb565Data[rgb565Index + 1] << 8)); + } + else + { + rgb565 = (UInt16)((rgb565Data[rgb565Index] << 8) | rgb565Data[rgb565Index + 1]); + } + + // 提取各颜色分量 + var r5 = (rgb565 >> 11) & 0x1F; // 高5位为红色 + var g6 = (rgb565 >> 5) & 0x3F; // 中间6位为绿色 + var b5 = rgb565 & 0x1F; // 低5位为蓝色 + + // 转换为8位颜色值 + var r8 = (byte)((r5 * 255) / 31); // 5位扩展到8位 + var g8 = (byte)((g6 * 255) / 63); // 6位扩展到8位 + var b8 = (byte)((b5 * 255) / 31); // 5位扩展到8位 + + // 存储到 RGB24 数组 + var rgb24Index = i * 3; + rgb24Data[rgb24Index] = r8; // R + rgb24Data[rgb24Index + 1] = g8; // G + rgb24Data[rgb24Index + 2] = b8; // B + } + + return rgb24Data; + } + catch (Exception ex) + { + return new(ex); + } + } + + /// + /// 将 RGB24 格式转换为 RGB565 格式 + /// RGB24: 8位红色 + 8位绿色 + 8位蓝色 = 24位 (3字节) + /// RGB565: 5位红色 + 6位绿色 + 5位蓝色 = 16位 (2字节) + /// + /// RGB24格式的原始数据 + /// 图像宽度 + /// 图像高度 + /// 是否为小端序(默认为true) + /// RGB565格式的转换后数据 + public static Result ConvertRGB24ToRGB565(byte[] rgb24Data, int width, int height, bool isLittleEndian = true) + { + if (rgb24Data == null) + return new(new ArgumentNullException(nameof(rgb24Data))); + + if (width <= 0 || height <= 0) + return new(new ArgumentException("Width and height must be positive")); + + var expectedPixelCount = width * height; + var actualPixelCount = rgb24Data.Length / 3; + + if (actualPixelCount < expectedPixelCount) + { + return new(new ArgumentException( + $"RGB24 data length insufficient. Expected: {expectedPixelCount * 3} bytes, Actual: {rgb24Data.Length} bytes")); + } + + try + { + var pixelCount = Math.Min(actualPixelCount, expectedPixelCount); + var rgb565Data = new byte[pixelCount * 2]; + + for (int i = 0; i < pixelCount; i++) + { + var rgb24Index = i * 3; + if (rgb24Index + 2 >= rgb24Data.Length) break; + + // 读取 RGB24 数据 + var r8 = rgb24Data[rgb24Index]; + var g8 = rgb24Data[rgb24Index + 1]; + var b8 = rgb24Data[rgb24Index + 2]; + + // 转换为5位、6位、5位 + var r5 = (UInt16)((r8 * 31) / 255); + var g6 = (UInt16)((g8 * 63) / 255); + var b5 = (UInt16)((b8 * 31) / 255); + + // 组合成16位值 + var rgb565 = (UInt16)((r5 << 11) | (g6 << 5) | b5); + + // 存储到 RGB565 数组 + var rgb565Index = i * 2; + if (isLittleEndian) + { + rgb565Data[rgb565Index] = (byte)(rgb565 & 0xFF); + rgb565Data[rgb565Index + 1] = (byte)(rgb565 >> 8); + } + else + { + rgb565Data[rgb565Index] = (byte)(rgb565 >> 8); + rgb565Data[rgb565Index + 1] = (byte)(rgb565 & 0xFF); + } + } + + return rgb565Data; + } + catch (Exception ex) + { + return new(ex); + } + } + + /// + /// 将 RGB24 数据转换为 JPEG 格式 + /// + /// RGB24格式的图像数据 + /// 图像宽度 + /// 图像高度 + /// JPEG质量(1-100,默认80) + /// JPEG格式的字节数组 + public static Result ConvertRGB24ToJpeg(byte[] rgb24Data, int width, int height, int quality = 80) + { + if (rgb24Data == null) + return new(new ArgumentNullException(nameof(rgb24Data))); + + if (width <= 0 || height <= 0) + return new(new ArgumentException("Width and height must be positive")); + + if (quality < 1 || quality > 100) + return new(new ArgumentException("Quality must be between 1 and 100")); + + var expectedDataLength = width * height * 3; + if (rgb24Data.Length < expectedDataLength) + { + return new(new ArgumentException( + $"RGB24 data length insufficient. Expected: {expectedDataLength} bytes, Actual: {rgb24Data.Length} bytes")); + } + + try + { + using var image = new SixLabors.ImageSharp.Image(width, height); + + // 将 RGB 数据复制到 ImageSharp 图像 + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int index = (y * width + x) * 3; + if (index + 2 < rgb24Data.Length) + { + var pixel = new Rgb24(rgb24Data[index], rgb24Data[index + 1], rgb24Data[index + 2]); + image[x, y] = pixel; + } + } + } + + using var stream = new MemoryStream(); + image.SaveAsJpeg(stream, new JpegEncoder { Quality = quality }); + return stream.ToArray(); + } + catch (Exception ex) + { + return new(ex); + } + } + + /// + /// 将 RGB565 数据直接转换为 JPEG 格式 + /// + /// RGB565格式的图像数据 + /// 图像宽度 + /// 图像高度 + /// JPEG质量(1-100,默认80) + /// 是否为小端序(默认为true) + /// JPEG格式的字节数组 + public static Result ConvertRGB565ToJpeg(byte[] rgb565Data, int width, int height, int quality = 80, bool isLittleEndian = true) + { + // 先转换为RGB24 + var rgb24Result = ConvertRGB565ToRGB24(rgb565Data, width, height, isLittleEndian); + if (!rgb24Result.IsSuccessful) + { + return new(rgb24Result.Error); + } + + // 再转换为JPEG + return ConvertRGB24ToJpeg(rgb24Result.Value, width, height, quality); + } + + /// + /// 创建 MJPEG 帧头部 + /// + /// 帧数据长度 + /// 边界字符串(默认为"--boundary") + /// MJPEG帧头部字节数组 + public static byte[] CreateMjpegFrameHeader(int frameDataLength, string boundary = "--boundary") + { + var header = $"{boundary}\r\nContent-Type: image/jpeg\r\nContent-Length: {frameDataLength}\r\n\r\n"; + return Encoding.ASCII.GetBytes(header); + } + + /// + /// 创建 MJPEG 帧尾部 + /// + /// MJPEG帧尾部字节数组 + public static byte[] CreateMjpegFrameFooter() + { + return Encoding.ASCII.GetBytes("\r\n"); + } + + /// + /// 创建完整的 MJPEG 帧数据 + /// + /// JPEG数据 + /// 边界字符串(默认为"--boundary") + /// 完整的MJPEG帧数据 + public static Result CreateMjpegFrame(byte[] jpegData, string boundary = "--boundary") + { + if (jpegData == null) + return new(new ArgumentNullException(nameof(jpegData))); + + try + { + var header = CreateMjpegFrameHeader(jpegData.Length, boundary); + var footer = CreateMjpegFrameFooter(); + + var totalLength = header.Length + jpegData.Length + footer.Length; + var frameData = new byte[totalLength]; + + var offset = 0; + Array.Copy(header, 0, frameData, offset, header.Length); + offset += header.Length; + + Array.Copy(jpegData, 0, frameData, offset, jpegData.Length); + offset += jpegData.Length; + + Array.Copy(footer, 0, frameData, offset, footer.Length); + + return frameData; + } + catch (Exception ex) + { + return new(ex); + } + } + + /// + /// 验证图像数据长度是否正确 + /// + /// 图像数据 + /// 图像宽度 + /// 图像高度 + /// 每像素字节数 + /// 验证结果 + public static bool ValidateImageDataLength(byte[] data, int width, int height, int bytesPerPixel) + { + if (data == null || width <= 0 || height <= 0 || bytesPerPixel <= 0) + return false; + + var expectedLength = width * height * bytesPerPixel; + return data.Length >= expectedLength; + } + + /// + /// 获取图像格式信息 + /// + /// 图像格式枚举 + /// 格式信息 + public static ImageFormatInfo GetImageFormatInfo(ImageFormat format) + { + return format switch + { + ImageFormat.RGB565 => new ImageFormatInfo("RGB565", 2, "16-bit RGB format (5R+6G+5B)"), + ImageFormat.RGB24 => new ImageFormatInfo("RGB24", 3, "24-bit RGB format (8R+8G+8B)"), + ImageFormat.RGBA32 => new ImageFormatInfo("RGBA32", 4, "32-bit RGBA format (8R+8G+8B+8A)"), + ImageFormat.Grayscale8 => new ImageFormatInfo("Grayscale8", 1, "8-bit grayscale format"), + _ => new ImageFormatInfo("Unknown", 0, "Unknown image format") + }; + } +} + +/// +/// 图像格式枚举 +/// +public enum ImageFormat +{ + /// + /// RGB565 + /// + RGB565, + + /// + /// RGB888 / RGB24 + /// + RGB24, + + /// + /// RGBA8888 / RGBA32 + /// + RGBA32, + + /// + /// 灰度图 + /// + Grayscale8 +} + +/// +/// 图像格式信息 +/// +public record ImageFormatInfo(string Name, int BytesPerPixel, string Description); diff --git a/server/src/Common/Number.cs b/server/src/Common/Number.cs new file mode 100644 index 0000000..0bffc08 --- /dev/null +++ b/server/src/Common/Number.cs @@ -0,0 +1,370 @@ +using System.Collections; +using DotNext; + +namespace Common; +/// +/// 数字处理工具 +/// +public class Number +{ + private static readonly byte[] BitReverseTable = new byte[] { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff + }; + + /// + /// 整数转成二进制字节数组 + /// + /// 整数 + /// 整数长度 + /// 是否高位在数组索引低 + /// 二进制字节数组 + public static Result NumberToBytes(ulong num, uint length, bool isLowNumHigh = false) + { + if (length > 8) + { + return new(new ArgumentException( + "Unsigned long number can't over 8 bytes(64 bits).", + nameof(length) + )); + } + + var arr = new byte[length]; + + if (isLowNumHigh) + { + for (var i = 0; i < length; i++) + { + arr[length - 1 - i] = Convert.ToByte((num >> (i << 3)) & (0xFF)); + } + } + else + { + for (var i = 0; i < length; i++) + { + arr[i] = Convert.ToByte((num >> ((int)(length - 1 - i) << 3)) & (0xFF)); + } + } + return arr; + } + + /// + /// 二进制字节数组转成64bits整数 + /// + /// 二进制字节数组 + /// 是否高位在数组索引低 + /// 整数 + public static Result BytesToUInt64(byte[] bytes, bool isLowNumHigh = false) + { + if (bytes.Length > 8) + { + return new(new ArgumentException( + "Unsigned long number can't over 8 bytes(64 bits).", + nameof(bytes) + )); + } + + UInt64 num = 0; + int len = bytes.Length; + + try + { + if (isLowNumHigh) + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt64((UInt64)bytes[len - 1 - i] << (i << 3)); + } + } + else + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt64((UInt64)bytes[i] << ((int)(len - 1 - i) << 3)); + } + } + + return num; + } + catch (Exception error) + { + return new(error); + } + } + + /// + /// 二进制字节数组转成32bits整数 + /// + /// 二进制字节数组 + /// 是否高位在数组索引低 + /// 整数 + public static Result BytesToUInt32(byte[] bytes, bool isLowNumHigh = false) + { + if (bytes.Length > 4) + { + return new(new ArgumentException( + "Unsigned long number can't over 4 bytes(32 bits).", + nameof(bytes) + )); + } + + UInt32 num = 0; + int len = bytes.Length; + + try + { + if (isLowNumHigh) + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt32((UInt32)bytes[len - 1 - i] << (i << 3)); + } + } + else + { + for (var i = 0; i < len; i++) + { + num += Convert.ToUInt32((UInt32)bytes[i] << ((int)(len - 1 - i) << 3)); + } + } + + return num; + } + catch (Exception error) + { + return new(error); + } + + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + public static Result UInt32ArrayToBytes(UInt32[] uintArray) + { + byte[] byteArray = new byte[uintArray.Length * 4]; + try + { + Buffer.BlockCopy(uintArray, 0, byteArray, 0, uintArray.Length * 4); + return byteArray; + } + catch (Exception error) + { + return new(error); + } + } + + + + /// + /// 比特合并成二进制字节 + /// + /// 第一个比特值 + /// 第一个比特值的长度(位数) + /// 第二个比特值 + /// 第二个比特值的长度(位数) + /// 合并后的二进制字节数组 + public static Result MultiBitsToBytes(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len) + { + return NumberToBytes(MultiBitsToNumber(bits1, bits1Len, bits2, bits2Len).Value, + (bits1Len + bits2Len) % 8 != 0 ? (bits1Len + bits2Len) / 8 + 1 : (bits1Len + bits2Len) / 8); + } + + /// + /// 比特合并成整型 + /// + /// 第一个比特值 + /// 第一个比特值的长度(位数) + /// 第二个比特值 + /// 第二个比特值的长度(位数) + /// 合并后的整型值 + public static Result MultiBitsToNumber(ulong bits1, uint bits1Len, ulong bits2, uint bits2Len) + { + if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits")); + + ulong num = (bits1 << Convert.ToInt32(bits2Len)) | bits2; + return num; + } + + /// + /// 比特合并成整型 + /// + /// 第一个比特值 + /// 第一个比特值的长度(位数) + /// 第二个比特值 + /// 第二个比特值的长度(位数) + /// 合并后的整型值 + public static Result MultiBitsToNumber(uint bits1, uint bits1Len, uint bits2, uint bits2Len) + { + if (bits1Len + bits2Len > 64) return new(new ArgumentException("Two Bits is more than 64 bits")); + + uint num = (bits1 << Convert.ToInt32(bits2Len)) | bits2; + return num; + } + + /// + /// 比特位检查 + /// + /// 源比特值 + /// 目标比特值 + /// 掩码(默认为全1) + /// 检查结果(是否匹配) + public static bool BitsCheck(ulong srcBits, ulong dstBits, ulong mask = 0xFFFF_FFFF_FFFF_FFFF) + { + return (srcBits & mask) == dstBits; + } + + /// + /// 比特位检查 + /// + /// 源比特值 + /// 目标比特值 + /// 掩码(默认为全1) + /// 检查结果(是否匹配) + public static bool BitsCheck(uint srcBits, uint dstBits, uint mask = 0xFFFF_FFFF) + { + return (srcBits & mask) == dstBits; + } + + /// + /// 获取整型对应位置的比特 + /// + /// 整型数字 + /// 位置 + /// 比特 + public static Result ToBit(UInt32 srcBits, int location) + { + if (location < 0) + return new(new ArgumentException( + "Location can't be negetive", nameof(location))); + + return ((srcBits >> location) & ((UInt32)0b1)) == 1; + } + + /// + /// 将BitArray转化为32bits无符号整型 + /// + /// BitArray比特数组 + /// 32bits无符号整型 + public static Result BitsToNumber(BitArray bits) + { + if (bits.Length > 32) + throw new ArgumentException("Argument length shall be at most 32 bits."); + + var array = new UInt32[1]; + bits.CopyTo(array, 0); + return array[0]; + } + + + /// + /// 字符串转二进制字节数组 + /// + /// 输入的字符串 + /// 进制(默认为16进制) + /// 转换后的二进制字节数组 + public static byte[] StringToBytes(string str, int numBase = 16) + { + var len = str.Length; + var bytesLen = len / 2; + var bytes = new byte[bytesLen]; + + for (var i = 0; i < bytesLen; i++) + { + bytes[i] = Convert.ToByte(str.Substring(i * 2, 2), 16); + } + + return bytes; + } + + /// + /// 反转字节数组中的子数组 + /// + /// 源字节数组 + /// 子数组的长度(反转的步长) + /// 反转后的字节数组 + public static Result ReverseBytes(byte[] srcBytes, int distance) + { + if (distance <= 0) + return new(new ArgumentException("Distance can't be negetive", nameof(distance))); + + var srcBytesLen = srcBytes.Length; + if (distance > srcBytesLen) + return new(new ArgumentException( + "Distance is larger than bytesArray", nameof(distance))); + if (srcBytesLen % distance != 0) + return new(new ArgumentException( + "The length of bytes can't be divided by distance without reminder", nameof(distance))); + + var dstBytes = new byte[srcBytesLen]; + var buffer = new byte[distance]; + + for (int i = 0; i < srcBytesLen; i += distance) + { + var end = i + distance; + buffer = srcBytes[i..end]; + Array.Reverse(buffer); + Array.Copy(buffer, 0, dstBytes, i, distance); + } + + return dstBytes; + } + + /// + /// 反转字节内比特顺序(使用查找表的方法) + /// + /// 字节 + /// 反转后的字节 + public static byte ReverseBits(byte srcByte) + { + return BitReverseTable[srcByte]; + } + + /// + /// 反转字节数组的字节内比特顺序(使用查找表的方法) + /// + /// 字节数组 + /// 反转后的字节字节数组 + public static byte[] ReverseBits(byte[] srcBytes) + { + var bytesLen = srcBytes.Length; + var dstBytes = new byte[bytesLen]; + for (int i = 0; i < bytesLen; i++) + { + dstBytes[i] = BitReverseTable[srcBytes[i]]; + } + return dstBytes; + } +} diff --git a/server/src/Common/SemaphorePool.cs b/server/src/Common/SemaphorePool.cs new file mode 100644 index 0000000..34ec9fb --- /dev/null +++ b/server/src/Common/SemaphorePool.cs @@ -0,0 +1,103 @@ +using System.Collections.Concurrent; + +namespace Common; + +/// +/// [TODO:description] +/// +public class SemaphorePool +{ + private SemaphoreSlim semaphore; + private ConcurrentQueue queue; + private int beginNum; + + /// + /// [TODO:description] + /// + public int RemainingCount { get; } + + /// + /// [TODO:description] + /// + public int MaxCount { get; } + + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public SemaphorePool(int initialCount, int beginNum = 0) + { + semaphore = new SemaphoreSlim(initialCount); + queue = new ConcurrentQueue(); + this.beginNum = beginNum; + this.RemainingCount = initialCount; + this.MaxCount = initialCount; + for (int i = 0; i < initialCount; i++) + { + queue.Enqueue(beginNum + i); + } + } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + public SemaphorePool(int initialCount, int maxCount, int beginNum = 0) + { + semaphore = new SemaphoreSlim(initialCount, maxCount); + queue = new ConcurrentQueue(); + this.beginNum = beginNum; + this.RemainingCount = initialCount; + this.MaxCount = maxCount; + for (int i = 0; i < initialCount; i++) + { + queue.Enqueue(beginNum + i); + } + } + + /// + /// [TODO:description] + /// + /// [TODO:return] + public void Wait() + { + WaitAsync().Wait(); + } + + /// + /// [TODO:description] + /// + /// [TODO:return] + public Task WaitAsync() + { + var tcs = new TaskCompletionSource(); + queue.Enqueue(tcs); + semaphore.WaitAsync().ContinueWith(t => + { + TaskCompletionSource popped; + if (queue.TryDequeue(out popped)) + popped.SetResult(true); + }); + return tcs.Task; + } + + /// + /// [TODO:description] + /// + /// [TODO:return] + public void Release() + { + semaphore.Release(); + queue.Clear(); + for (int i = 0; i < MaxCount; i++) + { + queue.Enqueue(beginNum + i); + } + } +} diff --git a/server/src/Common/String.cs b/server/src/Common/String.cs new file mode 100644 index 0000000..cace185 --- /dev/null +++ b/server/src/Common/String.cs @@ -0,0 +1,20 @@ +namespace Common; + +/// +/// 字符串处理工具 +/// +public class String +{ + /// + /// 反转字符串 + /// + /// 输入的字符串 + /// 反转后的字符串 + public static string Reverse(string s) + { + char[] charArray = s.ToCharArray(); + Array.Reverse(charArray); + return new string(charArray); + } + +} diff --git a/server/src/UdpClientPool.cs b/server/src/UdpClientPool.cs index a0d9d0c..5dbddb9 100644 --- a/server/src/UdpClientPool.cs +++ b/server/src/UdpClientPool.cs @@ -311,10 +311,10 @@ public class UDPClientPool /// IP端点(IP地址与端口) /// 任务ID /// 设备地址 - /// 要读取的数据长度(字节) + /// 要读取的数据长度(4字节) /// 超时时间(毫秒) /// 读取结果,包含接收到的字节数组 - public static async ValueTask> ReadAddrBytes( + public static async ValueTask> ReadAddr4Bytes( IPEndPoint endPoint, int taskID, UInt32 devAddr, int dataLength, int timeout = 1000) { var ret = false; @@ -330,7 +330,7 @@ public class UDPClientPool return new(new Exception("Message bus not working!")); // Calculate read times and segments - var max4BytesPerRead = 0x80; // 1024 bytes per read + var max4BytesPerRead = 0x80; // 512 bytes per read var rest4Bytes = dataLength % max4BytesPerRead; var readTimes = (rest4Bytes != 0) ? (dataLength / max4BytesPerRead + 1) : diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs index 934cede..8b636ca 100644 --- a/server/src/UdpServer.cs +++ b/server/src/UdpServer.cs @@ -72,7 +72,9 @@ public class UDPServer { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private static Dictionary> udpData = new Dictionary>(); + private Dictionary> udpData = new Dictionary>(); + + private Semaphore taskPool = new Semaphore(3, 3); private int listenPort; private UdpClient listener;