diff --git a/NewLife.Core/Data/IPacket.cs b/NewLife.Core/Data/IPacket.cs index b7a1fe05a..fb98e6bbf 100644 --- a/NewLife.Core/Data/IPacket.cs +++ b/NewLife.Core/Data/IPacket.cs @@ -381,14 +381,14 @@ protected override void Dispose(Boolean disposing) /// public Memory GetMemory() => new(_buffer, _offset, _length); - /// 切片得到新数据包,同时转移内存管理权,当前数据包应尽快停止使用 - /// - /// 可能是引用同一块内存,也可能是新的内存。 - /// 可能就是当前数据包,也可能引用相同的所有者或数组。 - /// - /// 偏移 - /// 个数。默认-1表示到末尾 - IPacket IPacket.Slice(Int32 offset, Int32 count) => Slice(offset, count); + ///// 切片得到新数据包,同时转移内存管理权,当前数据包应尽快停止使用 + ///// + ///// 可能是引用同一块内存,也可能是新的内存。 + ///// 可能就是当前数据包,也可能引用相同的所有者或数组。 + ///// + ///// 偏移 + ///// 个数。默认-1表示到末尾 + //IPacket IPacket.Slice(Int32 offset, Int32 count) => Slice(offset, count); /// 切片得到新数据包,同时转移内存管理权,当前数据包应尽快停止使用 /// @@ -397,7 +397,7 @@ protected override void Dispose(Boolean disposing) /// /// 偏移 /// 个数。默认-1表示到末尾 - public OwnerPacket Slice(Int32 offset, Int32 count) + public OwnerPacket SliceSingle(Int32 offset, Int32 count) { // 带有Next时,不支持Slice if (Next != null) throw new NotSupportedException("Slice with Next"); @@ -415,6 +415,72 @@ public OwnerPacket Slice(Int32 offset, Int32 count) return new OwnerPacket(buffer, _offset + offset, count); } + /// 切片得到新数据包,同时转移内存管理权,当前数据包应尽快停止使用 + /// + /// 可能是引用同一块内存,也可能是新的内存。 + /// 可能就是当前数据包,也可能引用相同的所有者或数组。 + /// + /// 偏移 + /// 个数。默认-1表示到末尾 + public IPacket Slice(Int32 offset, Int32 count) + { + //// 带有Next时,不支持Slice + //if (Next != null) throw new NotSupportedException("Slice with Next"); + + // 只有一次Slice机会 + if (_buffer == null) throw new InvalidDataException(); + + var start = _offset + offset; + var remain = _length - offset; + + // 超出范围 + if (count > Total - offset) throw new ArgumentOutOfRangeException(nameof(count), "count must be non-negative and less than or equal to the memory owner's length."); + + // 单个数据包 + if (Next == null) + { + // 转移管理权 + var buffer = _buffer; + _buffer = null!; + + if (count < 0 || count > remain) count = remain; + return new OwnerPacket(buffer, start, count); + } + else + { + // 如果当前段用完,则取下一段 + if (remain <= 0) + { + var rs = Next.Slice(offset - _length, count); + // 切分后数据包要用到Next,这里清空避免当前包释放时把Next也释放 + Next = null; + return rs; + } + + // 转移管理权 + var buffer = _buffer; + _buffer = null!; + + // 当前包用一截,剩下的全部 + if (count < 0) + { + var rs = new OwnerPacket(buffer, start, remain) { Next = Next }; + Next = null; + return rs; + } + + // 当前包可以读完 + if (count <= remain) return new OwnerPacket(buffer, start, count); + + // 当前包用一截,剩下的再截取 + { + var rs = new OwnerPacket(buffer, start, remain) { Next = Next.Slice(0, count - remain) }; + Next = null; + return rs; + } + } + } + /// 尝试获取数据段 /// /// diff --git a/NewLife.Core/Http/HttpBase.cs b/NewLife.Core/Http/HttpBase.cs index 7ed70c01d..123f9cfbc 100644 --- a/NewLife.Core/Http/HttpBase.cs +++ b/NewLife.Core/Http/HttpBase.cs @@ -143,7 +143,7 @@ public virtual IOwnerPacket Build() if (body != null) writer.Write(body.GetSpan()); - return pk.Slice(0, writer.Position); + return pk.SliceSingle(0, writer.Position); } /// 创建头部 diff --git a/NewLife.Core/Http/TinyHttpClient.cs b/NewLife.Core/Http/TinyHttpClient.cs index b07e13959..9a83ec8e5 100644 --- a/NewLife.Core/Http/TinyHttpClient.cs +++ b/NewLife.Core/Http/TinyHttpClient.cs @@ -144,7 +144,7 @@ protected virtual async Task SendDataAsync(Uri? uri, IPacket? requ var count = await ns.ReadAsync(pk.Buffer, 0, pk.Length, source.Token).ConfigureAwait(false); #endif - return pk.Slice(0, count); + return pk.SliceSingle(0, count); } /// 异步发出请求,并接收响应 diff --git a/NewLife.Core/Http/WebSocket.cs b/NewLife.Core/Http/WebSocket.cs index f569e066b..17c82d6a9 100644 --- a/NewLife.Core/Http/WebSocket.cs +++ b/NewLife.Core/Http/WebSocket.cs @@ -110,11 +110,12 @@ private void Send(WebSocketMessage msg) var socket = Context?.Socket; if (session == null && socket == null) throw new ObjectDisposedException(nameof(Context)); - using var data = msg.ToPacket(); + var data = msg.ToPacket(); if (session != null) session.Send(data); else socket?.Send(data); + data.TryDispose(); } /// 发送消息 @@ -138,8 +139,9 @@ public void SendAll(IPacket data, WebSocketMessageType type, Func想所有连接发送文本消息 diff --git a/NewLife.Core/Http/WebSocketMessage.cs b/NewLife.Core/Http/WebSocketMessage.cs index 3476d669c..aada111e1 100644 --- a/NewLife.Core/Http/WebSocketMessage.cs +++ b/NewLife.Core/Http/WebSocketMessage.cs @@ -128,10 +128,10 @@ public Boolean Read(IPacket pk) /// 把消息转为封包 /// - public virtual IOwnerPacket ToPacket() + public virtual IPacket ToPacket() { - var pk = Payload; - var len = pk == null ? 0 : pk.Total; + var body = Payload; + var len = body == null ? 0 : body.Total; // 特殊处理关闭消息 if (len == 0 && Type == WebSocketMessageType.Close) @@ -194,9 +194,9 @@ public virtual IOwnerPacket ToPacket() writer.Write(masks); // 掩码混淆数据。直接在数据缓冲区修改,避免拷贝 - if (Payload != null) + if (body != null) { - var data = Payload.GetSpan(); + var data = body.GetSpan(); for (var i = 0; i < len; i++) { data[i] = (Byte)(data[i] ^ masks[i % 4]); @@ -204,8 +204,12 @@ public virtual IOwnerPacket ToPacket() } } - if (pk != null && pk.Length > 0) - writer.Write(pk.GetSpan()); + if (body != null && body.Length > 0) + { + // 注意body可能是链式数据包 + //writer.Write(body.GetSpan()); + return rs.Slice(0, writer.Position).Append(body); + } else if (Type == WebSocketMessageType.Close) { writer.Write((Int16)CloseStatus); diff --git a/NewLife.Core/Net/Handlers/MessageCodec.cs b/NewLife.Core/Net/Handlers/MessageCodec.cs index 709abb968..49bab7c72 100644 --- a/NewLife.Core/Net/Handlers/MessageCodec.cs +++ b/NewLife.Core/Net/Handlers/MessageCodec.cs @@ -48,14 +48,14 @@ public class MessageCodec : Handler public override Object? Write(IHandlerContext context, Object message) { // 谁申请,谁归还 - IOwnerPacket? owner = null; + IPacket? owner = null; if (message is T msg) { var rs = Encode(context, msg); if (rs == null) return null; message = rs; - owner = rs as IOwnerPacket; + owner = rs as IPacket; // 加入队列,忽略请求消息 if (message is IMessage msg2) @@ -73,7 +73,7 @@ public class MessageCodec : Handler finally { // 下游可能忘了释放内存,这里兜底释放 - owner?.Dispose(); + owner.TryDispose(); } } diff --git a/NewLife.Core/Net/Handlers/WebSocketCodec.cs b/NewLife.Core/Net/Handlers/WebSocketCodec.cs index 70c13b6d5..8841f9078 100644 --- a/NewLife.Core/Net/Handlers/WebSocketCodec.cs +++ b/NewLife.Core/Net/Handlers/WebSocketCodec.cs @@ -69,7 +69,7 @@ public override Boolean Close(IHandlerContext context, String reason) message = new WebSocketMessage { Type = WebSocketMessageType.Binary, Payload = pk }; // 谁申请,谁归还 - IOwnerPacket? owner = null; + IPacket? owner = null; if (message is WebSocketMessage msg) message = owner = msg.ToPacket(); @@ -80,7 +80,7 @@ public override Boolean Close(IHandlerContext context, String reason) finally { // 下游可能忘了释放内存,这里兜底释放 - owner?.Dispose(); + owner.TryDispose(); } } } diff --git a/NewLife.Core/Net/SessionBase.cs b/NewLife.Core/Net/SessionBase.cs index 51ff44316..cdc071b57 100644 --- a/NewLife.Core/Net/SessionBase.cs +++ b/NewLife.Core/Net/SessionBase.cs @@ -275,7 +275,7 @@ public Int32 Send(IPacket data) var size = Client.Receive(pk.Buffer, SocketFlags.None); if (span != null) span.Value = size; - return pk.Slice(0, size); + return pk.SliceSingle(0, size); } catch (Exception ex) { @@ -306,7 +306,7 @@ public Int32 Send(IPacket data) #endif if (span != null) span.Value = size; - return pk.Slice(0, size); + return pk.SliceSingle(0, size); } catch (Exception ex) { diff --git a/NewLife.Core/Net/TcpSession.cs b/NewLife.Core/Net/TcpSession.cs index ab1ca1628..fed7c4fd3 100644 --- a/NewLife.Core/Net/TcpSession.cs +++ b/NewLife.Core/Net/TcpSession.cs @@ -390,7 +390,7 @@ protected override Int32 OnSend(IPacket pk) var size = await ss.ReadAsync(pk.Buffer, 0, pk.Length, cancellationToken); if (span != null) span.Value = size; - return pk.Slice(0, size); + return pk.SliceSingle(0, size); } catch (Exception ex) { diff --git a/NewLife.Core/Net/UdpSession.cs b/NewLife.Core/Net/UdpSession.cs index 0d0f01014..c1d984446 100644 --- a/NewLife.Core/Net/UdpSession.cs +++ b/NewLife.Core/Net/UdpSession.cs @@ -253,7 +253,7 @@ public IOwnerPacket Receive() var size = Server.Client.ReceiveFrom(pk.Buffer, ref ep); if (span != null) span.Value = size; - return pk.Slice(0, size); + return pk.SliceSingle(0, size); } catch (Exception ex) { @@ -289,7 +289,7 @@ public IOwnerPacket Receive() #endif if (span != null) span.Value = size; - return pk.Slice(0, size); + return pk.SliceSingle(0, size); } catch (Exception ex) { diff --git a/XUnitTest.Core/Http/WebSocketMessageTests.cs b/XUnitTest.Core/Http/WebSocketMessageTests.cs new file mode 100644 index 000000000..512631218 --- /dev/null +++ b/XUnitTest.Core/Http/WebSocketMessageTests.cs @@ -0,0 +1,107 @@ +using System; +using NewLife.Data; +using NewLife.Http; +using NewLife.Messaging; +using Xunit; + +namespace XUnitTest.Http; + +public class WebSocketMessageTests +{ + [Fact] + public void Text() + { + var msg = new WebSocketMessage + { + Type = WebSocketMessageType.Text, + Payload = (ArrayPacket)$"Hello NewLife", + }; + + var pk = msg.ToPacket(); + Assert.Equal("810D48656C6C6F204E65774C696665", 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()); + } + + [Fact] + public void Ping() + { + var msg = new WebSocketMessage + { + Type = WebSocketMessageType.Ping, + Payload = (ArrayPacket)$"Ping {DateTime.UtcNow.ToFullString()}", + }; + + var pk = msg.ToPacket(); + Assert.StartsWith("891850696E67", 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()); + } + + [Fact] + public void Close() + { + var msg = new WebSocketMessage + { + Type = WebSocketMessageType.Close, + CloseStatus = 1000, + StatusDescription = "Finish", + }; + + var pk = msg.ToPacket(); + Assert.Equal("880803E846696E697368", pk.ToHex()); + + var msg2 = new WebSocketMessage(); + var rs = msg2.Read(pk); + Assert.True(rs); + + Assert.Equal(msg.Type, msg2.Type); + Assert.Equal(msg.CloseStatus, msg2.CloseStatus); + Assert.Equal(msg.StatusDescription, msg2.StatusDescription); + } + + [Fact] + public void DefaultMessageOverWebsocket() + { + var dm = new DefaultMessage + { + Flag = 0x01, + Sequence = 0xAB, + Payload = (ArrayPacket)"Hello NewLife" + }; + + var msg = new WebSocketMessage + { + Type = WebSocketMessageType.Binary, + Payload = dm.ToPacket(), + }; + + var pk = msg.ToPacket(); + 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()); + } +} \ No newline at end of file