Compare commits
24 Commits
30cd487960
...
b0114f7886
Author | SHA1 | Date |
---|---|---|
|
b0114f7886 | |
|
f83fe3ef23 | |
|
da2fa56053 | |
|
ab6eacceab | |
|
c601d83a11 | |
|
dd3f0de639 | |
|
580fbcdb53 | |
|
6490d3a3d7 | |
|
138844ab18 | |
|
102c8861ca | |
|
01d3f936fb | |
|
885a41778b | |
|
3b3189563d | |
|
efb1dd1bdb | |
|
9fb122e8c8 | |
|
40f70a5bdb | |
|
1b113ad776 | |
|
8562c4f2de | |
|
5154f1281f | |
|
4851dbf2de | |
|
895127a493 | |
|
0507e5c79a | |
|
7b4511ca60 | |
|
6f7906a33c |
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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温度获取,Buildroot,CPU温度和主板温度
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
25
Readme.MD
25
Readme.MD
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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] + "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue