Compare commits

...

24 Commits

Author SHA1 Message Date
石头 b0114f7886 v11.6.2025.0801 改进Host.Close让应用有序退出;增强DbTable的大数据文件读写能力,支持机器学场景;优化Binary二进制读写,支持压缩数据流;改进Linux内存采集;改进ExcelReader对常规数字的读取; 2025-08-01 09:50:21 +08:00
智能大石头 f83fe3ef23 [feat] ExcelReader改进对数字和字符串的支持,特别是numFmtId=0常规格式时,自动识别整数、小数并返回,支持科学计数法,解决Excel显示小数而XML存储科学计数法导致导入后得到科学计数法字符串的问题。 2025-07-31 14:36:40 +08:00
智能大石头 da2fa56053 [improv] 改进Linux可用内存采集,改变系统内核的悲观预测。MemAvailable是系统内核预测的可用内存,过低则认为不能安全分配给新进程,可能过于悲观;MemFree是完全空闲的内存,未被使用的物理内存页,但内核不敢用; 2025-07-29 20:29:29 +08:00
智能大石头 ab6eacceab 优化新建配置和更新配置时的日志显示 2025-07-29 09:32:46 +08:00
石头 c601d83a11 优化RPC测试代码,调用时提交参数 2025-07-27 16:06:14 +08:00
石头 dd3f0de639 二进制序列化浮点数也要区分大小端 2025-07-25 01:42:19 +08:00
石头 580fbcdb53 优化Binary读取数字类型时的内存分配 2025-07-25 01:08:03 +08:00
石头 6490d3a3d7 [reactor] Binary新增EndOfStream,只是是否已达到数据流末尾,重构各个读取方法,在数据流到达末尾后不再向下读取。 2025-07-24 20:29:08 +08:00
石头 138844ab18 合并 2025-07-24 15:03:18 +08:00
智能大石头 102c8861ca 新增DbTable.WriteRow,支持写入单行数据 2025-07-15 18:46:28 +08:00
智能大石头 01d3f936fb DbTable.ReadRows支持传入-1,遍历数据流返回多行。有些场景生成db文件时,无法在开始写入长度。 2025-07-15 18:31:15 +08:00
石头 885a41778b 增加TooManyRequests 2025-07-13 21:05:51 +08:00
石头 3b3189563d 常见Api响应代码增加BadRequest=400 2025-07-13 20:56:21 +08:00
石头 efb1dd1bdb Merge branch 'dev' of http://git.newlifex.com/NewLife/X into dev 2025-07-11 02:10:32 +08:00
石头 9fb122e8c8 增加快速拥有 2025-07-11 02:10:21 +08:00
智能大石头 40f70a5bdb Merge branch 'dev' of http://git.newlifex.com/NewLife/X into dev 2025-07-09 09:13:36 +08:00
石头 1b113ad776 fix unittest 2025-07-09 09:11:46 +08:00
智能大石头 8562c4f2de fix unittest 2025-07-09 04:27:39 +08:00
智能大石头 5154f1281f [feat] DbTable新增ReadRows/LoadRows/WriteRows/SaveRows方法,引入迭代器模式,支持百万级大数据文件读写 2025-07-09 03:55:52 +08:00
智能大石头 4851dbf2de [fea] 新增Host.MaxTime,支持设置主机最大运行时间,然后自动退出;网络服务器和定时调度器支持在应用进程退出时释放资源; 2025-07-08 16:59:04 +08:00
智能大石头 895127a493 改进Host.Close,调用者可能是外部SIGINT信号,需要阻塞它,给Stop留出执行时间 2025-07-08 14:15:27 +08:00
石头 0507e5c79a Revert "[NewLife.Core] dev 自动提交"
This reverts commit 7b4511ca60.
2025-07-07 10:32:40 +08:00
石头 7b4511ca60 [NewLife.Core] dev 自动提交 2025-07-06 14:00:05 +08:00
石头 6f7906a33c 细化分布式锁的描述 2025-07-06 00:16:43 +08:00
34 changed files with 1246 additions and 532 deletions

View File

@ -302,7 +302,19 @@ public abstract class Cache : DisposeBase, ICache
/// <returns></returns>
public virtual Int32 Commit() => 0;
/// <summary>申请分布式锁</summary>
/// <summary>申请分布式锁(简易版)。使用完以后需要主动释放</summary>
/// <remarks>
/// 需要注意使用完锁后需调用Dispose方法以释放锁申请与释放一定是成对出现。
///
/// 线程A申请得到锁key以后在A主动释放锁或者到达超时时间msTimeout之前其它线程B申请这个锁都会被阻塞。
/// 线程B申请锁key的最大阻塞时间为msTimeout在到期之前如果线程A主动释放锁或者A锁定key的超时时间msTimeout已到那么线程B会抢到锁。
///
/// <code>
/// using var rlock = cache.AcquireLock("myKey", 5000);
/// //todo 需要分布式锁保护的代码
/// rlock.Dispose(); //释放锁。也可以在using语句结束时自动释放
/// </code>
/// </remarks>
/// <param name="key">要锁定的key</param>
/// <param name="msTimeout">锁等待时间,单位毫秒</param>
/// <returns></returns>
@ -314,7 +326,20 @@ public abstract class Cache : DisposeBase, ICache
return rlock;
}
/// <summary>申请分布式锁</summary>
/// <summary>申请分布式锁。使用完以后需要主动释放</summary>
/// <remarks>
/// 需要注意使用完锁后需调用Dispose方法以释放锁申请与释放一定是成对出现。
///
/// 线程A申请得到锁key以后在A主动释放锁或者到达超时时间msExpire之前其它线程B申请这个锁都会被阻塞。
/// 线程B申请锁key的最大阻塞时间为msTimeout在到期之前如果线程A主动释放锁或者A锁定key的超时时间msExpire已到那么线程B会抢到锁。
///
/// <code>
/// using var rlock = cache.AcquireLock("myKey", 5000, 15000, false);
/// if (rlock ==null) throw new Exception("申请锁失败!");
/// //todo 需要分布式锁保护的代码
/// rlock.Dispose(); //释放锁。也可以在using语句结束时自动释放
/// </code>
/// </remarks>
/// <param name="key">要锁定的key</param>
/// <param name="msTimeout">锁等待时间,申请加锁时如果遇到冲突则等待的最大时间,单位毫秒</param>
/// <param name="msExpire">锁过期时间,超过该时间如果没有主动释放则自动释放锁,必须整数秒,单位毫秒</param>

View File

@ -187,13 +187,38 @@ public interface ICache
/// <returns></returns>
Int32 Commit();
/// <summary>申请分布式锁</summary>
/// <summary>申请分布式锁(简易版)。使用完以后需要主动释放</summary>
/// <remarks>
/// 需要注意使用完锁后需调用Dispose方法以释放锁申请与释放一定是成对出现。
///
/// 线程A申请得到锁key以后在A主动释放锁或者到达超时时间msTimeout之前其它线程B申请这个锁都会被阻塞。
/// 线程B申请锁key的最大阻塞时间为msTimeout在到期之前如果线程A主动释放锁或者A锁定key的超时时间msTimeout已到那么线程B会抢到锁。
///
/// <code>
/// using var rlock = cache.AcquireLock("myKey", 5000);
/// //todo 需要分布式锁保护的代码
/// rlock.Dispose(); //释放锁。也可以在using语句结束时自动释放
/// </code>
/// </remarks>
/// <param name="key">要锁定的key</param>
/// <param name="msTimeout">锁等待时间,单位毫秒</param>
/// <returns></returns>
IDisposable? AcquireLock(String key, Int32 msTimeout);
/// <summary>申请分布式锁</summary>
/// <summary>申请分布式锁。使用完以后需要主动释放</summary>
/// <remarks>
/// 需要注意使用完锁后需调用Dispose方法以释放锁申请与释放一定是成对出现。
///
/// 线程A申请得到锁key以后在A主动释放锁或者到达超时时间msExpire之前其它线程B申请这个锁都会被阻塞。
/// 线程B申请锁key的最大阻塞时间为msTimeout在到期之前如果线程A主动释放锁或者A锁定key的超时时间msExpire已到那么线程B会抢到锁。
///
/// <code>
/// using var rlock = cache.AcquireLock("myKey", 5000, 15000, false);
/// if (rlock ==null) throw new Exception("申请锁失败!");
/// //todo 需要分布式锁保护的代码
/// rlock.Dispose(); //释放锁。也可以在using语句结束时自动释放
/// </code>
/// </remarks>
/// <param name="key">要锁定的key</param>
/// <param name="msTimeout">锁等待时间,申请加锁时如果遇到冲突则等待的最大时间,单位毫秒</param>
/// <param name="msExpire">锁过期时间,超过该时间如果没有主动释放则自动释放锁,必须整数秒,单位毫秒</param>

View File

@ -37,13 +37,21 @@ public interface ICacheProvider
/// <returns></returns>
IProducerConsumer<T> GetInnerQueue<T>(String topic);
/// <summary>申请分布式锁</summary>
/// <summary>申请分布式锁(简易版)。使用完以后需要主动释放</summary>
/// <remarks>
/// 需要注意使用完锁后需调用Dispose方法以释放锁申请与释放一定是成对出现。
///
/// 一般实现为Redis分布式锁申请锁的具体表现为锁定某个key锁维持时间为msTimeout遇到冲突时等待msTimeout时间。
/// 如果在等待时间内获得锁则返回一个IDisposable对象离开using代码块时自动释放锁。
/// 如果在等待时间内没有获得锁,则抛出异常,需要自己处理锁冲突的情况。
///
/// 如果希望指定不同的维持时间和等待时间,可以使用<see cref="ICache"/>接口的<see cref="ICache.AcquireLock(String, Int32, Int32, Boolean)"/>方法。
///
/// <code>
/// using var rlock = cacheProvider.AcquireLock("myKey", 5000);
/// //todo 需要分布式锁保护的代码
/// rlock.Dispose(); //释放锁。也可以在using语句结束时自动释放
/// </code>
/// </remarks>
/// <param name="lockKey">要锁定的键值。建议加上应用模块等前缀以避免冲突</param>
/// <param name="msTimeout">遇到冲突时等待的最大时间,同时也是锁维持的时间</param>

View File

@ -766,9 +766,8 @@ public class MemoryCache : Cache
/// <returns></returns>
public Int64 Save(Stream stream)
{
var bn = new Binary
var bn = new Binary(stream)
{
Stream = stream,
EncodeInt = true,
};
@ -817,9 +816,8 @@ public class MemoryCache : Cache
/// <returns></returns>
public Int64 Load(Stream stream)
{
var bn = new Binary
var bn = new Binary(stream)
{
Stream = stream,
EncodeInt = true,
};
@ -862,9 +860,8 @@ public class MemoryCache : Cache
value = pk;
if (type != null && pk != null)
{
var bn2 = new Binary() { Stream = pk.GetStream(), EncodeInt = true };
var bn2 = new Binary(pk.GetStream()) { EncodeInt = true };
value = bn2.Read(type);
bn.Total += bn2.Total;
}
}

View File

@ -691,13 +691,17 @@ public class MachineInfo : IExtend
if (dic.TryGetValue("MemTotal", out var str) && !str.IsNullOrEmpty())
Memory = (UInt64)str.TrimEnd(" kB").ToInt() * 1024;
if (dic.TryGetValue("MemAvailable", out str) && !str.IsNullOrEmpty())
AvailableMemory = (UInt64)str.TrimEnd(" kB").ToInt() * 1024;
else if (dic.TryGetValue("MemFree", out str) && !str.IsNullOrEmpty())
AvailableMemory =
(UInt64)(str.TrimEnd(" kB").ToInt() +
dic["Buffers"]?.TrimEnd(" kB").ToInt() ?? 0 +
dic["Cached"]?.TrimEnd(" kB").ToInt() ?? 0) * 1024;
// MemAvailable是系统内核预测的可用内存过低则认为不能安全分配给新进程可能过于悲观
// MemFree是完全空闲的内存未被使用的物理内存页但内核不敢用
var ma = (UInt64)(dic["MemAvailable"]?.TrimEnd(" kB").ToInt() ?? 0) * 1024;
var mf = (UInt64)(dic["MemFree"]?.TrimEnd(" kB").ToInt() ?? 0) * 1024;
var mc = (UInt64)(dic["Cached"]?.TrimEnd(" kB").ToInt() ?? 0) * 1024;
// 空闲内存 100% 可用,缓存内存 40% 可快速回收(保守估计)
mf += (UInt64)(mc * 0.4);
if (mf > 5ul * 1024 * 1024 * 1024) mf = 5ul * 1024 * 1024 * 1024;
AvailableMemory = ma > mf ? ma : mf;
}
// A2/A4温度获取BuildrootCPU温度和主板温度

View File

@ -223,7 +223,7 @@ public class TarFile : DisposeBase
var entry = CreateEntryFromFile(fi.FullName, relativePath);
}
Write(_stream);
Write(_stream!);
}
/// <summary>创建一个 Tar 归档文件,将指定目录中的所有文件打包。</summary>
@ -475,7 +475,9 @@ public class TarEntry
}
else
{
var buf = stream.ReadBytes(FileSize);
var buf = new Byte[FileSize];
stream.ReadExactly(buf, 0, buf.Length);
//var buf = stream.ReadBytes(FileSize);
_stream = new MemoryStream(buf);
_position = 0;

View File

@ -130,15 +130,22 @@ public abstract class FileConfigProvider : ConfigProvider
if (str != null && str != old)
{
// 如果文件内容有变化,输出差异
var i = 0;
while (i < str.Length && i < old.Length && str[i] == old[i]) i++;
if (old.IsNullOrEmpty())
XTrace.WriteLine("新建配置:{0}", fileName);
else
{
// 如果文件内容有变化,输出差异
var i = 0;
while (i < str.Length && i < old.Length && str[i] == old[i]) i++;
var s = i > 16 ? i - 16 : 0;
var e = i + 16 < str.Length ? i + 16 : str.Length;
var diff = str.Substring(s, e - s).Replace("\r", "\\r").Replace("\n", "\\n");
var s = i > 16 ? i - 16 : 0;
var e = i + 32 < old.Length ? i + 32 : old.Length;
var ori = old.Substring(s, e - s).Replace("\r", "\\r").Replace("\n", "\\n");
var e2 = i + 32 < str.Length ? i + 32 : str.Length;
var diff = str.Substring(s, e2 - s).Replace("\r", "\\r").Replace("\n", "\\n");
XTrace.WriteLine("保存配置:{0},差异:\"...{1}...\"", fileName, diff);
XTrace.WriteLine("更新配置:{0},原:\"{1}\",新:\"{2}\"", fileName, ori, diff);
}
File.WriteAllText(fileName, str);
}

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Data;
using System.Data.Common;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
@ -215,16 +216,16 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
private const Byte _Ver = 3;
private const String MAGIC = "NewLifeDbTable";
/// <summary>创建二进制序列化器</summary>
/// <param name="stream">数据流</param>
/// <returns></returns>
public Binary CreateBinary(Stream stream) => new(stream) { FullTime = true, EncodeInt = true };
/// <summary>从数据流读取</summary>
/// <param name="stream"></param>
public Int64 Read(Stream stream)
{
var bn = new Binary
{
FullTime = true,
EncodeInt = true,
Stream = stream,
};
var bn = CreateBinary(stream);
// 读取头部
var rs = ReadHeader(bn);
@ -235,71 +236,97 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
return rs;
}
/// <summary>读取头部</summary>
/// <param name="bn"></param>
public Int64 ReadHeader(Binary bn)
/// <summary>读取头部。获取列名、类型和行数等信息</summary>
/// <param name="binary">二进制序列化器</param>
public Int64 ReadHeader(Binary binary)
{
var pStart = bn.Total;
var pStart = binary.Total;
// 头部,幻数、版本和标记
var magic = bn.ReadBytes(MAGIC.Length).ToStr();
var magic = binary.ReadBytes(MAGIC.Length).ToStr();
if (magic != MAGIC) throw new InvalidDataException();
var ver = bn.Read<Byte>();
_ = bn.Read<Byte>();
var ver = binary.Read<Byte>();
_ = binary.Read<Byte>();
// 版本兼容
if (ver > _Ver) throw new InvalidDataException($"DbTable[ver={_Ver}] Unable to support newer versions [{ver}]");
// v3开始支持FullTime
if (ver < 3) bn.FullTime = false;
if (ver < 3) binary.FullTime = false;
// 读取头部
var count = bn.Read<Int32>();
var count = binary.Read<Int32>();
var cs = new String[count];
var ts = new Type[count];
for (var i = 0; i < count; i++)
{
cs[i] = bn.Read<String>() + "";
cs[i] = binary.Read<String>() + "";
// 复杂类型写入类型字符串
var tc = (TypeCode)bn.Read<Byte>();
var tc = (TypeCode)binary.Read<Byte>();
if (tc != TypeCode.Object)
ts[i] = Type.GetType("System." + tc) ?? typeof(Object);
else if (ver >= 2)
ts[i] = Type.GetType(bn.Read<String>() + "") ?? typeof(Object);
ts[i] = Type.GetType(binary.Read<String>() + "") ?? typeof(Object);
}
Columns = cs;
Types = ts;
Total = bn.ReadBytes(4).ToInt();
Total = binary.ReadBytes(4).ToInt();
return bn.Total - pStart;
return binary.Total - pStart;
}
/// <summary>读取数据</summary>
/// <param name="bn"></param>
/// <param name="rows"></param>
/// <param name="binary">二进制序列化器</param>
/// <param name="rows">行数</param>
/// <returns></returns>
public Int64 ReadData(Binary bn, Int32 rows)
public Int64 ReadData(Binary binary, Int32 rows)
{
if (rows <= 0) return 0;
var pStart = bn.Total;
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
var rs = new List<Object?[]>(rows);
for (var k = 0; k < rows; k++)
{
var row = new Object?[ts.Length];
for (var i = 0; i < ts.Length; i++)
{
row[i] = bn.Read(ts[i]);
}
rs.Add(row);
}
Rows = rs;
var pStart = binary.Total;
return bn.Total - pStart;
Rows = ReadRows(binary, rows).ToList();
return binary.Total - pStart;
}
/// <summary>使用迭代器模式读取行数据。调用者可以一边读取一边处理数据</summary>
/// <param name="binary">二进制序列化器</param>
/// <param name="rows">行数。传入-1时循环遍历数据流</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public IEnumerable<Object?[]> ReadRows(Binary binary, Int32 rows)
{
if (rows == 0) yield break;
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
if (rows > 0)
{
for (var k = 0; k < rows; k++)
{
var row = new Object?[ts.Length];
for (var i = 0; i < ts.Length; i++)
{
row[i] = binary.Read(ts[i]);
}
yield return row;
}
}
else
{
while (!binary.EndOfStream)
{
var row = new Object?[ts.Length];
for (var i = 0; i < ts.Length; i++)
{
row[i] = binary.Read(ts[i]);
}
yield return row;
}
}
}
/// <summary>读取</summary>
@ -330,11 +357,35 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
}
/// <summary>从文件加载</summary>
/// <param name="file"></param>
/// <param name="file">文件路径</param>
/// <param name="compressed">是否压缩</param>
/// <returns></returns>
public Int64 LoadFile(String file, Boolean compressed = false) => file.AsFile().OpenRead(compressed, s => Read(s));
/// <summary>使用迭代器模式加载文件数据。调用者可以一边读取一边处理数据</summary>
/// <param name="file">文件路径。gz文件自动使用压缩</param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public IEnumerable<Object?[]> LoadRows(String file)
{
if (file.IsNullOrEmpty()) throw new ArgumentNullException(nameof(file));
using var fs = file.AsFile().OpenRead();
var bn = CreateBinary(fs);
// 解压缩
if (file.EndsWithIgnoreCase(".gz"))
bn.Stream = new GZipStream(fs, CompressionMode.Decompress);
ReadHeader(bn);
// 有些场景生成db文件时无法在开始写入长度。
var rows = Total;
if (rows == 0 && fs.Length > 0) rows = -1;
return ReadRows(bn, rows);
}
Boolean IAccessor.Read(Stream stream, Object? context)
{
Read(stream);
@ -347,12 +398,7 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
/// <param name="stream"></param>
public Int64 Write(Stream stream)
{
var bn = new Binary
{
FullTime = true,
EncodeInt = true,
Stream = stream,
};
var bn = CreateBinary(stream);
// 写入数据体
var rs = Rows;
@ -368,71 +414,71 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
}
/// <summary>写入头部到数据流</summary>
/// <param name="bn"></param>
public Int64 WriteHeader(Binary bn)
/// <param name="binary">二进制序列化器</param>
public Int64 WriteHeader(Binary binary)
{
var cs = Columns ?? throw new ArgumentNullException(nameof(Columns));
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
var pStart = bn.Total;
var pStart = binary.Total;
// 头部,幻数、版本和标记
var buf = MAGIC.GetBytes();
bn.Write(buf, 0, buf.Length);
bn.Write(_Ver);
bn.Write(0);
binary.Write(buf, 0, buf.Length);
binary.Write(_Ver);
binary.Write(0);
// 写入头部
var count = cs.Length;
bn.Write(count);
binary.Write(count);
for (var i = 0; i < count; i++)
{
bn.Write(cs[i]);
binary.Write(cs[i]);
// 复杂类型写入类型字符串
var code = ts[i].GetTypeCode();
bn.Write((Byte)code);
if (code == TypeCode.Object) bn.Write(ts[i].FullName);
binary.Write((Byte)code);
if (code == TypeCode.Object) binary.Write(ts[i].FullName);
}
// 数据行数
bn.Write(Total.GetBytes(), 0, 4);
binary.Write(Total.GetBytes(), 0, 4);
return bn.Total - pStart;
return binary.Total - pStart;
}
/// <summary>写入数据部分到数据流</summary>
/// <param name="bn"></param>
public Int64 WriteData(Binary bn)
/// <param name="binary">二进制序列化器</param>
public Int64 WriteData(Binary binary)
{
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
var rs = Rows;
if (rs == null) return 0;
var pStart = bn.Total;
var pStart = binary.Total;
// 写入数据
foreach (var row in rs)
{
for (var i = 0; i < row.Length; i++)
{
bn.Write(row[i], ts[i]);
binary.Write(row[i], ts[i]);
}
}
return bn.Total - pStart;
return binary.Total - pStart;
}
/// <summary>写入数据部分到数据流</summary>
/// <param name="bn"></param>
/// <param name="binary">二进制序列化器</param>
/// <param name="fields">要写入的字段序列</param>
public Int64 WriteData(Binary bn, Int32[] fields)
public Int64 WriteData(Binary binary, Int32[] fields)
{
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
var rs = Rows;
if (rs == null) return 0;
var pStart = bn.Total;
var pStart = binary.Total;
// 写入数据,按照指定的顺序
foreach (var row in rs)
@ -442,13 +488,71 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
// 找到目标顺序,实际上几乎不可能出现-1
var idx = fields[i];
if (idx >= 0)
bn.Write(row[idx], ts[idx]);
binary.Write(row[idx], ts[idx]);
else
bn.Write(null, ts[idx]);
binary.Write(null, ts[idx]);
}
}
return bn.Total - pStart;
return binary.Total - pStart;
}
/// <summary>使用迭代器模式写入数据。调用方可以一边处理数据一边写入</summary>
/// <param name="binary">二进制序列化器</param>
/// <param name="rows">数据源</param>
/// <param name="fields">要写入的字段序列</param>
/// <exception cref="ArgumentNullException"></exception>
public Int32 WriteRows(Binary binary, IEnumerable<Object?[]> rows, Int32[]? fields = null)
{
if (rows == null) throw new ArgumentNullException(nameof(rows));
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
// 写入数据
var count = 0;
foreach (var row in rows)
{
WriteRow(binary, row, fields);
count++;
}
return count;
}
/// <summary>写入一行数据</summary>
/// <param name="binary"></param>
/// <param name="row"></param>
/// <param name="fields"></param>
/// <exception cref="ArgumentNullException"></exception>
public Int64 WriteRow(Binary binary, Object?[] row, Int32[]? fields = null)
{
if (row == null) throw new ArgumentNullException(nameof(row));
var ts = Types ?? throw new ArgumentNullException(nameof(Types));
var pStart = binary.Total;
// 写入数据
if (fields == null)
{
for (var i = 0; i < row.Length; i++)
{
binary.Write(row[i], ts[i]);
}
}
else
{
for (var i = 0; i < fields.Length; i++)
{
// 找到目标顺序,实际上几乎不可能出现-1
var idx = fields[i];
if (idx >= 0)
binary.Write(row[idx], ts[idx]);
else
binary.Write(null, ts[idx]);
}
}
return binary.Total - pStart;
}
/// <summary>转数据包</summary>
@ -476,6 +580,32 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
/// <returns></returns>
public Int64 SaveFile(String file, Boolean compressed = false) => file.AsFile().OpenWrite(compressed, s => Write(s));
/// <summary>使用迭代器模式写入多行数据到文件。调用者可以一边处理数据一边写入</summary>
/// <param name="file">文件路径。gz文件自动使用压缩</param>
/// <param name="rows">数据源</param>
/// <param name="fields">要写入的字段序列</param>
/// <exception cref="ArgumentNullException"></exception>
public Int32 SaveRows(String file, IEnumerable<Object?[]> rows, Int32[]? fields = null)
{
if (file.IsNullOrEmpty()) throw new ArgumentNullException(nameof(file));
file = file.GetFullPath().EnsureDirectory(true);
using var fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
var bn = CreateBinary(fs);
// 压缩
if (file.EndsWithIgnoreCase(".gz"))
bn.Stream = new GZipStream(fs, CompressionMode.Compress);
WriteHeader(bn);
var count = WriteRows(bn, rows, fields);
// 如果文件已存在,此处截掉多余部分
fs.SetLength(fs.Position);
return count;
}
Boolean IAccessor.Write(Stream stream, Object? context)
{
Write(stream);
@ -625,26 +755,38 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
}
#endregion
#region
#region
/// <summary>写入模型列表</summary>
/// <typeparam name="T"></typeparam>
/// <param name="models"></param>
public void WriteModels<T>(IEnumerable<T> models)
{
// 可用属性
var pis = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
var pis = typeof(T).GetProperties(true);
pis = pis.Where(e => e.PropertyType.IsBaseType()).ToArray();
// 头部
if (Columns == null || Columns.Length == 0)
{
Columns = pis.Select(e => SerialHelper.GetName(e)).ToArray();
Types = pis.Select(e => e.PropertyType).ToArray();
}
Rows = Cast<T>(models).ToList();
}
/// <summary>模型列表转为对象数组行。支持WriteRows/SaveRows实现一边处理一边写入</summary>
/// <typeparam name="T"></typeparam>
/// <param name="models"></param>
/// <returns></returns>
public IEnumerable<Object?[]> Cast<T>(IEnumerable<T> models)
{
// 可用属性
var pis = typeof(T).GetProperties(true);
pis = pis.Where(e => e.PropertyType.IsBaseType()).ToArray();
Rows = [];
foreach (var item in models)
{
// 头部
if (Columns == null || Columns.Length == 0)
{
Columns = pis.Select(e => SerialHelper.GetName(e)).ToArray();
Types = pis.Select(e => e.PropertyType).ToArray();
}
var row = new Object?[Columns.Length];
for (var i = 0; i < row.Length; i++)
{
@ -657,7 +799,7 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
row[i] = item.GetValue(pis[i]);
}
}
Rows.Add(row);
yield return row;
}
}
@ -805,9 +947,9 @@ public class DbTable : IEnumerable<DbRow>, ICloneable, IAccessor
{
var dt = new DbTable
{
Columns = Columns,
Types = Types,
Rows = Rows,
Columns = Columns.ToArray(),
Types = Types.ToArray(),
Rows = Rows.ToList(),
Total = Total
};

View File

@ -1,4 +1,5 @@
using System.IO.Compression;
using System.Globalization;
using System.IO.Compression;
using System.Text;
using System.Xml.Linq;
@ -206,6 +207,49 @@ public class ExcelReader : DisposeBase
val = TimeSpan.FromSeconds(Math.Round(str.ToDouble() * 24 * 3600));
}
}
// 自动处理0/General
else if (st.NumFmtId == 0)
{
if (val is String str)
{
if (Int32.TryParse(str, out var n)) return n;
if (Int64.TryParse(str, out var m)) return m;
if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var d)) return d;
if (Double.TryParse(str, out var d2)) return d2;
}
}
else if (st.NumFmtId is 1 or 3 or 37 or 38)
{
if (val is String str)
{
if (Int32.TryParse(str, out var n)) return n;
if (Int64.TryParse(str, out var m)) return m;
}
}
else if (st.NumFmtId is 2 or 4 or 11 or 39 or 40)
{
if (val is String str)
{
if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var d)) return d;
if (Double.TryParse(str, out var d2)) return d2;
}
}
else if (st.NumFmtId is 9 or 10)
{
if (val is String str)
{
if (Double.TryParse(str, out var d2)) return d2;
}
}
// 文本Text
else if (st.NumFmtId == 49)
{
if (val is String str)
{
if (Decimal.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var d)) return d.ToString();
if (Double.TryParse(str, out var d2)) return d2.ToString();
}
}
return val;
}
@ -242,14 +286,14 @@ public class ExcelReader : DisposeBase
[11] = "0.00E+00",
[12] = "# ?/?",
[13] = "# ??/??",
[14] = "mm/dd/yy",
[14] = "mm-dd-yy",
[15] = "d-mmm-yy",
[16] = "d-mmm",
[17] = "mmm-yy",
[18] = "h:mm AM/PM",
[19] = "h:mm:ss AM/PM",
[20] = "h:mm",
[21] = "h:mm:dd",
[21] = "h:mm:ss",
[22] = "m/d/yy h:mm",
[37] = "#,##0 ;(#,##0)",
[38] = "#,##0 ;[Red](#,##0)",

View File

@ -74,7 +74,7 @@ public abstract class Logger : ILog
{
// 根据时间值的精确度选择不同的格式化输出
//var dt = (DateTime)args[i];
// todo: 解决系统使用utc时间时日志文件被跨天
// 解决系统使用utc时间时日志文件被跨天
dt = dt.AddHours(Setting.Current.UtcIntervalHours);
if (dt.Millisecond > 0)
args[i] = dt.ToString("yyyy-MM-dd HH:mm:ss.fff");

View File

@ -90,6 +90,9 @@ public class Host : DisposeBase, IHost
/// <summary>服务集合</summary>
public IList<IHostedService> Services { get; } = [];
/// <summary>最大执行时间。单位毫秒,默认-1表示永久阻塞等待外部ControlC/SIGINT信号</summary>
public Int32 MaxTime { get; set; } = -1;
#endregion
#region
@ -117,7 +120,6 @@ public class Host : DisposeBase, IHost
{
base.Dispose(disposing);
//_life?.TrySetResult(0);
Close(disposing ? "Dispose" : "GC");
}
#endregion
@ -190,6 +192,7 @@ public class Host : DisposeBase, IHost
#region
private TaskCompletionSource<Object>? _life;
private TaskCompletionSource<Object>? _life2;
/// <summary>同步运行,大循环阻塞</summary>
public void Run() => RunAsync().GetAwaiter().GetResult();
@ -203,22 +206,31 @@ public class Host : DisposeBase, IHost
#if NET45
_life = new TaskCompletionSource<Object>();
_life2 = new TaskCompletionSource<Object>();
#else
_life = new TaskCompletionSource<Object>(TaskCreationOptions.RunContinuationsAsynchronously);
_life2 = new TaskCompletionSource<Object>(TaskCreationOptions.RunContinuationsAsynchronously);
#endif
RegisterExit((s, e) => Close(s as String ?? s?.GetType().Name));
RegisterExit((s, e) => Close(s as String ?? s?.GetType().Name ?? (e as ConsoleCancelEventArgs)?.SpecialKey.ToString()));
await StartAsync(source.Token).ConfigureAwait(false);
XTrace.WriteLine("Application started. Press Ctrl+C to shut down.");
await _life.Task.ConfigureAwait(false);
// 等待生命周期结束
if (MaxTime >= 0)
_life.Task.Wait(TimeSpan.FromMilliseconds(MaxTime));
else
await _life.Task.ConfigureAwait(false);
XTrace.WriteLine("Application is shutting down...");
await StopAsync(source.Token).ConfigureAwait(false);
XTrace.WriteLine("Stopped!");
// 通知外部,主循环已完成
_life2.TrySetResult(0);
}
/// <summary>关闭主机</summary>
@ -227,7 +239,14 @@ public class Host : DisposeBase, IHost
{
XTrace.WriteLine("Application closed. {0}", reason);
// 通知主循环可以进入Stop流程
_life?.TrySetResult(0);
// 需要阻塞等待StopAsync执行完成。调用者可能是外部SIGINT信号需要阻塞它给Stop留出执行时间
_life2?.Task.Wait(15_000);
// 再阻塞一会让host.RunAsync后面的清理代码有机会执行
if (reason == "SIGINT") Thread.Sleep(500);
}
#endregion

View File

@ -193,28 +193,6 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature
base.Dispose(disposing);
if (Active) Stop(GetType().Name + (disposing ? "Dispose" : "GC"));
var sessions = _Sessions;
if (sessions != null)
{
WriteLog("准备释放网络会话{0}个!", sessions.Count);
foreach (var item in sessions.Values.ToArray())
{
item.TryDispose();
}
sessions.Clear();
}
var severs = Servers;
if (severs != null)
{
WriteLog("准备释放服务{0}个!", severs.Count);
foreach (var item in severs)
{
item.TryDispose();
}
severs.Clear();
}
}
#endregion
@ -389,6 +367,28 @@ public class NetServer : DisposeBase, IServer, IExtend, ILogFeature
WriteLog("停止监听 {0}", item);
item.Stop(reason);
}
var sessions = _Sessions;
if (sessions != null)
{
WriteLog("准备释放网络会话{0}个!", sessions.Count);
foreach (var item in sessions.Values.ToArray())
{
item.TryDispose();
}
sessions.Clear();
}
var severs = Servers;
if (severs != null)
{
WriteLog("准备释放服务{0}个!", severs.Count);
foreach (var item in severs)
{
item.TryDispose();
}
severs.Clear();
}
}
#endregion

View File

@ -354,7 +354,7 @@ public class UdpServer : SessionBase, ISocketServer, ILogFeature
se.SocketFlags = SocketFlags.None;
//return Client.ReceiveFromAsync(se);
//TODO: Android 不支持 ReceiveMessageFromAsync 方法
// Android 不支持 ReceiveMessageFromAsync 方法
if (Runtime.Mono)
return Client.ReceiveFromAsync(se);
else

View File

@ -7,7 +7,7 @@
<Description>Core basic components: log (file / network), configuration (XML / JSON / HTTP), cache, network (TCP / UDP / HTTP /WebSocket), serialization (binary / XML / JSON), APM performance tracking. 核心基础组件,日志(文件/网络、配置XML/Json/Http、缓存、网络Tcp/Udp/Http/WebSocket、序列化Binary/XML/Json、APM性能追踪。</Description>
<Company>新生命开发团队</Company>
<Copyright>©2002-2025 NewLife</Copyright>
<VersionPrefix>11.5</VersionPrefix>
<VersionPrefix>11.6</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
@ -32,7 +32,7 @@
<RepositoryUrl>https://github.com/NewLifeX/X</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>新生命团队;X组件;NewLife;$(AssemblyName)</PackageTags>
<PackageReleaseNotes>改进ChangeType浮点数转换支持科学计数法</PackageReleaseNotes>
<PackageReleaseNotes>改进Host.Close让应用有序退出增强DbTable的大数据文件读写能力支持机器学场景优化Binary二进制读写支持压缩数据流改进Linux内存采集改进ExcelReader对常规数字的读取</PackageReleaseNotes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>

View File

@ -9,15 +9,21 @@ public class ApiCode
/// <summary>200成功。一般用于Http响应也有部分JsonRpc使用该响应码表示成功</summary>
public const Int32 Ok200 = 200;
/// <summary>未经许可。一般是指未登录</summary>
/// <summary>错误请求。客户端请求语法错误或参数无效时使用</summary>
public const Int32 BadRequest = 400;
/// <summary>未提供凭证。凭证无效/需要重新认证当服务器认为客户端的认证信息如用户名或密码无效时会返回401状态码提示用户需要重新认证</summary>
public const Int32 Unauthorized = 401;
/// <summary>禁止访问。一般是只已登录但无权访问</summary>
/// <summary>禁止访问。权限不足,用户虽然已登录,但没有被授予访问该特定资源的权限</summary>
public const Int32 Forbidden = 403;
/// <summary>找不到</summary>
/// <summary>服务器找不到请求的资源</summary>
public const Int32 NotFound = 404;
/// <summary>限制请求速率。用户在给定的时间内发送了太多请求</summary>
public const Int32 TooManyRequests = 429;
/// <summary>内部服务错误。通用错误</summary>
public const Int32 InternalServerError = 500;
}

View File

@ -1,4 +1,5 @@
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using NewLife.Collections;
@ -41,6 +42,10 @@ public class Binary : FormatterBase, IBinary
/// <summary>处理器列表</summary>
public IList<IBinaryHandler> Handlers { get; private set; }
/// <summary>是否已达到数据流末尾</summary>
/// <remarks>该类内部与外部均可设置例如IAccessor中可设置EOF</remarks>
public Boolean EndOfStream { get; set; }
/// <summary>总的字节数。读取或写入</summary>
public Int64 Total { get; set; }
#endregion
@ -61,6 +66,12 @@ public class Binary : FormatterBase, IBinary
// 根据优先级排序
Handlers = list.OrderBy(e => e.Priority).ToList();
}
/// <summary>实例化</summary>
public Binary(Stream stream) : this()
{
Stream = stream ?? throw new ArgumentNullException(nameof(stream));
}
#endregion
#region
@ -248,10 +259,12 @@ public class Binary : FormatterBase, IBinary
/// <param name="type"></param>
/// <param name="value"></param>
/// <returns></returns>
public virtual Boolean TryRead(Type type, ref Object? value)
public virtual Boolean TryRead(Type type, [NotNullWhen(true)] ref Object? value)
{
if (Hosts.Count == 0 && Log != null && Log.Enable) WriteLog("BinaryRead {0} {1}", type.Name, value);
if (EndOfStream) return false;
// 优先 IAccessor 接口
if (value is IAccessor acc)
{
@ -268,7 +281,10 @@ public class Binary : FormatterBase, IBinary
foreach (var item in Handlers)
{
if (item.TryRead(type, ref value)) return true;
if (item.TryRead(type, ref value!)) return true;
// TryRead 失败时,可能是数据流不足
if (EndOfStream) return false;
}
return false;
}
@ -277,25 +293,162 @@ public class Binary : FormatterBase, IBinary
/// <returns></returns>
public virtual Byte ReadByte()
{
if (EndOfStream) throw new EndOfStreamException("The data stream is out of range!");
var b = Stream.ReadByte();
if (b < 0) throw new Exception("The data stream is out of range!");
if (b < 0)
{
EndOfStream = true;
throw new EndOfStreamException("The data stream is out of range!");
}
// 探测数据流是否已经到达末尾
var ms = Stream;
if (ms.CanSeek && ms.Position >= ms.Length) EndOfStream = true;
Total++;
return (Byte)b;
}
/// <summary>从当前流中将 count 个字节读入字节数组</summary>
/// <param name="count">要读取的字节数。</param>
/// <summary>尝试从当前流中读取一个字节</summary>
/// <param name="value"></param>
/// <returns></returns>
public virtual Boolean TryReadByte(out Byte value)
{
value = 0;
if (EndOfStream) return false;
var b = Stream.ReadByte();
if (b < 0)
{
EndOfStream = true;
return false;
}
// 探测数据流是否已经到达末尾
var ms = Stream;
if (ms.CanSeek && ms.Position >= ms.Length) EndOfStream = true;
value = (Byte)b;
Total++;
return true;
}
/// <summary>从当前流中将 count 个字节读入字节数组</summary>
/// <param name="count">要读取的字节数。-1表示读取到末尾</param>
/// <returns>要读取的字节数组。数据到达末尾时返回空数据</returns>
public virtual Byte[] ReadBytes(Int32 count)
{
var buffer = Stream.ReadBytes(count);
if (count == 0) return [];
if (EndOfStream) throw new EndOfStreamException("The data stream is out of range!");
// 如果是-1则读取全部剩余数据
if (count < 0)
{
var buf = Stream.ReadBytes(-1);
Total += buf.Length;
return buf;
}
var buffer = new Byte[count];
var totalRead = 0;
while (totalRead < count)
{
var bytesRead = Stream.Read(buffer, totalRead, count - totalRead);
if (bytesRead <= 0)
{
Total += totalRead;
EndOfStream = true;
//throw new EndOfStreamException("The data stream is out of range!");
return totalRead > 0 ? buffer : [];
}
//if (bytesRead == 0) break;
totalRead += bytesRead;
}
Total += totalRead;
// 探测数据流是否已经到达末尾
var ms = Stream;
if (ms.CanSeek && ms.Position >= ms.Length) EndOfStream = true;
//Stream.ReadExactly(buffer, 0, buffer.Length);
//var buffer = Stream.ReadBytes(count);
//if (n != count) throw new InvalidDataException($"数据不足,需要{count},实际{n}");
Total += buffer.Length;
//Total += buffer.Length;
return buffer;
}
#if NETCOREAPP || NETSTANDARD2_1
/// <summary>从当前流中读取字节数组</summary>
/// <param name="span">字节数组</param>
/// <returns></returns>
public virtual Int32 ReadBytes(Span<Byte> span)
{
if (span.Length == 0) return 0;
if (EndOfStream) throw new EndOfStreamException("The data stream is out of range!");
var totalRead = 0;
while (totalRead < span.Length)
{
var bytesRead = Stream.Read(span.Slice(totalRead));
if (bytesRead <= 0)
{
Total += totalRead;
EndOfStream = true;
return totalRead;
}
totalRead += bytesRead;
}
Total += totalRead;
// 探测数据流是否已经到达末尾
var ms = Stream;
if (ms.CanSeek && ms.Position >= ms.Length) EndOfStream = true;
return totalRead;
}
#endif
/// <summary>从当前流中读取字节数组</summary>
/// <param name="buffer">字节数组</param>
/// <param name="offset">偏移量</param>
/// <param name="count">个数</param>
/// <returns></returns>
public virtual Int32 ReadBytes(Byte[] buffer, Int32 offset, Int32 count)
{
if (count == 0) return 0;
if (EndOfStream) throw new EndOfStreamException("The data stream is out of range!");
var totalRead = 0;
while (totalRead < count)
{
var bytesRead = Stream.Read(buffer, offset + totalRead, count - totalRead);
if (bytesRead <= 0)
{
Total += totalRead;
EndOfStream = true;
return totalRead;
}
totalRead += bytesRead;
}
Total += totalRead;
// 探测数据流是否已经到达末尾
var ms = Stream;
if (ms.CanSeek && ms.Position >= ms.Length) EndOfStream = true;
return totalRead;
}
/// <summary>读取大小</summary>
/// <returns></returns>
public virtual Int32 ReadSize()
@ -314,6 +467,41 @@ public class Binary : FormatterBase, IBinary
};
}
/// <summary>读取大小</summary>
/// <returns></returns>
public virtual Boolean TryReadSize(out Int32 value)
{
value = 0;
var sizeWidth = -1;
if (UseFieldSize && TryGetFieldSize(out value, out sizeWidth)) return true;
if (sizeWidth < 0) sizeWidth = SizeWidth;
switch (sizeWidth)
{
case 1:
if (!TryReadByte(out var b)) return false;
value = b;
break;
case 2:
Object? n16 = null;
if (!TryRead(typeof(Int16), ref n16)) return false;
value = (Int16)n16;
break;
case 4:
Object? n32 = null;
if (!TryRead(typeof(Int32), ref n32)) return false;
value = (Int32)n32;
break;
case 0:
if (!TryReadEncodedInt32(out value)) return false;
break;
default:
value = -1;
break;
}
return true;
}
private Boolean TryGetFieldSize(out Int32 size, out Int32 sizeWidth)
{
sizeWidth = -1;
@ -356,6 +544,31 @@ public class Binary : FormatterBase, IBinary
#region 7
[ThreadStatic]
private static Byte[]? _encodes;
/// <summary>写7位压缩编码整数</summary>
/// <remarks>
/// 以7位压缩格式写入32位整数小于7位用1个字节小于14位用2个字节。
/// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据所以每个字节实际可利用存储空间只有后7位。
/// </remarks>
/// <param name="value">数值</param>
/// <returns>实际写入字节数</returns>
public Int32 WriteEncoded(Int16 value)
{
_encodes ??= new Byte[16];
var count = 0;
var num = (UInt16)value;
while (num >= 0x80)
{
_encodes[count++] = (Byte)(num | 0x80);
num >>= 7;
}
_encodes[count++] = (Byte)num;
Write(_encodes, 0, count);
return count;
}
/// <summary>写7位压缩编码整数</summary>
/// <remarks>
/// 以7位压缩格式写入32位整数小于7位用1个字节小于14位用2个字节。
@ -381,6 +594,31 @@ public class Binary : FormatterBase, IBinary
return count;
}
/// <summary>写7位压缩编码整数</summary>
/// <remarks>
/// 以7位压缩格式写入32位整数小于7位用1个字节小于14位用2个字节。
/// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据所以每个字节实际可利用存储空间只有后7位。
/// </remarks>
/// <param name="value">数值</param>
/// <returns>实际写入字节数</returns>
public Int32 WriteEncoded(Int64 value)
{
_encodes ??= new Byte[16];
var count = 0;
var num = (UInt64)value;
while (num >= 0x80)
{
_encodes[count++] = (Byte)(num | 0x80);
num >>= 7;
}
_encodes[count++] = (Byte)num;
Write(_encodes, 0, count);
return count;
}
/// <summary>以压缩格式读取16位整数</summary>
/// <returns></returns>
public Int16 ReadEncodedInt16()
@ -390,7 +628,8 @@ public class Binary : FormatterBase, IBinary
Byte n = 0;
while (true)
{
b = ReadByte();
//b = ReadByte();
if (!TryReadByte(out b)) break;
// 必须转为Int16否则可能溢出
rs += (Int16)((b & 0x7f) << n);
if ((b & 0x80) == 0) break;
@ -410,7 +649,8 @@ public class Binary : FormatterBase, IBinary
Byte n = 0;
while (true)
{
b = ReadByte();
//b = ReadByte();
if (!TryReadByte(out b)) break;
// 必须转为Int32否则可能溢出
rs += (b & 0x7f) << n;
if ((b & 0x80) == 0) break;
@ -430,7 +670,8 @@ public class Binary : FormatterBase, IBinary
Byte n = 0;
while (true)
{
b = ReadByte();
//b = ReadByte();
if (!TryReadByte(out b)) break;
// 必须转为Int64否则可能溢出
rs += (Int64)(b & 0x7f) << n;
if ((b & 0x80) == 0) break;
@ -440,6 +681,69 @@ public class Binary : FormatterBase, IBinary
}
return rs;
}
/// <summary>以压缩格式读取16位整数</summary>
/// <returns></returns>
public Boolean TryReadEncodedInt16(out Int16 value)
{
Byte b;
Byte n = 0;
value = 0;
while (true)
{
if (!TryReadByte(out b)) return false;
// 必须转为Int16否则可能溢出
value += (Int16)((b & 0x7f) << n);
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 16) throw new FormatException("The number value is too large to read in compressed format!");
}
return true;
}
/// <summary>以压缩格式读取32位整数</summary>
/// <returns></returns>
public Boolean TryReadEncodedInt32(out Int32 value)
{
Byte b;
Byte n = 0;
value = 0;
while (true)
{
if (!TryReadByte(out b)) return false;
// 必须转为Int32否则可能溢出
value += (b & 0x7f) << n;
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 32) throw new FormatException("The number value is too large to read in compressed format!");
}
return true;
}
/// <summary>以压缩格式读取64位整数</summary>
/// <returns></returns>
public Boolean TryReadEncodedInt64(out Int64 value)
{
Byte b;
Byte n = 0;
value = 0;
while (true)
{
if (!TryReadByte(out b)) return false;
// 必须转为Int64否则可能溢出
value += (Int64)(b & 0x7f) << n;
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 64) throw new FormatException("The number value is too large to read in compressed format!");
}
return true;
}
#endregion
#region
@ -495,6 +799,8 @@ public class Binary : FormatterBase, IBinary
public String ReadBCD(Int32 len)
{
var buf = ReadBytes(len);
if (buf.Length == 0) return String.Empty;
var cs = new Char[len * 2];
for (var i = 0; i < len; i++)
{
@ -545,6 +851,7 @@ public class Binary : FormatterBase, IBinary
public String ReadFixedString(Int32 len)
{
var buf = ReadBytes(len);
if (buf.Length == 0) return String.Empty;
// 得到实际长度,在读取-1全部字符串时也能剔除首尾的0x00和0xFF
if (len < 0) len = buf.Length;
@ -564,9 +871,9 @@ public class Binary : FormatterBase, IBinary
#endregion
#region
/// <summary>是否已达到末尾</summary>
/// <returns></returns>
public Boolean EndOfStream() => Stream.Position >= Stream.Length;
///// <summary>是否已达到末尾</summary>
///// <returns></returns>
//public Boolean EndOfStream() => Stream.Position >= Stream.Length;
/// <summary>检查剩余量是否足够</summary>
/// <param name="size"></param>
@ -582,7 +889,7 @@ public class Binary : FormatterBase, IBinary
/// <returns></returns>
public static T? FastRead<T>(Stream stream, Boolean encodeInt = true)
{
var bn = new Binary() { Stream = stream, EncodeInt = encodeInt };
var bn = new Binary(stream) { EncodeInt = encodeInt };
return bn.Read<T>();
}
@ -613,9 +920,8 @@ public class Binary : FormatterBase, IBinary
/// <returns></returns>
public static Int64 FastWrite(Object value, Stream stream, Boolean encodeInt = true)
{
var bn = new Binary
var bn = new Binary(stream)
{
Stream = stream,
EncodeInt = encodeInt,
};
bn.Write(value);

View File

@ -1,5 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing;
using NewLife.Collections;
namespace NewLife.Serialization;
@ -36,14 +36,19 @@ public class BinaryColor : BinaryHandlerBase
{
if (type != typeof(Color)) return false;
var a = Host.ReadByte();
var r = Host.ReadByte();
var g = Host.ReadByte();
var b = Host.ReadByte();
var buf = Pool.Shared.Rent(4);
if (Host.ReadBytes(buf, 0, 4) < 4) return false;
var a = buf[0];
var r = buf[1];
var g = buf[2];
var b = buf[3];
var color = Color.FromArgb(a, r, g, b);
WriteLog("ReadColor {0}", color);
value = color;
Pool.Shared.Return(buf);
return true;
}
}

View File

@ -167,8 +167,9 @@ public class BinaryComposite : BinaryHandlerBase
if (TryGetAccessor(member, out var acc) && acc.Read(Host, context)) continue;
// 数据流不足时,放弃读取目标成员,并认为整体成功
var hs = Host.Stream;
if (hs.CanSeek && hs.Position >= hs.Length) break;
//var hs = Host.Stream;
//if (hs.CanSeek && hs.Position >= hs.Length) break;
if (Host is Binary bn && bn.EndOfStream) break;
Object? v = null;
v = value is IModel src ? src[member.Name] : value.GetValue(member);

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Buffers.Binary;
using System.Text;
using NewLife.Collections;
using NewLife.Reflection;
@ -121,10 +122,12 @@ public class BinaryGeneral : BinaryHandlerBase
}
// 可空类型,先写入一个字节表示是否为空
Byte b = 0;
if (type.IsNullable())
{
var v = Host.ReadByte();
if (v == 0)
if (!Host.TryReadByte(out b)) return false;
//var v = Host.ReadByte();
if (b == 0)
{
value = null;
return true;
@ -135,14 +138,18 @@ public class BinaryGeneral : BinaryHandlerBase
switch (code)
{
case TypeCode.Boolean:
value = Host.ReadByte() > 0;
if (!Host.TryReadByte(out b)) return false;
value = b > 0;
return true;
case TypeCode.Byte:
case TypeCode.SByte:
value = Host.ReadByte();
if (!Host.TryReadByte(out b)) return false;
value = b;
return true;
case TypeCode.Char:
value = ReadChar();
if (!Host.TryReadByte(out b)) return false;
value = Convert.ToChar(b);
//value = ReadChar();
return true;
case TypeCode.DBNull:
value = DBNull.Value;
@ -150,52 +157,85 @@ public class BinaryGeneral : BinaryHandlerBase
case TypeCode.DateTime:
if (Host is Binary bn && bn.FullTime)
{
var n = ReadInt64();
if (!TryReadInt64(out var n)) return false;
value = DateTime.FromBinary(n);
}
else
{
var n = ReadUInt32();
if (!TryReadInt32(out var n)) return false;
if (n == 0)
value = DateTime.MinValue;
else
value = _dt1970.AddSeconds(n);
value = _dt1970.AddSeconds((UInt32)n);
}
return true;
case TypeCode.Decimal:
value = ReadDecimal();
//value = ReadDecimal();
var data = new Int32[4];
for (var i = 0; i < data.Length; i++)
{
if (!TryReadInt32(out data[i])) return false;
}
value = new Decimal(data);
return true;
case TypeCode.Double:
value = ReadDouble();
{
if (!TryReadDouble(out var num)) return false;
value = num;
}
return true;
case TypeCode.Empty:
value = null;
return true;
case TypeCode.Int16:
value = ReadInt16();
{
if (!TryReadInt16(out var num)) return false;
value = num;
}
return true;
case TypeCode.Int32:
value = ReadInt32();
{
if (!TryReadInt32(out var num)) return false;
value = num;
}
return true;
case TypeCode.Int64:
value = ReadInt64();
{
if (!TryReadInt64(out var num)) return false;
value = num;
}
return true;
case TypeCode.Object:
break;
case TypeCode.Single:
value = ReadSingle();
{
if (!TryReadSingle(out var num)) return false;
value = num;
}
return true;
case TypeCode.String:
value = ReadString();
{
if (!TryReadString(out var str)) return false;
value = str;
}
return true;
case TypeCode.UInt16:
value = ReadUInt16();
{
if (!TryReadInt16(out var num)) return false;
value = (UInt16)num;
}
return true;
case TypeCode.UInt32:
value = ReadUInt32();
{
if (!TryReadInt32(out var num)) return false;
value = (UInt32)num;
}
return true;
case TypeCode.UInt64:
value = ReadUInt64();
{
if (!TryReadInt64(out var num)) return false;
value = (UInt64)num;
}
return true;
default:
break;
@ -271,8 +311,8 @@ public class BinaryGeneral : BinaryHandlerBase
/// <param name="value">要写入的 2 字节有符号整数。</param>
public virtual void Write(Int16 value)
{
if (Host.EncodeInt)
WriteEncoded(value);
if (Host.EncodeInt && Host is Binary bn)
bn.WriteEncoded(value);
else
WriteIntBytes(BitConverter.GetBytes(value));
}
@ -281,8 +321,8 @@ public class BinaryGeneral : BinaryHandlerBase
/// <param name="value">要写入的 4 字节有符号整数。</param>
public virtual void Write(Int32 value)
{
if (Host.EncodeInt)
WriteEncoded(value);
if (Host.EncodeInt && Host is Binary bn)
bn.WriteEncoded(value);
else
WriteIntBytes(BitConverter.GetBytes(value));
}
@ -291,8 +331,8 @@ public class BinaryGeneral : BinaryHandlerBase
/// <param name="value">要写入的 8 字节有符号整数。</param>
public virtual void Write(Int64 value)
{
if (Host.EncodeInt)
WriteEncoded(value);
if (Host.EncodeInt && Host is Binary bn)
bn.WriteEncoded(value);
else
WriteIntBytes(BitConverter.GetBytes(value));
}
@ -330,11 +370,39 @@ public class BinaryGeneral : BinaryHandlerBase
#region
/// <summary>将 4 字节浮点值写入当前流,并将流的位置提升 4 个字节。</summary>
/// <param name="value">要写入的 4 字节浮点值。</param>
public virtual void Write(Single value) => Write(BitConverter.GetBytes(value), -1);
public virtual void Write(Single value)
{
#if NET5_0_OR_GREATER
Span<Byte> buffer = stackalloc Byte[4];
if (Host.IsLittleEndian)
BinaryPrimitives.WriteSingleLittleEndian(buffer, value);
else
BinaryPrimitives.WriteSingleBigEndian(buffer, value);
Host.Write(buffer);
#else
var buffer = BitConverter.GetBytes(value);
if (!Host.IsLittleEndian) Array.Reverse(buffer);
Host.Write(buffer, 0, buffer.Length);
#endif
}
/// <summary>将 8 字节浮点值写入当前流,并将流的位置提升 8 个字节。</summary>
/// <param name="value">要写入的 8 字节浮点值。</param>
public virtual void Write(Double value) => Write(BitConverter.GetBytes(value), -1);
public virtual void Write(Double value)
{
#if NET5_0_OR_GREATER
Span<Byte> buffer = stackalloc Byte[8];
if (Host.IsLittleEndian)
BinaryPrimitives.WriteDoubleLittleEndian(buffer, value);
else
BinaryPrimitives.WriteDoubleBigEndian(buffer, value);
Host.Write(buffer);
#else
var buffer = BitConverter.GetBytes(value);
if (!Host.IsLittleEndian) Array.Reverse(buffer);
Host.Write(buffer, 0, buffer.Length);
#endif
}
/// <summary>将一个十进制值写入当前流,并将流位置提升十六个字节。</summary>
/// <param name="value">要写入的十进制值。</param>
@ -399,276 +467,192 @@ public class BinaryGeneral : BinaryHandlerBase
#endregion
#region
#region
/// <summary>从当前流中读取下一个字节,并使流的当前位置提升 1 个字节。</summary>
/// <returns></returns>
public virtual Byte ReadByte() => Host.ReadByte();
/// <summary>从当前流中将 count 个字节读入字节数组如果count小于0则先读取字节数组长度。</summary>
/// <param name="count">要读取的字节数。</param>
/// <returns></returns>
public virtual Byte[] ReadBytes(Int32 count)
{
if (count < 0) count = Host.ReadSize();
if (count <= 0) return [];
var max = IOHelper.MaxSafeArraySize;
if (count > max) throw new XException("Security required, reading large variable length arrays is not allowed {0:n0}>{1:n0}", count, max);
var buffer = Host.ReadBytes(count);
return buffer;
}
#endregion
#region
/// <summary>读取整数的字节数组,某些写入器(如二进制写入器)可能需要改变字节顺序</summary>
/// <param name="count">数量</param>
/// <returns></returns>
protected virtual Byte[] ReadIntBytes(Int32 count)
{
var buffer = ReadBytes(count);
// 如果不是小端字节顺序,则倒序
if (!Host.IsLittleEndian) Array.Reverse(buffer);
return buffer;
}
/// <summary>从当前流中读取 2 字节有符号整数,并使流的当前位置提升 2 个字节。</summary>
/// <returns></returns>
public virtual Int16 ReadInt16()
public virtual Boolean TryReadInt16(out Int16 value)
{
if (Host.EncodeInt)
return ReadEncodedInt16();
value = 0;
if (Host.EncodeInt && Host is Binary bn)
return bn.TryReadEncodedInt16(out value);
const Int32 SIZE = 2;
#if NETCOREAPP || NETSTANDARD2_1
Span<Byte> buffer = stackalloc Byte[SIZE];
if (Host.ReadBytes(buffer) == 0) return false;
if (Host.IsLittleEndian)
value = BinaryPrimitives.ReadInt16LittleEndian(buffer);
else
return BitConverter.ToInt16(ReadIntBytes(2), 0);
value = BinaryPrimitives.ReadInt16BigEndian(buffer);
#else
var buffer = Pool.Shared.Rent(SIZE);
if (Host.ReadBytes(buffer, 0, SIZE) == 0) return false;
if (!Host.IsLittleEndian) Array.Reverse(buffer, 0, SIZE);
value = BitConverter.ToInt16(buffer, 0);
Pool.Shared.Return(buffer);
#endif
return true;
}
/// <summary>从当前流中读取 4 字节有符号整数,并使流的当前位置提升 4 个字节。</summary>
/// <returns></returns>
public virtual Int32 ReadInt32()
public virtual Boolean TryReadInt32(out Int32 value)
{
if (Host.EncodeInt)
return ReadEncodedInt32();
value = 0;
if (Host.EncodeInt && Host is Binary bn)
return bn.TryReadEncodedInt32(out value);
const Int32 SIZE = 4;
#if NETCOREAPP || NETSTANDARD2_1
Span<Byte> buffer = stackalloc Byte[SIZE];
if (Host.ReadBytes(buffer) == 0) return false;
if (Host.IsLittleEndian)
value = BinaryPrimitives.ReadInt32LittleEndian(buffer);
else
return BitConverter.ToInt32(ReadIntBytes(4), 0);
value = BinaryPrimitives.ReadInt32BigEndian(buffer);
#else
var buffer = Pool.Shared.Rent(SIZE);
if (Host.ReadBytes(buffer, 0, SIZE) == 0) return false;
if (!Host.IsLittleEndian) Array.Reverse(buffer, 0, SIZE);
value = BitConverter.ToInt32(buffer, 0);
Pool.Shared.Return(buffer);
#endif
return true;
}
/// <summary>从当前流中读取 8 字节有符号整数,并使流的当前位置向前移动 8 个字节。</summary>
/// <returns></returns>
public virtual Int64 ReadInt64()
public virtual Boolean TryReadInt64(out Int64 value)
{
if (Host.EncodeInt)
return ReadEncodedInt64();
value = 0;
if (Host.EncodeInt && Host is Binary bn)
return bn.TryReadEncodedInt64(out value);
const Int32 SIZE = 8;
#if NETCOREAPP || NETSTANDARD2_1
Span<Byte> buffer = stackalloc Byte[SIZE];
if (Host.ReadBytes(buffer) == 0) return false;
if (Host.IsLittleEndian)
value = BinaryPrimitives.ReadInt64LittleEndian(buffer);
else
return BitConverter.ToInt64(ReadIntBytes(8), 0);
value = BinaryPrimitives.ReadInt64BigEndian(buffer);
#else
var buffer = Pool.Shared.Rent(SIZE);
if (Host.ReadBytes(buffer, 0, SIZE) == 0) return false;
if (!Host.IsLittleEndian) Array.Reverse(buffer, 0, SIZE);
value = BitConverter.ToInt64(buffer, 0);
Pool.Shared.Return(buffer);
#endif
return true;
}
#endregion
#region
/// <summary>使用 Little-Endian 编码从当前流中读取 2 字节无符号整数,并将流的位置提升 2 个字节。</summary>
/// <returns></returns>
//[CLSCompliant(false)]
public virtual UInt16 ReadUInt16() => (UInt16)ReadInt16();
/// <summary>从当前流中读取 4 字节无符号整数并使流的当前位置提升 4 个字节。</summary>
/// <returns></returns>
//[CLSCompliant(false)]
public virtual UInt32 ReadUInt32() => (UInt32)ReadInt32();
/// <summary>从当前流中读取 8 字节无符号整数并使流的当前位置提升 8 个字节。</summary>
/// <returns></returns>
//[CLSCompliant(false)]
public virtual UInt64 ReadUInt64() => (UInt64)ReadInt64();
#endregion
#region
/// <summary>从当前流中读取 4 字节浮点值,并使流的当前位置提升 4 个字节。</summary>
/// <returns></returns>
public virtual Single ReadSingle() => BitConverter.ToSingle(ReadBytes(4), 0);
public virtual Boolean TryReadSingle(out Single value)
{
value = 0;
const Int32 SIZE = 4;
#if NET5_0_OR_GREATER
Span<Byte> buffer = stackalloc Byte[SIZE];
if (Host.ReadBytes(buffer) == 0) return false;
if (Host.IsLittleEndian)
value = BinaryPrimitives.ReadSingleLittleEndian(buffer);
else
value = BinaryPrimitives.ReadSingleBigEndian(buffer);
#else
var buffer = Pool.Shared.Rent(SIZE);
if (Host.ReadBytes(buffer, 0, SIZE) == 0) return false;
if (!Host.IsLittleEndian) Array.Reverse(buffer, 0, SIZE);
value = BitConverter.ToSingle(buffer, 0);
Pool.Shared.Return(buffer);
#endif
return true;
}
/// <summary>从当前流中读取 8 字节浮点值,并使流的当前位置提升 8 个字节。</summary>
/// <returns></returns>
public virtual Double ReadDouble() => BitConverter.ToDouble(ReadBytes(8), 0);
public virtual Boolean TryReadDouble(out Double value)
{
value = 0;
const Int32 SIZE = 8;
#if NET5_0_OR_GREATER
Span<Byte> buffer = stackalloc Byte[SIZE];
if (Host.ReadBytes(buffer) == 0) return false;
if (Host.IsLittleEndian)
value = BinaryPrimitives.ReadDoubleLittleEndian(buffer);
else
value = BinaryPrimitives.ReadDoubleBigEndian(buffer);
#else
var buffer = Pool.Shared.Rent(SIZE);
if (Host.ReadBytes(buffer, 0, SIZE) == 0) return false;
if (!Host.IsLittleEndian) Array.Reverse(buffer, 0, SIZE);
value = BitConverter.ToDouble(buffer, 0);
Pool.Shared.Return(buffer);
#endif
return true;
}
#endregion
#region
/// <summary>从当前流中读取下一个字符,并根据所使用的 Encoding 和从流中读取的特定字符,提升流的当前位置。</summary>
/// <returns></returns>
public virtual Char ReadChar() => Convert.ToChar(ReadByte());
/// <summary>从当前流中读取一个字符串。字符串有长度前缀7位压缩编码整数。</summary>
/// <returns></returns>
public virtual String ReadString()
public virtual Boolean TryReadString(out String value)
{
// 先读长度
var n = Host.ReadSize();
//if (n > 1000) n = Host.ReadSize();
if (n <= 0) return String.Empty;
//if (n == 0) return String.Empty;
value = String.Empty;
// 先读长度
if (!Host.TryReadSize(out var n)) return false;
if (n <= 0) return true;
#if NETCOREAPP || NETSTANDARD2_1
Span<Byte> buffer = stackalloc Byte[n];
if (Host.ReadBytes(buffer) == 0) return false;
#else
var buffer = Pool.Shared.Rent(n);
if (Host.ReadBytes(buffer, 0, n) == 0) return false;
#endif
var buffer = ReadBytes(n);
var enc = Host.Encoding ?? Encoding.UTF8;
var str = enc.GetString(buffer);
if (Host is Binary bn && bn.TrimZero && str != null) str = str.Trim('\0');
return str ?? String.Empty;
value = str ?? String.Empty;
#if NETCOREAPP || NETSTANDARD2_1
#else
Pool.Shared.Return(buffer);
#endif
return true;
}
#endregion
#region
/// <summary>从当前流中读取十进制数值,并将该流的当前位置提升十六个字节。</summary>
/// <returns></returns>
public virtual Decimal ReadDecimal()
{
var data = new Int32[4];
for (var i = 0; i < data.Length; i++)
{
data[i] = ReadInt32();
}
return new Decimal(data);
}
#endregion
#region 7
/// <summary>以压缩格式读取16位整数</summary>
/// <returns></returns>
public Int16 ReadEncodedInt16()
{
Byte b;
Int16 rs = 0;
Byte n = 0;
while (true)
{
b = ReadByte();
// 必须转为Int16否则可能溢出
rs += (Int16)((b & 0x7f) << n);
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 16) throw new FormatException("The number value is too large to read in compressed format!");
}
return rs;
}
/// <summary>以压缩格式读取32位整数</summary>
/// <returns></returns>
public Int32 ReadEncodedInt32()
{
Byte b;
var rs = 0;
Byte n = 0;
while (true)
{
b = ReadByte();
// 必须转为Int32否则可能溢出
rs += (b & 0x7f) << n;
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 32) throw new FormatException("The number value is too large to read in compressed format!");
}
return rs;
}
/// <summary>以压缩格式读取64位整数</summary>
/// <returns></returns>
public Int64 ReadEncodedInt64()
{
Byte b;
Int64 rs = 0;
Byte n = 0;
while (true)
{
b = ReadByte();
// 必须转为Int64否则可能溢出
rs += (Int64)(b & 0x7f) << n;
if ((b & 0x80) == 0) break;
n += 7;
if (n >= 64) throw new FormatException("The number value is too large to read in compressed format!");
}
return rs;
}
#endregion
#endregion
#region 7
[ThreadStatic]
private static Byte[]? _encodes;
/// <summary>
/// 以7位压缩格式写入16位整数小于7位用1个字节小于14位用2个字节。
/// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据所以每个字节实际可利用存储空间只有后7位。
/// </summary>
/// <param name="value">数值</param>
/// <returns>实际写入字节数</returns>
public Int32 WriteEncoded(Int16 value)
{
_encodes ??= new Byte[16];
var count = 0;
var num = (UInt16)value;
while (num >= 0x80)
{
_encodes[count++] = (Byte)(num | 0x80);
num = (UInt16)(num >> 7);
}
_encodes[count++] = (Byte)num;
Write(_encodes, 0, count);
return count;
}
/// <summary>
/// 以7位压缩格式写入32位整数小于7位用1个字节小于14位用2个字节。
/// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据所以每个字节实际可利用存储空间只有后7位。
/// </summary>
/// <param name="value">数值</param>
/// <returns>实际写入字节数</returns>
public Int32 WriteEncoded(Int32 value)
{
_encodes ??= new Byte[16];
var count = 0;
var num = (UInt32)value;
while (num >= 0x80)
{
_encodes[count++] = (Byte)(num | 0x80);
num >>= 7;
}
_encodes[count++] = (Byte)num;
Write(_encodes, 0, count);
return count;
}
/// <summary>
/// 以7位压缩格式写入64位整数小于7位用1个字节小于14位用2个字节。
/// 由每次写入的一个字节的第一位标记后面的字节是否还是当前数据所以每个字节实际可利用存储空间只有后7位。
/// </summary>
/// <param name="value">数值</param>
/// <returns>实际写入字节数</returns>
public Int32 WriteEncoded(Int64 value)
{
_encodes ??= new Byte[16];
var count = 0;
var num = (UInt64)value;
while (num >= 0x80)
{
_encodes[count++] = (Byte)(num | 0x80);
num >>= 7;
}
_encodes[count++] = (Byte)num;
Write(_encodes, 0, count);
return count;
}
#endregion
}

View File

@ -1,4 +1,5 @@
using System.Net;
using NewLife.Collections;
using NewLife.Data;
using NewLife.Reflection;
@ -111,31 +112,51 @@ public class BinaryNormal : BinaryHandlerBase
/// <returns></returns>
public override Boolean TryRead(Type type, ref Object? value)
{
var bn = (Host as Binary)!;
if (type == typeof(Guid))
{
value = new Guid(ReadBytes(16));
#if NETCOREAPP || NETSTANDARD2_1
Span<Byte> buffer = stackalloc Byte[16];
if (Host.ReadBytes(buffer) == 0) return false;
value = new Guid(buffer);
#else
var buffer = Pool.Shared.Rent(16);
if (Host.ReadBytes(buffer, 0, 16) == 0) return false;
value = new Guid(buffer);
Pool.Shared.Return(buffer);
#endif
return true;
}
else if (type == typeof(Byte[]))
{
value = ReadBytes(-1);
if (!TryReadArray(-1, out var buf)) return false;
value = buf;
return true;
}
else if (type == typeof(IPacket))
{
var buf = ReadBytes(-1);
if (!TryReadArray(-1, out var buf)) return false;
value = new ArrayPacket(buf);
return true;
}
else if (type == typeof(Packet))
{
var buf = ReadBytes(-1);
if (!TryReadArray(-1, out var buf)) return false;
value = new Packet(buf);
return true;
}
else if (type == typeof(Char[]))
{
value = ReadChars(-1);
//value = ReadChars(-1);
if (!TryReadArray(-1, out var buf)) return false;
value = Host.Encoding.GetChars(buf);
return true;
}
else if (type == typeof(DateTimeOffset))
@ -157,12 +178,16 @@ public class BinaryNormal : BinaryHandlerBase
#endif
else if (type == typeof(IPAddress))
{
value = new IPAddress(ReadBytes(-1));
if (!TryReadArray(-1, out var buf)) return false;
value = new IPAddress(buf);
return true;
}
else if (type == typeof(IPEndPoint))
{
var ip = new IPAddress(ReadBytes(-1));
if (!TryReadArray(-1, out var buf)) return false;
var ip = new IPAddress(buf);
var port = Host.Read<UInt16>();
value = new IPEndPoint(ip, port);
return true;
@ -173,27 +198,20 @@ public class BinaryNormal : BinaryHandlerBase
/// <summary>从当前流中将 count 个字节读入字节数组如果count小于0则先读取字节数组长度。</summary>
/// <param name="count">要读取的字节数。</param>
/// <param name="buffer"></param>
/// <returns></returns>
protected virtual Byte[] ReadBytes(Int32 count)
protected virtual Boolean TryReadArray(Int32 count, out Byte[] buffer)
{
if (Host is not Binary bn) throw new NotSupportedException();
buffer = [];
if (count < 0 && !Host.TryReadSize(out count)) return false;
var bc = bn.GetHandler<BinaryGeneral>();
if (bc == null) throw new NotSupportedException();
if (count <= 0) return true;
return bc.ReadBytes(count);
}
var max = IOHelper.MaxSafeArraySize;
if (count > max) throw new XException("Security required, reading large variable length arrays is not allowed {0:n0}>{1:n0}", count, max);
/// <summary>从当前流中读取 count 个字符,以字符数组的形式返回数据,并根据所使用的 Encoding 和从流中读取的特定字符,提升当前位置。</summary>
/// <param name="count">要读取的字符数。</param>
/// <returns></returns>
public virtual Char[] ReadChars(Int32 count)
{
if (count < 0) count = Host.ReadSize();
buffer = Host.ReadBytes(count);
// 首先按最小值读取
var data = ReadBytes(count);
return Host.Encoding.GetChars(data);
return true;
}
}

View File

@ -31,6 +31,10 @@ public interface IBinary : IFormatterX
/// <param name="count">要写入的字节数。</param>
void Write(Byte[] buffer, Int32 offset, Int32 count);
/// <summary>写入数据</summary>
/// <param name="buffer"></param>
void Write(ReadOnlySpan<Byte> buffer);
/// <summary>写入大小</summary>
/// <param name="size">要写入的大小值</param>
/// <returns>返回特性指定的固定长度,如果没有则返回-1</returns>
@ -42,14 +46,37 @@ public interface IBinary : IFormatterX
/// <returns></returns>
Byte ReadByte();
/// <summary>尝试从当前流中读取一个字节</summary>
/// <param name="value"></param>
/// <returns></returns>
Boolean TryReadByte(out Byte value);
/// <summary>从当前流中将 count 个字节读入字节数组</summary>
/// <param name="count">要读取的字节数。</param>
/// <returns></returns>
Byte[] ReadBytes(Int32 count);
#if NETCOREAPP || NETSTANDARD2_1
/// <summary>从当前流中读取字节数组</summary>
/// <param name="span">字节数组</param>
/// <returns></returns>
Int32 ReadBytes(Span<Byte> span);
#endif
/// <summary>从当前流中读取字节数组</summary>
/// <param name="buffer">字节数组</param>
/// <param name="offset">偏移量</param>
/// <param name="count">个数</param>
/// <returns></returns>
Int32 ReadBytes(Byte[] buffer, Int32 offset, Int32 count);
/// <summary>读取大小</summary>
/// <returns></returns>
Int32 ReadSize();
/// <summary>读取大小</summary>
/// <returns></returns>
Boolean TryReadSize(out Int32 value);
#endregion
}

View File

@ -14,10 +14,9 @@ public class FullStringAttribute : AccessorAttribute
{
if (formatter is Binary bn && context.Value != null && context.Member != null)
{
var buf = bn.Stream.ReadBytes(-1);
var str = bn.Encoding.GetString(buf);
bn.Total += buf.Length;
//var str = bn.Stream.ToStr(bn.Encoding);
//var buf = bn.ReadBytes(-1);
//var str = bn.Encoding.GetString(buf);
var str = bn.ReadBytes(-1).ToStr(bn.Encoding);
if (bn.TrimZero && str != null) str = str.Trim('\0');
context.Value.SetValue(context.Member, str);

View File

@ -1,34 +1,55 @@
using System.Diagnostics;
using NewLife.Log;
using NewLife.Model;
using NewLife.Reflection;
namespace NewLife.Threading;
/// <summary>定时器调度器</summary>
public class TimerScheduler : ILogFeature
public class TimerScheduler : IDisposable, ILogFeature
{
#region
private TimerScheduler(String name) => Name = name;
private static readonly Dictionary<String, TimerScheduler> _cache = [];
static TimerScheduler()
{
Host.RegisterExit(ClearAll);
}
/// <summary>创建指定名称的调度器</summary>
/// <param name="name"></param>
/// <returns></returns>
public static TimerScheduler Create(String name)
{
if (_cache.TryGetValue(name, out var ts)) return ts;
if (_cache.TryGetValue(name, out var scheduler)) return scheduler;
lock (_cache)
{
if (_cache.TryGetValue(name, out ts)) return ts;
if (_cache.TryGetValue(name, out scheduler)) return scheduler;
ts = new TimerScheduler(name);
_cache[name] = ts;
scheduler = new TimerScheduler(name);
_cache[name] = scheduler;
return ts;
// 跟随默认调度器使用日志
if (_cache.TryGetValue("Default", out var def) && def.Log != null)
scheduler.Log = def.Log;
return scheduler;
}
}
private static void ClearAll()
{
var schedulers = _cache;
if (schedulers == null || schedulers.Count == 0) return;
XTrace.WriteLine("TimerScheduler.ClearAll [{0}]", schedulers.Count);
foreach (var item in schedulers)
{
item.Value.Dispose();
}
schedulers.Clear();
}
/// <summary>默认调度器</summary>
public static TimerScheduler Default { get; } = Create("Default");
@ -41,6 +62,24 @@ public class TimerScheduler : ILogFeature
public static TimeProvider GlobalTimeProvider { get; set; } = TimeProvider.System;
#endregion
#region
private TimerScheduler(String name) => Name = name;
/// <summary>销毁</summary>
public void Dispose()
{
var ts = Timers?.ToList();
if (ts != null && ts.Count > 0)
{
XTrace.WriteLine("{0}Timer.ClearAll [{1}]", Name, ts.Count);
foreach (var item in ts)
{
item.Dispose();
}
}
}
#endregion
#region
/// <summary>名称</summary>
public String Name { get; private set; }
@ -66,7 +105,7 @@ public class TimerScheduler : ILogFeature
{
if (timer == null) throw new ArgumentNullException(nameof(timer));
using var span = DefaultTracer.Instance?.NewSpan("timer:Add", timer.ToString());
using var span = DefaultTracer.Instance?.NewSpan("timer:Add", new { Name, timer = timer.ToString() });
timer.Id = Interlocked.Increment(ref _tid);
WriteLog("Timer.Add {0}", timer);
@ -104,7 +143,7 @@ public class TimerScheduler : ILogFeature
{
if (timer == null || timer.Id == 0) return;
using var span = DefaultTracer.Instance?.NewSpan("timer:Remove", reason + " " + timer);
using var span = DefaultTracer.Instance?.NewSpan("timer:Remove", new { Name, timer = timer.ToString(), reason });
WriteLog("Timer.Remove {0} reason:{1}", timer, reason);
lock (this)

View File

@ -6,7 +6,7 @@
<Description>扩展加密算法</Description>
<Company>新生命开发团队</Company>
<Copyright>©2002-2025 NewLife</Copyright>
<VersionPrefix>11.5</VersionPrefix>
<VersionPrefix>11.6</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>

View File

@ -14,7 +14,30 @@
## 框架概述
NewLife 由多个组件库组成NewLife.Core 提供了基础。该框架支持从 .NET 4.5 到 .NET 9.0 的多个 .NET 版本,从而在不同环境中实现广泛的兼容性。
NewLife 由多个组件库组成NewLife.Core 提供了基础。该框架支持从 .NET 2.0 到 .NET 9.0 的多个 .NET 版本,从而在不同环境中实现广泛的兼容性。
## 快速拥有
使用NewLife组件的最简便方式是从Nuget引用例如在项目Nuget管理中搜索`NewLife.Core` 并引入。
NewLife组件由社区共创20多年使用MIT开源协议**任何人可任意修改并再次发行**(无需声明来源)!许多企业基于此构建内部开发框架时,甚至可通过批量替换源码中所有`NewLife`字符串为贵公司名实现私有化定制。
团队始终秉承开放态度不仅支持VisualStudio最新正式版打开解决方案编译也兼容`dotnet build`命令行编译,项目文件摒弃复杂功能以追求简单易用,真正做到开箱即用。
我们公开强命名证书`newlife.snk`以支持独自编译替换程序集。
命令行中运行以下命令快速体验NewLife组件
```
dotnet new install NewLife.Templates
dotnet new nconsole --name test
cd test
dotnet run
```

View File

@ -29,20 +29,20 @@
private void InitializeComponent()
{
groupBox1 = new GroupBox();
btnDownloadPlugin = new Button();
btnOpenAsync = new Button();
txtServer = new TextBox();
btnOpen = new Button();
label1 = new Label();
groupBox2 = new GroupBox();
btnCallAsync = new Button();
textBox2 = new TextBox();
txtArgument = new TextBox();
label3 = new Label();
btnCall = new Button();
cbApi = new ComboBox();
label2 = new Label();
groupBox3 = new GroupBox();
richTextBox1 = new RichTextBox();
btnDownloadPlugin = new Button();
groupBox1.SuspendLayout();
groupBox2.SuspendLayout();
groupBox3.SuspendLayout();
@ -65,6 +65,17 @@
groupBox1.TabStop = false;
groupBox1.Text = "数据库连接";
//
// btnDownloadPlugin
//
btnDownloadPlugin.Location = new Point(766, 19);
btnDownloadPlugin.Margin = new Padding(3, 2, 3, 2);
btnDownloadPlugin.Name = "btnDownloadPlugin";
btnDownloadPlugin.Size = new Size(106, 45);
btnDownloadPlugin.TabIndex = 5;
btnDownloadPlugin.Text = "插件下载";
btnDownloadPlugin.UseVisualStyleBackColor = true;
btnDownloadPlugin.Click += btnDownloadPlugin_Click;
//
// btnOpenAsync
//
btnOpenAsync.Location = new Point(630, 19);
@ -108,7 +119,7 @@
//
groupBox2.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
groupBox2.Controls.Add(btnCallAsync);
groupBox2.Controls.Add(textBox2);
groupBox2.Controls.Add(txtArgument);
groupBox2.Controls.Add(label3);
groupBox2.Controls.Add(btnCall);
groupBox2.Controls.Add(cbApi);
@ -133,12 +144,12 @@
btnCallAsync.UseVisualStyleBackColor = true;
btnCallAsync.Click += btnCallAsync_Click;
//
// textBox2
// txtArgument
//
textBox2.Location = new Point(96, 79);
textBox2.Name = "textBox2";
textBox2.Size = new Size(346, 26);
textBox2.TabIndex = 4;
txtArgument.Location = new Point(96, 79);
txtArgument.Name = "txtArgument";
txtArgument.Size = new Size(346, 26);
txtArgument.TabIndex = 4;
//
// label3
//
@ -199,17 +210,6 @@
richTextBox1.TabIndex = 0;
richTextBox1.Text = "";
//
// btnDownloadPlugin
//
btnDownloadPlugin.Location = new Point(766, 19);
btnDownloadPlugin.Margin = new Padding(3, 2, 3, 2);
btnDownloadPlugin.Name = "btnDownloadPlugin";
btnDownloadPlugin.Size = new Size(106, 45);
btnDownloadPlugin.TabIndex = 5;
btnDownloadPlugin.Text = "插件下载";
btnDownloadPlugin.UseVisualStyleBackColor = true;
btnDownloadPlugin.Click += btnDownloadPlugin_Click;
//
// FrmMain
//
AutoScaleDimensions = new SizeF(10F, 20F);
@ -243,7 +243,7 @@
private Button btnCall;
private ComboBox cbApi;
private Label label2;
private TextBox textBox2;
private TextBox txtArgument;
private Label label3;
private Button btnOpenAsync;
private Button btnCallAsync;

View File

@ -3,6 +3,7 @@ using NewLife;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Remoting;
using NewLife.Serialization;
using NewLife.Threading;
using NewLife.Web;
@ -129,13 +130,15 @@ public partial class FrmMain : Form
private void btnCall_Click(object sender, EventArgs e)
{
var act = cbApi.Text.Substring(" ", "(");
var rs = _client.Invoke<String>(act, null);
var args = txtArgument.Text.Trim().DecodeJson();
var rs = _client.Invoke<String>(act, args);
}
private async void btnCallAsync_Click(object sender, EventArgs e)
{
var act = cbApi.Text.Substring(" ", "(");
var rs = await _client.InvokeAsync<String>(act, null);
var args = txtArgument.Text.Trim().DecodeJson();
var rs = await _client.InvokeAsync<String>(act, args);
}
private void btnDownloadPlugin_Click(object sender, EventArgs e)

View File

@ -1,10 +1,14 @@
using NewLife.Log;
using NewLife.Model;
using NewLife.Threading;
using Stardust;
using Zero.EchoServer;
// 启用控制台日志,拦截所有异常
XTrace.UseConsole();
#if DEBUG
TimerScheduler.Default.Log = XTrace.Log;
#endif
var services = ObjectContainer.Current;
@ -38,5 +42,6 @@ star?.Service?.Register("EchoServer", () => $"tcp://*:{server.Port},udp://*:{ser
// 阻塞,等待友好退出
var host = services.BuildHost();
//(host as Host).MaxTime = 5_000;
await host.RunAsync();
server.Stop("stop");

View File

@ -5,11 +5,15 @@ using NewLife.Http;
using NewLife.Log;
using NewLife.Model;
using NewLife.Remoting;
using NewLife.Threading;
using Stardust;
using Zero.HttpServer;
// 启用控制台日志,拦截所有异常
XTrace.UseConsole();
#if DEBUG
TimerScheduler.Default.Log = XTrace.Log;
#endif
var services = ObjectContainer.Current;
@ -56,7 +60,7 @@ server.Start();
XTrace.WriteLine("服务端启动完成!");
// 注册到星尘,非必须
await star.Service?.RegisterAsync("Zero.HttpServer", $"http://*:{server.Port}");
star.Service?.RegisterAsync("Zero.HttpServer", $"http://*:{server.Port}");
// 客户端测试,非服务端代码,正式使用时请注释掉
_ = Task.Run(ClientTest.HttpClientTest);
@ -65,5 +69,6 @@ _ = Task.Run(ClientTest.WebSocketClientTest);
// 异步阻塞,友好退出
var host = services.BuildHost();
(host as Host).MaxTime = 10_000;
await host.RunAsync();
server.Stop("stop");

View File

@ -2,6 +2,7 @@
using NewLife.Caching.Services;
using NewLife.Log;
using NewLife.Model;
using NewLife.Threading;
using Stardust;
using Zero.Server;
using Zero.TcpServer;
@ -9,6 +10,9 @@ using Zero.TcpServer.Handlers;
// 启用控制台日志,拦截所有异常
XTrace.UseConsole();
#if DEBUG
TimerScheduler.Default.Log = XTrace.Log;
#endif
//var services = new ServiceCollection();
var services = ObjectContainer.Current;
@ -60,5 +64,6 @@ _ = Task.Run(ClientTest.UdpSessionTest);
// 阻塞,等待友好退出
var host = services.BuildHost();
(host as Host).MaxTime = 10_000;
await host.RunAsync();
server.Stop("stop");

View File

@ -1,11 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Data;
using NewLife;
using NewLife.Data;
using Xunit;
using NewLife;
using System.Data;
namespace XUnitTest.Data;
@ -16,12 +12,12 @@ public class DbTableTests
{
var dt = new DbTable
{
Columns = new[] { "Id", "Name", "CreateTime" },
Rows = new List<Object[]>
{
new Object[] { 123, "Stone", DateTime.Now },
new Object[] { 456, "NewLife", DateTime.Today }
}
Columns = ["Id", "Name", "CreateTime"],
Rows =
[
[123, "Stone", DateTime.Now],
[456, "NewLife", DateTime.Today]
]
};
Assert.Equal(123, dt.Get<Int32>(0, "Id"));
@ -65,12 +61,12 @@ public class DbTableTests
{
var db = new DbTable
{
Columns = new[] { "Id", "Name", "CreateTime" },
Rows = new List<Object[]>
{
new Object[] { 123, "Stone", DateTime.Now },
new Object[] { 456, "NewLife", DateTime.Today }
}
Columns = ["Id", "Name", "CreateTime"],
Rows =
[
[123, "Stone", DateTime.Now],
[456, "NewLife", DateTime.Today]
]
};
var json = db.ToJson();
@ -86,12 +82,12 @@ public class DbTableTests
{
var db = new DbTable
{
Columns = new[] { "Id", "Name", "CreateTime" },
Rows = new List<Object[]>
{
new Object[] { 123, "Stone", DateTime.Now },
new Object[] { 456, "NewLife", DateTime.Today }
}
Columns = ["Id", "Name", "CreateTime"],
Rows =
[
[123, "Stone", DateTime.Now],
[456, "NewLife", DateTime.Today]
]
};
var list = db.ToDictionary();
@ -110,14 +106,14 @@ public class DbTableTests
var dt = new DbTable
{
Columns = new[] { "ID", "Name", "Time" },
Types = new[] { typeof(Int32), typeof(String), typeof(DateTime) },
Rows = new List<Object[]>
{
new Object[] { 11, "Stone", DateTime.Now.Trim() },
new Object[] { 22, "大石头", DateTime.Today },
new Object[] { 33, "新生命", DateTime.UtcNow.Trim() }
}
Columns = ["ID", "Name", "Time"],
Types = [typeof(Int32), typeof(String), typeof(DateTime)],
Rows =
[
[11, "Stone", DateTime.Now.Trim()],
[22, "大石头", DateTime.Today],
[33, "新生命", DateTime.UtcNow.Trim()]
]
};
dt.SaveFile(file, true);
@ -144,14 +140,14 @@ public class DbTableTests
var dt = new DbTable
{
Columns = new[] { "ID", "Name", "Time" },
Types = new[] { typeof(Int32), typeof(String), typeof(DateTime) },
Rows = new List<Object[]>
{
new Object[] { 11, "Stone", DateTime.Now.Trim() },
new Object[] { 22, "大石头", DateTime.Today },
new Object[] { 33, "新生命", DateTime.UtcNow.Trim() }
}
Columns = ["ID", "Name", "Time"],
Types = [typeof(Int32), typeof(String), typeof(DateTime)],
Rows =
[
[11, "Stone", DateTime.Now.Trim()],
[22, "大石头", DateTime.Today],
[33, "新生命", DateTime.UtcNow.Trim()]
]
};
var pk = dt.ToPacket();

View File

@ -36,7 +36,7 @@ public class ExcelReaderTests
else if (row1[i] is TimeSpan ts)
Assert.Equal(TimeSpan.Parse(values[i]), ts);
else
Assert.Equal(values[i], row1[i]);
Assert.Equal(values[i], row1[i] + "");
}
}

View File

@ -9,6 +9,7 @@ using NewLife.Log;
using NewLife.Messaging;
using NewLife.Net;
using NewLife.Net.Handlers;
using NewLife.Security;
using NewLife.Serialization;
using Xunit;
@ -20,9 +21,21 @@ public class ISocketRemoteTests
{
FileInfo src = null;
var di = "D:\\Tools".AsDirectory();
if (di.Exists) src = di.GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "../../".AsDirectory().GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
src ??= "data/".AsDirectory().GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
if (!di.Exists) di = "../../".AsDirectory();
if (!di.Exists) di = "data/".AsDirectory();
if (di.Exists)
src = di.GetFiles().Where(e => e.Length < 10 * 1024 * 1024).OrderByDescending(e => e.Length).FirstOrDefault();
var file = "bigSrc.bin".GetFullPath();
if (src == null && File.Exists(file)) src = file.AsFile();
if (src == null)
{
var buf = Rand.NextBytes(10 * 1024 * 1024);
File.WriteAllBytes(file, buf);
src = file.AsFile();
}
XTrace.WriteLine("发送文件:{0}", src.FullName);
XTrace.WriteLine("文件大小:{0}", src.Length.ToGMK());
@ -33,7 +46,7 @@ public class ISocketRemoteTests
public void SendFile()
{
// 目标文件
var file = "bigfile.bin".GetFullPath();
var file = "bigDest.bin".GetFullPath();
if (File.Exists(file)) File.Delete(file);
using var target = File.Create(file);
@ -117,6 +130,7 @@ public class ISocketRemoteTests
Thread.Sleep(1000);
// 验证接收文件是否完整
Assert.NotEmpty(svr.Files);
var dest = svr.Files[^1].AsFile();
Assert.Equal(src.Length, dest.Length);
Assert.Equal(src.MD5().ToHex(), dest.MD5().ToHex());

View File

@ -27,6 +27,11 @@ public class BinaryTests
Assert.Equal(model.Code, model2.Code);
Assert.Equal(model.Name, model2.Name);
Assert.Equal(bn2.Total, pk.Length);
//Assert.False(bn2.EndOfStream);
Object? value = null;
Assert.False(bn2.TryRead(typeof(MyModel), ref value));
Assert.True(bn2.EndOfStream);
}
[Fact]