diff --git a/NewLife.Core/Data/IPacket.cs b/NewLife.Core/Data/IPacket.cs index fb98e6bbf..99cfe6a9a 100644 --- a/NewLife.Core/Data/IPacket.cs +++ b/NewLife.Core/Data/IPacket.cs @@ -1,5 +1,6 @@ using System.Buffers; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; using NewLife.Collections; @@ -293,6 +294,32 @@ public static Boolean TryGetSpan(this IPacket pk, out Span span) span = default; return false; } + + /// 尝试扩展头部,用于填充包头,减少内存分配 + /// + /// + /// + /// + public static Boolean TryExpandHeader(this IPacket pk, Int32 size, [NotNullWhen(true)] out IPacket? newPacket) + { + newPacket = null; + + if (pk is ArrayPacket ap && ap.Offset >= size) + { + newPacket = new ArrayPacket(ap.Buffer, ap.Offset - size, ap.Length + size) { Next = ap.Next }; + + return true; + } + else if (pk is OwnerPacket owner && owner.Offset >= size) + { + newPacket = new OwnerPacket(owner.Buffer, owner.Offset - size, owner.Length + size) { Next = owner.Next }; + owner.Free(); + + return true; + } + + return false; + } } /// 所有权内存包。具有所有权管理,不再使用时释放 @@ -346,7 +373,7 @@ public OwnerPacket(Int32 length) /// /// 长度 /// - private OwnerPacket(Byte[] buffer, Int32 offset, Int32 length) + public OwnerPacket(Byte[] buffer, Int32 offset, Int32 length) { if (offset < 0 || length < 0 || offset + length > buffer.Length) throw new ArgumentOutOfRangeException(nameof(length), "Length must be non-negative and less than or equal to the memory owner's length."); @@ -502,6 +529,13 @@ Boolean IPacket.TryGetArray(out ArraySegment segment) return false; } + /// 释放所有权,不再使用 + public void Free() + { + _buffer = null!; + Next = null; + } + /// 钉住内存 /// /// diff --git a/NewLife.Core/Http/WebSocketMessage.cs b/NewLife.Core/Http/WebSocketMessage.cs index aada111e1..d3dc25d12 100644 --- a/NewLife.Core/Http/WebSocketMessage.cs +++ b/NewLife.Core/Http/WebSocketMessage.cs @@ -1,4 +1,5 @@ using System.Buffers.Binary; +using System.Drawing; using System.Text; using NewLife.Buffers; using NewLife.Data; @@ -132,6 +133,7 @@ public virtual IPacket ToPacket() { var body = Payload; var len = body == null ? 0 : body.Total; + var masks = MaskKey; // 特殊处理关闭消息 if (len == 0 && Type == WebSocketMessageType.Close) @@ -140,7 +142,22 @@ public virtual IPacket ToPacket() if (!StatusDescription.IsNullOrEmpty()) len += Encoding.UTF8.GetByteCount(StatusDescription); } - var rs = new OwnerPacket(1 + 1 + 8 + 4 + len); + //var rs = new OwnerPacket(1 + 1 + 8 + 4 + len); + var size = len switch + { + < 126 => 1 + 1, + < 0xFFFF => 1 + 1 + 2, + _ => 1 + 1 + 8, + }; + if (masks != null) size += masks.Length; + + if (body == null || !body.TryExpandHeader(size, out var rs)) + { + if (Type == WebSocketMessageType.Close) + rs = new OwnerPacket(size + len); + else + rs = new OwnerPacket(size) { Next = body }; + } var writer = new SpanWriter(rs.GetSpan()) { IsLittleEndian = false @@ -148,8 +165,6 @@ public virtual IPacket ToPacket() writer.WriteByte((Byte)(0x80 | (Byte)Type)); - var masks = MaskKey; - /* * 数据长度 * len < 126 单字节表示长度 @@ -208,7 +223,11 @@ public virtual IPacket ToPacket() { // 注意body可能是链式数据包 //writer.Write(body.GetSpan()); - return rs.Slice(0, writer.Position).Append(body); + + // 扩展得到的数据包,直接写入了头部,尾部数据不用拷贝也无需切片 + return rs; + + //return rs.Slice(0, writer.Position).Append(body); } else if (Type == WebSocketMessageType.Close) { diff --git a/NewLife.Core/Messaging/DefaultMessage.cs b/NewLife.Core/Messaging/DefaultMessage.cs index 17c72adca..2419aa3eb 100644 --- a/NewLife.Core/Messaging/DefaultMessage.cs +++ b/NewLife.Core/Messaging/DefaultMessage.cs @@ -123,9 +123,7 @@ public override IPacket ToPacket() // 增加4字节头部,如果负载数据之前有足够空间则直接使用,否则新建数据包形成链式结构 var size = len < 0xFFFF ? 4 : 8; - var pk = body is ArrayPacket ap && ap.Offset >= size - ? new ArrayPacket(ap.Buffer, ap.Offset - size, ap.Length + size) { Next = ap.Next } - : new ArrayPacket(new Byte[size]) { Next = body }; + if (body == null || !body.TryExpandHeader(size, out var pk)) pk = new OwnerPacket(size) { Next = body }; // 标记位 var header = pk.GetSpan(); diff --git a/XUnitTest.Core/Http/WebSocketMessageTests.cs b/XUnitTest.Core/Http/WebSocketMessageTests.cs index 512631218..5edca6fec 100644 --- a/XUnitTest.Core/Http/WebSocketMessageTests.cs +++ b/XUnitTest.Core/Http/WebSocketMessageTests.cs @@ -1,4 +1,5 @@ using System; +using NewLife.Buffers; using NewLife.Data; using NewLife.Http; using NewLife.Messaging; @@ -104,4 +105,49 @@ public void DefaultMessageOverWebsocket() Assert.Equal(dm.Sequence, dm2.Sequence); Assert.Equal(dm.Payload.ToHex(), dm2.Payload.ToHex()); } + + [Fact] + public void DefaultMessageOverWebsocket2() + { + var str = "Hello NewLife"; + var buf = new Byte[8 + str.Length]; + var src = new ArrayPacket(buf, 8, buf.Length - 8); + var writer = new SpanWriter(src.GetSpan()); + writer.Write(str, -1); + + var dm = new DefaultMessage + { + Flag = 0x01, + Sequence = 0xAB, + Payload = src + }; + var pk = dm.ToPacket(); + Assert.Null(pk.Next); + + var msg = new WebSocketMessage + { + Type = WebSocketMessageType.Binary, + Payload = pk, + }; + + pk = msg.ToPacket(); + Assert.Null(pk.Next); + Assert.Equal("821101AB0D0048656C6C6F204E65774C696665", pk.ToHex()); + + var msg2 = new WebSocketMessage(); + var rs = msg2.Read(pk); + Assert.True(rs); + + Assert.Equal(msg.Type, msg2.Type); + Assert.Equal(msg.Payload.ToHex(), msg2.Payload.ToHex()); + + var dm2 = new DefaultMessage(); + rs = dm2.Read(msg2.Payload); + Assert.True(rs); + + Assert.Equal(dm.Flag, dm2.Flag); + Assert.Equal(dm.Sequence, dm2.Sequence); + Assert.Equal(dm.Payload.ToHex(), dm2.Payload.ToHex()); + Assert.Equal(str, dm2.Payload.ToStr()); + } } \ No newline at end of file