This commit is contained in:
CortexCore
2023-10-06 23:43:19 +08:00
parent ebf9c1f526
commit 2c4710bc5d
186 changed files with 111802 additions and 764 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f609ad55e013f046a45995f7969b933
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,74 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// 用于调试的KCP IO 类没有Kcp功能
/// </summary>
public class FakeKcpIO : IKcpIO
{
QueuePipe<byte[]> recv = new QueuePipe<byte[]>();
public int Input(ReadOnlySpan<byte> span)
{
byte[] buffer = new byte[span.Length];
span.CopyTo(buffer);
recv.Write(buffer);
return 0;
}
public int Input(ReadOnlySequence<byte> span)
{
byte[] buffer = new byte[span.Length];
span.CopyTo(buffer);
return Input(buffer);
}
public async ValueTask RecvAsync(IBufferWriter<byte> writer, object options = null)
{
var buffer = await recv.ReadAsync().ConfigureAwait(false);
var target = writer.GetMemory(buffer.Length);
buffer.AsSpan().CopyTo(target.Span);
writer.Advance(buffer.Length);
}
public async ValueTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null)
{
var temp = await recv.ReadAsync().ConfigureAwait(false);
temp.AsSpan().CopyTo(buffer);
return temp.Length;
}
QueuePipe<byte[]> send = new QueuePipe<byte[]>();
public int Send(ReadOnlySpan<byte> span, object options = null)
{
byte[] buffer = new byte[span.Length];
span.CopyTo(buffer);
send.Write(buffer);
return 0;
}
public int Send(ReadOnlySequence<byte> span, object options = null)
{
byte[] buffer = new byte[span.Length];
span.CopyTo(buffer);
return Send(buffer);
}
public async ValueTask OutputAsync(IBufferWriter<byte> writer, object options = null)
{
var buffer = await send.ReadAsync().ConfigureAwait(false);
Write(writer, buffer);
}
private static void Write(IBufferWriter<byte> writer, byte[] buffer)
{
var span = writer.GetSpan(buffer.Length);
buffer.AsSpan().CopyTo(span);
writer.Advance(buffer.Length);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a87b1ccc19005148aaa66764cd6cc7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,148 @@
using System.Threading.Tasks;
using System.Threading;
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
using System.Buffers;
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// Kcp回调
/// </summary>
public interface IKcpCallback
{
/// <summary>
/// kcp 发送方向输出
/// </summary>
/// <param name="buffer">kcp 交出发送缓冲区控制权,缓冲区来自<see cref="RentBuffer(int)"/></param>
/// <param name="avalidLength">数据的有效长度</param>
/// <returns>不需要返回值</returns>
/// <remarks>通过增加 avalidLength 能够在协议栈中有效的减少数据拷贝</remarks>
void Output(BufferOwner buffer, int avalidLength);
}
/// <summary>
/// Kcp回调
/// </summary>
/// <remarks>
/// 失败设计,<see cref="KcpOutputWriter.Output(BufferOwner, int)"/>。IMemoryOwner是没有办法代替的。
/// 这里只相当于把 IKcpCallback 和 IRentable 和并。
/// </remarks>
public interface IKcpOutputWriter : IBufferWriter<byte>
{
int UnflushedBytes { get; }
void Flush();
}
/// <summary>
/// 外部提供缓冲区,可以在外部链接一个内存池
/// </summary>
public interface IRentable
{
/// <summary>
/// 外部提供缓冲区,可以在外部链接一个内存池
/// </summary>
BufferOwner RentBuffer(int length);
}
public interface IKcpSetting
{
int Interval(int interval);
/// <summary>
/// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
/// </summary>
/// <param name="nodelay">0:disable(default), 1:enable</param>
/// <param name="interval">internal update timer interval in millisec, default is 100ms</param>
/// <param name="resend">0:disable fast resend(default), 1:enable fast resend</param>
/// <param name="nc">0:normal congestion control(default), 1:disable congestion control</param>
/// <returns></returns>
int NoDelay(int nodelay, int interval, int resend, int nc);
/// <summary>
/// change MTU size, default is 1400
/// <para>** 这个方法不是线程安全的。请在没有发送和接收时调用 。</para>
/// </summary>
/// <param name="mtu"></param>
/// <returns></returns>
/// <remarks>
/// 如果没有必要不要修改Mtu。过小的Mtu会导致分片数大于接收窗口造成kcp阻塞冻结。
/// </remarks>
int SetMtu(int mtu = 1400);
/// <summary>
/// set maximum window size: sndwnd=32, rcvwnd=128 by default
/// </summary>
/// <param name="sndwnd"></param>
/// <param name="rcvwnd"></param>
/// <returns></returns>
/// <remarks>
/// 如果没有必要请不要修改。注意确保接收窗口必须大于最大分片数。
/// </remarks>
int WndSize(int sndwnd = 32, int rcvwnd = 128);
}
public interface IKcpUpdate
{
void Update(in DateTimeOffset time);
}
public interface IKcpSendable
{
/// <summary>
/// 将要发送到网络的数据Send到kcp协议中
/// </summary>
/// <param name="span"></param>
/// <param name="options"></param>
int Send(ReadOnlySpan<byte> span, object options = null);
/// <summary>
/// 将要发送到网络的数据Send到kcp协议中
/// </summary>
/// <param name="span"></param>
/// <param name="options"></param>
int Send(ReadOnlySequence<byte> span, object options = null);
}
public interface IKcpInputable
{
/// <summary>
/// 下层收到数据后添加到kcp协议中
/// </summary>
/// <param name="span"></param>
int Input(ReadOnlySpan<byte> span);
/// <summary>
/// 下层收到数据后添加到kcp协议中
/// </summary>
/// <param name="span"></param>
int Input(ReadOnlySequence<byte> span);
}
/// <summary>
/// kcp协议输入输出标准接口
/// </summary>
public interface IKcpIO : IKcpSendable, IKcpInputable
{
/// <summary>
/// 从kcp中取出一个整合完毕的数据包
/// </summary>
/// <returns></returns>
ValueTask RecvAsync(IBufferWriter<byte> writer, object options = null);
/// <summary>
/// 从kcp中取出一个整合完毕的数据包
/// </summary>
/// <param name="buffer"></param>
/// <param name="options"></param>
/// <returns>接收数据长度</returns>
ValueTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null);
/// <summary>
/// 从kcp协议中取出需要发送到网络的数据。
/// </summary>
/// <param name="writer"></param>
/// <param name="options"></param>
/// <returns></returns>
ValueTask OutputAsync(IBufferWriter<byte> writer, object options = null);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0cd14cc62244cdb479f360f6235c872e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,88 @@
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// Kcp报头
/// https://zhuanlan.zhihu.com/p/559191428
/// </summary>
public interface IKcpHeader
{
/// <summary>
/// 会话编号,两方一致才会通信
/// </summary>
uint conv { get; set; }
/// <summary>
/// 指令类型
/// </summary>
/// <remarks>
/// <para/> IKCP_CMD_PUSH = 81 // cmd: push data 数据报文
/// <para/> IKCP_CMD_ACK = 82 // cmd: ack 确认报文
/// <para/> IKCP_CMD_WASK = 83 // cmd: window probe (ask) 窗口探测报文,询问对端剩余接收窗口的大小.
/// <para/> IKCP_CMD_WINS = 84 // cmd: window size (tell) 窗口通知报文,通知对端剩余接收窗口的大小.
/// </remarks>
byte cmd { get; set; }
/// <summary>
/// 剩余分片数量,表示随后还有多少个报文属于同一个包。
/// </summary>
byte frg { get; set; }
/// <summary>
/// 自己可用窗口大小
/// </summary>
ushort wnd { get; set; }
/// <summary>
/// 发送时的时间戳 <seealso cref="DateTimeOffset.ToUnixTimeMilliseconds"/>
/// </summary>
uint ts { get; set; }
/// <summary>
/// 编号 确认编号或者报文编号
/// </summary>
uint sn { get; set; }
/// <summary>
/// 代表编号前面的所有报都收到了的标志
/// </summary>
uint una { get; set; }
/// <summary>
/// 数据内容长度
/// </summary>
uint len { get; }
}
public interface IKcpSegment : IKcpHeader
{
/// <summary>
/// 重传的时间戳。超过当前时间重发这个包
/// </summary>
uint resendts { get; set; }
/// <summary>
/// 超时重传时间,根据网络去定
/// </summary>
uint rto { get; set; }
/// <summary>
/// 快速重传机制,记录被跳过的次数,超过次数进行快速重传
/// </summary>
uint fastack { get; set; }
/// <summary>
/// 重传次数
/// </summary>
uint xmit { get; set; }
/// <summary>
/// 数据内容
/// </summary>
Span<byte> data { get; }
/// <summary>
/// 将IKcpSegment编码成字节数组并返回总长度包括Kcp报头
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
int Encode(Span<byte> buffer);
}
public interface ISegmentManager<Segment> where Segment : IKcpSegment
{
Segment Alloc(int appendDateSize);
void Free(Segment seg);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6eb068461678c1c4b887bc464e6b610b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,387 @@
using System.Buffers;
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
namespace System.Net.Sockets.Kcp
{
public class Kcp<Segment> : KcpCore<Segment>
where Segment : IKcpSegment
{
/// <summary>
/// create a new kcp control object, 'conv' must equal in two endpoint
/// from the same connection.
/// </summary>
/// <param name="conv_"></param>
/// <param name="callback"></param>
/// <param name="rentable">可租用内存的回调</param>
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
: base(conv_)
{
callbackHandle = callback;
this.rentable = rentable;
}
//extension 重构和新增加的部分============================================
IRentable rentable;
/// <summary>
/// 如果外部能够提供缓冲区则使用外部缓冲区否则new byte[]
/// </summary>
/// <param name="needSize"></param>
/// <returns></returns>
internal protected override BufferOwner CreateBuffer(int needSize)
{
var res = rentable?.RentBuffer(needSize);
if (res == null)
{
return base.CreateBuffer(needSize);
}
else
{
if (res.Memory.Length < needSize)
{
throw new ArgumentException($"{nameof(rentable.RentBuffer)} 指定的委托不符合标准,返回的" +
$"BufferOwner.Memory.Length 小于 {nameof(needSize)}");
}
}
return res;
}
/// <summary>
/// TryRecv Recv设计上同一时刻只允许一个线程调用。
/// <para/>因为要保证数据顺序多个线程同时调用Recv也没有意义。
/// <para/>所以只需要部分加锁即可。
/// </summary>
/// <returns></returns>
public (BufferOwner buffer, int avalidLength) TryRecv()
{
var peekSize = -1;
lock (rcv_queueLock)
{
if (rcv_queue.Count == 0)
{
///没有可用包
return (null, -1);
}
var seq = rcv_queue[0];
if (seq.frg == 0)
{
peekSize = (int)seq.len;
}
if (rcv_queue.Count < seq.frg + 1)
{
///没有足够的包
return (null, -1);
}
uint length = 0;
foreach (var item in rcv_queue)
{
length += item.len;
if (item.frg == 0)
{
break;
}
}
peekSize = (int)length;
if (peekSize < 0)
{
return (null, -2);
}
}
var buffer = CreateBuffer(peekSize);
var recvlength = UncheckRecv(buffer.Memory.Span);
return (buffer, recvlength);
}
/// <summary>
/// TryRecv Recv设计上同一时刻只允许一个线程调用。
/// <para/>因为要保证数据顺序多个线程同时调用Recv也没有意义。
/// <para/>所以只需要部分加锁即可。
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
public int TryRecv(IBufferWriter<byte> writer)
{
var peekSize = -1;
lock (rcv_queueLock)
{
if (rcv_queue.Count == 0)
{
///没有可用包
return -1;
}
var seq = rcv_queue[0];
if (seq.frg == 0)
{
peekSize = (int)seq.len;
}
if (rcv_queue.Count < seq.frg + 1)
{
///没有足够的包
return -1;
}
uint length = 0;
foreach (var item in rcv_queue)
{
length += item.len;
if (item.frg == 0)
{
break;
}
}
peekSize = (int)length;
if (peekSize < 0)
{
return -2;
}
}
return UncheckRecv(writer);
}
/// <summary>
/// user/upper level recv: returns size, returns below zero for EAGAIN
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public int Recv(Span<byte> buffer)
{
if (0 == rcv_queue.Count)
{
return -1;
}
var peekSize = PeekSize();
if (peekSize < 0)
{
return -2;
}
if (peekSize > buffer.Length)
{
return -3;
}
/// 拆分函数
var recvLength = UncheckRecv(buffer);
return recvLength;
}
/// <summary>
/// user/upper level recv: returns size, returns below zero for EAGAIN
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
public int Recv(IBufferWriter<byte> writer)
{
if (0 == rcv_queue.Count)
{
return -1;
}
var peekSize = PeekSize();
if (peekSize < 0)
{
return -2;
}
//if (peekSize > buffer.Length)
//{
// return -3;
//}
/// 拆分函数
var recvLength = UncheckRecv(writer);
return recvLength;
}
/// <summary>
/// 这个函数不检查任何参数
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
int UncheckRecv(Span<byte> buffer)
{
var recover = false;
if (rcv_queue.Count >= rcv_wnd)
{
recover = true;
}
#region merge fragment.
/// merge fragment.
var recvLength = 0;
lock (rcv_queueLock)
{
var count = 0;
foreach (var seg in rcv_queue)
{
seg.data.CopyTo(buffer.Slice(recvLength));
recvLength += (int)seg.len;
count++;
int frg = seg.frg;
SegmentManager.Free(seg);
if (frg == 0)
{
break;
}
}
if (count > 0)
{
rcv_queue.RemoveRange(0, count);
}
}
#endregion
Move_Rcv_buf_2_Rcv_queue();
#region fast recover
/// fast recover
if (rcv_queue.Count < rcv_wnd && recover)
{
// ready to send back IKCP_CMD_WINS in ikcp_flush
// tell remote my window size
probe |= IKCP_ASK_TELL;
}
#endregion
return recvLength;
}
/// <summary>
/// 这个函数不检查任何参数
/// </summary>
/// <param name="writer"></param>
/// <returns></returns>
int UncheckRecv(IBufferWriter<byte> writer)
{
var recover = false;
if (rcv_queue.Count >= rcv_wnd)
{
recover = true;
}
#region merge fragment.
/// merge fragment.
var recvLength = 0;
lock (rcv_queueLock)
{
var count = 0;
foreach (var seg in rcv_queue)
{
var len = (int)seg.len;
var destination = writer.GetSpan(len);
seg.data.CopyTo(destination);
writer.Advance(len);
recvLength += len;
count++;
int frg = seg.frg;
SegmentManager.Free(seg);
if (frg == 0)
{
break;
}
}
if (count > 0)
{
rcv_queue.RemoveRange(0, count);
}
}
#endregion
Move_Rcv_buf_2_Rcv_queue();
#region fast recover
/// fast recover
if (rcv_queue.Count < rcv_wnd && recover)
{
// ready to send back IKCP_CMD_WINS in ikcp_flush
// tell remote my window size
probe |= IKCP_ASK_TELL;
}
#endregion
return recvLength;
}
/// <summary>
/// check the size of next message in the recv queue
/// </summary>
/// <returns></returns>
public int PeekSize()
{
lock (rcv_queueLock)
{
if (rcv_queue.Count == 0)
{
///没有可用包
return -1;
}
var seq = rcv_queue[0];
if (seq.frg == 0)
{
return (int)seq.len;
}
if (rcv_queue.Count < seq.frg + 1)
{
///没有足够的包
return -1;
}
uint length = 0;
foreach (var seg in rcv_queue)
{
length += seg.len;
if (seg.frg == 0)
{
break;
}
}
return (int)length;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6141704235cf1dd46add0955e143139d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 37ba4f63dfc1a89498c047a2581af215
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,259 @@
using System.Buffers;
using System.Collections.Generic;
using System.Threading.Tasks;
using BufferOwner = System.Buffers.IMemoryOwner<byte>;
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// <inheritdoc cref="IPipe{T}"/>
/// <para></para>这是个简单的实现,更复杂使用微软官方实现<see cref="System.Threading.Channels.Channel.CreateBounded{T}(int)"/>
/// </summary>
/// <typeparam name="T"></typeparam>
internal class QueuePipe<T> : Queue<T>
{
readonly object _innerLock = new object();
private TaskCompletionSource<T> source;
//线程同步上下文由Task机制保证无需额外处理
//SynchronizationContext callbackContext;
//public bool UseSynchronizationContext { get; set; } = true;
public virtual void Write(T item)
{
lock (_innerLock)
{
if (source == null)
{
Enqueue(item);
}
else
{
if (Count > 0)
{
throw new Exception("内部顺序错误,不应该出现,请联系作者");
}
var next = source;
source = null;
next.TrySetResult(item);
}
}
}
public new void Enqueue(T item)
{
lock (_innerLock)
{
base.Enqueue(item);
}
}
public void Flush()
{
lock (_innerLock)
{
if (Count > 0)
{
var res = Dequeue();
var next = source;
source = null;
next?.TrySetResult(res);
}
}
}
public virtual Task<T> ReadAsync()
{
lock (_innerLock)
{
if (this.Count > 0)
{
var next = Dequeue();
return Task.FromResult(next);
}
else
{
source = new TaskCompletionSource<T>();
return source.Task;
}
}
}
public ValueTask<T> ReadValueTaskAsync()
{
throw new NotImplementedException();
}
}
public class KcpIO<Segment> : KcpCore<Segment>, IKcpIO
where Segment : IKcpSegment
{
OutputQ outq;
public KcpIO(uint conv_) : base(conv_)
{
outq = new OutputQ();
callbackHandle = outq;
}
internal override void Parse_data(Segment newseg)
{
base.Parse_data(newseg);
lock (rcv_queueLock)
{
var recover = false;
if (rcv_queue.Count >= rcv_wnd)
{
recover = true;
}
while (TryRecv(out var arraySegment) > 0)
{
recvSignal.Enqueue(arraySegment);
}
recvSignal.Flush();
#region fast recover
/// fast recover
if (rcv_queue.Count < rcv_wnd && recover)
{
// ready to send back IKCP_CMD_WINS in ikcp_flush
// tell remote my window size
probe |= IKCP_ASK_TELL;
}
#endregion
}
}
QueuePipe<ArraySegment<Segment>> recvSignal = new QueuePipe<ArraySegment<Segment>>();
internal int TryRecv(out ArraySegment<Segment> package)
{
package = default;
lock (rcv_queueLock)
{
var peekSize = -1;
if (rcv_queue.Count == 0)
{
///没有可用包
return -1;
}
var seq = rcv_queue[0];
if (seq.frg == 0)
{
peekSize = (int)seq.len;
}
if (rcv_queue.Count < seq.frg + 1)
{
///没有足够的包
return -1;
}
uint length = 0;
Segment[] kcpSegments = ArrayPool<Segment>.Shared.Rent(seq.frg + 1);
var index = 0;
foreach (var item in rcv_queue)
{
kcpSegments[index] = item;
index++;
length += item.len;
if (item.frg == 0)
{
break;
}
}
if (index > 0)
{
rcv_queue.RemoveRange(0, index);
}
package = new ArraySegment<Segment>(kcpSegments, 0, index);
peekSize = (int)length;
if (peekSize < 0)
{
return -2;
}
return peekSize;
}
}
public async ValueTask RecvAsync(IBufferWriter<byte> writer, object options = null)
{
var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false);
for (int i = arraySegment.Offset; i < arraySegment.Count; i++)
{
WriteRecv(writer, arraySegment.Array[i]);
}
ArrayPool<Segment>.Shared.Return(arraySegment.Array, true);
}
private void WriteRecv(IBufferWriter<byte> writer, Segment seg)
{
var curCount = (int)seg.len;
var target = writer.GetSpan(curCount);
seg.data.CopyTo(target);
SegmentManager.Free(seg);
writer.Advance(curCount);
}
public async ValueTask<int> RecvAsync(ArraySegment<byte> buffer, object options = null)
{
var arraySegment = await recvSignal.ReadAsync().ConfigureAwait(false);
int start = buffer.Offset;
for (int i = arraySegment.Offset; i < arraySegment.Count; i++)
{
var target = new Memory<byte>(buffer.Array, start, buffer.Array.Length - start);
var seg = arraySegment.Array[i];
seg.data.CopyTo(target.Span);
start += seg.data.Length;
SegmentManager.Free(seg);
}
ArrayPool<Segment>.Shared.Return(arraySegment.Array, true);
return start - buffer.Offset;
}
public async ValueTask OutputAsync(IBufferWriter<byte> writer, object options = null)
{
var (Owner, Count) = await outq.ReadAsync().ConfigureAwait(false);
WriteOut(writer, Owner, Count);
}
private static void WriteOut(IBufferWriter<byte> writer, BufferOwner Owner, int Count)
{
var target = writer.GetSpan(Count);
Owner.Memory.Span.Slice(0, Count).CopyTo(target);
writer.Advance(Count);
Owner.Dispose();
}
protected internal override BufferOwner CreateBuffer(int needSize)
{
return MemoryPool<byte>.Shared.Rent(needSize);
}
internal class OutputQ : QueuePipe<(BufferOwner Owner, int Count)>,
IKcpCallback
{
public void Output(BufferOwner buffer, int avalidLength)
{
Write((buffer, avalidLength));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09f39232ff420934487a7a3e03245ccf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
namespace System.Net.Sockets.Kcp
{
public abstract class KcpOutputWriter : IKcpOutputWriter
{
public int UnflushedBytes { get; set; }
public IMemoryOwner<byte> MemoryOwner { get; set; }
public void Flush()
{
Output(MemoryOwner, UnflushedBytes);
MemoryOwner = null;
UnflushedBytes = 0;
}
public void Advance(int count)
{
UnflushedBytes += count;
}
public Memory<byte> GetMemory(int sizeHint = 0)
{
if (MemoryOwner == null)
{
MemoryOwner = MemoryPool<byte>.Shared.Rent(2048);
}
return MemoryOwner.Memory.Slice(UnflushedBytes);
}
public Span<byte> GetSpan(int sizeHint = 0)
{
if (MemoryOwner == null)
{
MemoryOwner = MemoryPool<byte>.Shared.Rent(2048);
}
return MemoryOwner.Memory.Span.Slice(UnflushedBytes);
}
/// <summary>
/// Socket发送是要pin byte[],为了不阻塞KcpFlush动态缓存是必须的。
/// </summary>
/// <param name="buffer"></param>
/// <param name="avalidLength"></param>
public abstract void Output(IMemoryOwner<byte> buffer, int avalidLength);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c18538996fbeb34a97dec4012fc825b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,403 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// 调整了没存布局,直接拷贝块提升性能。
/// <para>结构体保存内容只有一个指针,不用担心参数传递过程中的性能</para>
/// https://github.com/skywind3000/kcp/issues/118#issuecomment-338133930
/// <para>不要对没有初始化的KcpSegment(内部指针为0所有属性都将指向位置区域) 进行任何赋值操作,可能导致内存损坏。
/// 出于性能考虑,没有对此项进行安全检查。</para>
/// </summary>
public struct KcpSegment : IKcpSegment
{
internal readonly unsafe byte* ptr;
public unsafe KcpSegment(byte* intPtr, uint appendDateSize)
{
this.ptr = intPtr;
len = appendDateSize;
}
/// <summary>
/// 使用完必须显示释放,否则内存泄漏
/// </summary>
/// <param name="appendDateSize"></param>
/// <returns></returns>
public static KcpSegment AllocHGlobal(int appendDateSize)
{
var total = LocalOffset + HeadOffset + appendDateSize;
IntPtr intPtr = Marshal.AllocHGlobal(total);
unsafe
{
///清零 不知道是不是有更快的清0方法
Span<byte> span = new Span<byte>(intPtr.ToPointer(), total);
span.Clear();
return new KcpSegment((byte*)intPtr.ToPointer(), (uint)appendDateSize);
}
}
/// <summary>
/// 释放非托管内存
/// </summary>
/// <param name="seg"></param>
public static void FreeHGlobal(KcpSegment seg)
{
unsafe
{
Marshal.FreeHGlobal((IntPtr)seg.ptr);
}
}
/// 以下为本机使用的参数
/// <summary>
/// offset = 0
/// </summary>
public uint resendts
{
get
{
unsafe
{
return *(uint*)(ptr + 0);
}
}
set
{
unsafe
{
*(uint*)(ptr + 0) = value;
}
}
}
/// <summary>
/// offset = 4
/// </summary>
public uint rto
{
get
{
unsafe
{
return *(uint*)(ptr + 4);
}
}
set
{
unsafe
{
*(uint*)(ptr + 4) = value;
}
}
}
/// <summary>
/// offset = 8
/// </summary>
public uint fastack
{
get
{
unsafe
{
return *(uint*)(ptr + 8);
}
}
set
{
unsafe
{
*(uint*)(ptr + 8) = value;
}
}
}
/// <summary>
/// offset = 12
/// </summary>
public uint xmit
{
get
{
unsafe
{
return *(uint*)(ptr + 12);
}
}
set
{
unsafe
{
*(uint*)(ptr + 12) = value;
}
}
}
///以下为需要网络传输的参数
public const int LocalOffset = 4 * 4;
public const int HeadOffset = KcpConst.IKCP_OVERHEAD;
/// <summary>
/// offset = <see cref="LocalOffset"/>
/// </summary>
/// https://github.com/skywind3000/kcp/issues/134
public uint conv
{
get
{
unsafe
{
return *(uint*)(LocalOffset + 0 + ptr);
}
}
set
{
unsafe
{
*(uint*)(LocalOffset + 0 + ptr) = value;
}
}
}
/// <summary>
/// offset = <see cref="LocalOffset"/> + 4
/// </summary>
public byte cmd
{
get
{
unsafe
{
return *(LocalOffset + 4 + ptr);
}
}
set
{
unsafe
{
*(LocalOffset + 4 + ptr) = value;
}
}
}
/// <summary>
/// offset = <see cref="LocalOffset"/> + 5
/// </summary>
public byte frg
{
get
{
unsafe
{
return *(LocalOffset + 5 + ptr);
}
}
set
{
unsafe
{
*(LocalOffset + 5 + ptr) = value;
}
}
}
/// <summary>
/// offset = <see cref="LocalOffset"/> + 6
/// </summary>
public ushort wnd
{
get
{
unsafe
{
return *(ushort*)(LocalOffset + 6 + ptr);
}
}
set
{
unsafe
{
*(ushort*)(LocalOffset + 6 + ptr) = value;
}
}
}
/// <summary>
/// offset = <see cref="LocalOffset"/> + 8
/// </summary>
public uint ts
{
get
{
unsafe
{
return *(uint*)(LocalOffset + 8 + ptr);
}
}
set
{
unsafe
{
*(uint*)(LocalOffset + 8 + ptr) = value;
}
}
}
/// <summary>
/// <para> SendNumber? </para>
/// offset = <see cref="LocalOffset"/> + 12
/// </summary>
public uint sn
{
get
{
unsafe
{
return *(uint*)(LocalOffset + 12 + ptr);
}
}
set
{
unsafe
{
*(uint*)(LocalOffset + 12 + ptr) = value;
}
}
}
/// <summary>
/// offset = <see cref="LocalOffset"/> + 16
/// </summary>
public uint una
{
get
{
unsafe
{
return *(uint*)(LocalOffset + 16 + ptr);
}
}
set
{
unsafe
{
*(uint*)(LocalOffset + 16 + ptr) = value;
}
}
}
/// <summary>
/// <para> AppendDateSize </para>
/// offset = <see cref="LocalOffset"/> + 20
/// </summary>
public uint len
{
get
{
unsafe
{
return *(uint*)(LocalOffset + 20 + ptr);
}
}
private set
{
unsafe
{
*(uint*)(LocalOffset + 20 + ptr) = value;
}
}
}
/// <summary>
///
/// </summary>
/// https://github.com/skywind3000/kcp/issues/35#issuecomment-263770736
public Span<byte> data
{
get
{
unsafe
{
return new Span<byte>(LocalOffset + HeadOffset + ptr, (int)len);
}
}
}
/// <summary>
/// 将片段中的要发送的数据拷贝到指定缓冲区
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public int Encode(Span<byte> buffer)
{
var datelen = (int)(HeadOffset + len);
///备用偏移值 现阶段没有使用
const int offset = 0;
if (KcpConst.IsLittleEndian)
{
if (BitConverter.IsLittleEndian)
{
///小端可以一次拷贝
unsafe
{
///要发送的数据从LocalOffset开始。
///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
sendDate.CopyTo(buffer);
}
}
else
{
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv);
buffer[offset + 4] = cmd;
buffer[offset + 5] = frg;
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len);
data.CopyTo(buffer.Slice(HeadOffset));
}
}
else
{
if (BitConverter.IsLittleEndian)
{
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv);
buffer[offset + 4] = cmd;
buffer[offset + 5] = frg;
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len);
data.CopyTo(buffer.Slice(HeadOffset));
}
else
{
///大端可以一次拷贝
unsafe
{
///要发送的数据从LocalOffset开始。
///本结构体调整了要发送字段和单机使用字段的位置,让报头数据和数据连续,节约一次拷贝。
Span<byte> sendDate = new Span<byte>(ptr + LocalOffset, datelen);
sendDate.CopyTo(buffer);
}
}
}
return datelen;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c83672e9da889e49a1a694e563e521f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace System.Net.Sockets.Kcp
{
public partial class KcpCore<Segment>
{
public KcpLogMask LogMask { get; set; } = KcpLogMask.IKCP_LOG_PARSE_DATA | KcpLogMask.IKCP_LOG_NEED_SEND | KcpLogMask.IKCP_LOG_DEAD_LINK;
public virtual bool CanLog(KcpLogMask mask)
{
if ((mask & LogMask) == 0)
{
return false;
}
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
if (TraceListener != null)
{
return true;
}
#endif
return false;
}
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
public System.Diagnostics.TraceListener TraceListener { get; set; }
#endif
public virtual void LogFail(string message)
{
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
TraceListener?.Fail(message);
#endif
}
public virtual void LogWriteLine(string message, string category)
{
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
TraceListener?.WriteLine(message, category);
#endif
}
[Obsolete("一定要先判断CanLog 内部判断是否存在TraceListener,避免在没有TraceListener时生成字符串", true)]
public virtual void LogWriteLine(string message, KcpLogMask mask)
{
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
if (CanLog(mask))
{
LogWriteLine(message, mask.ToString());
}
#endif
}
}
[Flags]
public enum KcpLogMask
{
IKCP_LOG_OUTPUT = 1 << 0,
IKCP_LOG_INPUT = 1 << 1,
IKCP_LOG_SEND = 1 << 2,
IKCP_LOG_RECV = 1 << 3,
IKCP_LOG_IN_DATA = 1 << 4,
IKCP_LOG_IN_ACK = 1 << 5,
IKCP_LOG_IN_PROBE = 1 << 6,
IKCP_LOG_IN_WINS = 1 << 7,
IKCP_LOG_OUT_DATA = 1 << 8,
IKCP_LOG_OUT_ACK = 1 << 9,
IKCP_LOG_OUT_PROBE = 1 << 10,
IKCP_LOG_OUT_WINS = 1 << 11,
IKCP_LOG_PARSE_DATA = 1 << 12,
IKCP_LOG_NEED_SEND = 1 << 13,
IKCP_LOG_DEAD_LINK = 1 << 14,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1e41e98d9d5dc2744801b250e4de0f5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,268 @@
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Buffers.Binary;
namespace System.Net.Sockets.Kcp
{
/// <summary>
/// 动态申请非托管内存
/// </summary>
public class SimpleSegManager : ISegmentManager<KcpSegment>
{
public static SimpleSegManager Default { get; } = new SimpleSegManager();
public KcpSegment Alloc(int appendDateSize)
{
return KcpSegment.AllocHGlobal(appendDateSize);
}
public void Free(KcpSegment seg)
{
KcpSegment.FreeHGlobal(seg);
}
public class Kcp : Kcp<KcpSegment>
{
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
: base(conv_, callback, rentable)
{
SegmentManager = Default;
}
}
public class KcpIO : KcpIO<KcpSegment>
{
public KcpIO(uint conv_)
: base(conv_)
{
SegmentManager = Default;
}
}
}
/// <summary>
/// 申请固定大小非托管内存。使用这个就不能SetMtu了大小已经写死。
/// </summary>
/// <remarks>需要大量测试</remarks>
public unsafe class UnSafeSegManager : ISegmentManager<KcpSegment>
{
public static UnSafeSegManager Default { get; } = new UnSafeSegManager();
/// <summary>
/// 因为默认mtu是1400并且内存需要内存行/内存页对齐。这里直接512对齐。
/// </summary>
public const int blockSize = 512 * 3;
public HashSet<IntPtr> header = new HashSet<IntPtr>();
public Stack<IntPtr> blocks = new Stack<IntPtr>();
public readonly object locker = new object();
public UnSafeSegManager()
{
Alloc();
}
void Alloc()
{
int count = 50;
IntPtr intPtr = Marshal.AllocHGlobal(blockSize * count);
header.Add(intPtr);
for (int i = 0; i < count; i++)
{
blocks.Push(intPtr + blockSize * i);
}
}
~UnSafeSegManager()
{
foreach (var item in header)
{
Marshal.FreeHGlobal(item);
}
}
public KcpSegment Alloc(int appendDateSize)
{
lock (locker)
{
var total = KcpSegment.LocalOffset + KcpSegment.HeadOffset + appendDateSize;
if (total > blockSize)
{
throw new ArgumentOutOfRangeException();
}
if (blocks.Count > 0)
{
}
else
{
Alloc();
}
var ptr = blocks.Pop();
Span<byte> span = new Span<byte>(ptr.ToPointer(), blockSize);
span.Clear();
return new KcpSegment((byte*)ptr.ToPointer(), (uint)appendDateSize);
}
}
public void Free(KcpSegment seg)
{
lock (locker)
{
IntPtr ptr = (IntPtr)seg.ptr;
blocks.Push(ptr);
}
}
public class Kcp : Kcp<KcpSegment>
{
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
: base(conv_, callback, rentable)
{
SegmentManager = Default;
}
}
public class KcpIO : KcpIO<KcpSegment>
{
public KcpIO(uint conv_)
: base(conv_)
{
SegmentManager = Default;
}
}
}
/// <summary>
/// 使用内存池而不是非托管内存有内存alloc但是不多。可以解决Marshal.AllocHGlobal 内核调用带来的性能问题
/// </summary>
public class PoolSegManager : ISegmentManager<PoolSegManager.Seg>
{
public static PoolSegManager Default { get; } = new PoolSegManager();
/// <summary>
/// 因为默认mtu是1400并且内存需要内存行/内存页对齐。这里直接512对齐。
/// </summary>
public const int blockSize = 512 * 3;
public class Seg : IKcpSegment
{
byte[] cache;
public Seg(int blockSize)
{
cache = Buffers.ArrayPool<byte>.Shared.Rent(blockSize);
}
///以下为需要网络传输的参数
public const int LocalOffset = 4 * 4;
public const int HeadOffset = Kcp.IKCP_OVERHEAD;
public byte cmd { get; set; }
public uint conv { get; set; }
public Span<byte> data => cache.AsSpan().Slice(0, (int)len);
public uint fastack { get; set; }
public byte frg { get; set; }
public uint len { get; internal set; }
public uint resendts { get; set; }
public uint rto { get; set; }
public uint sn { get; set; }
public uint ts { get; set; }
public uint una { get; set; }
public ushort wnd { get; set; }
public uint xmit { get; set; }
public int Encode(Span<byte> buffer)
{
var datelen = (int)(HeadOffset + len);
///备用偏移值 现阶段没有使用
const int offset = 0;
if (BitConverter.IsLittleEndian)
{
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset), conv);
buffer[offset + 4] = cmd;
buffer[offset + 5] = frg;
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(offset + 6), wnd);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 8), ts);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 12), sn);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 16), una);
BinaryPrimitives.WriteUInt32LittleEndian(buffer.Slice(offset + 20), len);
data.CopyTo(buffer.Slice(HeadOffset));
}
else
{
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset), conv);
buffer[offset + 4] = cmd;
buffer[offset + 5] = frg;
BinaryPrimitives.WriteUInt16BigEndian(buffer.Slice(offset + 6), wnd);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 8), ts);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 12), sn);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 16), una);
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(offset + 20), len);
data.CopyTo(buffer.Slice(HeadOffset));
}
return datelen;
}
}
ConcurrentStack<Seg> Pool = new ConcurrentStack<Seg>();
public Seg Alloc(int appendDateSize)
{
if (appendDateSize > blockSize)
{
throw new NotSupportedException();
}
if (Pool.TryPop(out var ret))
{
}
else
{
ret = new Seg(blockSize);
}
ret.len = (uint)appendDateSize;
return ret;
}
public void Free(Seg seg)
{
seg.cmd = 0;
seg.conv = 0;
seg.fastack = 0;
seg.frg = 0;
seg.len = 0;
seg.resendts = 0;
seg.rto = 0;
seg.sn = 0;
seg.ts = 0;
seg.una = 0;
seg.wnd = 0;
seg.xmit = 0;
Pool.Push(seg);
}
public class Kcp : Kcp<Seg>
{
public Kcp(uint conv_, IKcpCallback callback, IRentable rentable = null)
: base(conv_, callback, rentable)
{
SegmentManager = Default;
}
}
public class KcpIO : KcpIO<Seg>
{
public KcpIO(uint conv_)
: base(conv_)
{
SegmentManager = Default;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9b45a212fd67da4428ad2edff357b0ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
namespace System.Net.Sockets.Kcp.Simple
{
/// <summary>
/// 简单例子
/// </summary>
public class SimpleKcpClient : IKcpCallback
{
UdpClient client;
public SimpleKcpClient(int port)
: this(port, null)
{
}
public SimpleKcpClient(int port, IPEndPoint endPoint)
{
client = new UdpClient(port);
kcp = new SimpleSegManager.Kcp(2001, this);
this.EndPoint = endPoint;
BeginRecv();
}
public SimpleSegManager.Kcp kcp { get; }
public IPEndPoint EndPoint { get; set; }
public void Output(IMemoryOwner<byte> buffer, int avalidLength)
{
var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray();
client.SendAsync(s, s.Length, EndPoint);
buffer.Dispose();
}
public async void SendAsync(byte[] datagram, int bytes)
{
kcp.Send(datagram.AsSpan().Slice(0, bytes));
}
public async ValueTask<byte[]> ReceiveAsync()
{
var (buffer, avalidLength) = kcp.TryRecv();
while (buffer == null)
{
await Task.Delay(10);
(buffer, avalidLength) = kcp.TryRecv();
}
var s = buffer.Memory.Span.Slice(0, avalidLength).ToArray();
return s;
}
private async void BeginRecv()
{
var res = await client.ReceiveAsync();
EndPoint = res.RemoteEndPoint;
kcp.Input(res.Buffer);
BeginRecv();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c4bfbc85fb2f094d8489ccae851b7ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
//[assembly: InternalsVisibleTo("UnitTestProject1")]
namespace System.Net.Sockets.Kcp
{
public static class KcpExtension_FDF71D0BC31D49C48EEA8FAA51F017D4
{
private static readonly DateTime utc_time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
[Obsolete("", true)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertTime(this in DateTime time)
{
return (uint)(Convert.ToInt64(time.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
}
private static readonly DateTimeOffset utc1970 = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertTimeOld(this in DateTimeOffset time)
{
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertTime2(this in DateTimeOffset time)
{
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
return (uint)(time.ToUnixTimeMilliseconds() & 0xffffffff);
#else
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ConvertTime(this in DateTimeOffset time)
{
#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER
return (uint)(time.ToUnixTimeMilliseconds());
#else
return (uint)(Convert.ToInt64(time.Subtract(utc1970).TotalMilliseconds) & 0xffffffff);
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToLogString<T>(this T segment, bool local = false)
where T : IKcpSegment
{
if (local)
{
return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd} [ LocalValue: xmit:{segment.xmit} fastack:{segment.fastack} rto:{segment.rto} ]";
}
else
{
return $"sn:{segment.sn,2} una:{segment.una,2} frg:{segment.frg,2} cmd:{segment.cmd,2} len:{segment.len,2} wnd:{segment.wnd}";
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Encode<T>(this T Seg, IBufferWriter<byte> writer)
where T : IKcpSegment
{
var totalLength = (int)(KcpSegment.HeadOffset + Seg.len);
var span = writer.GetSpan(totalLength);
Seg.Encode(span);
writer.Advance(totalLength);
return totalLength;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 830bbaa3d0d50b84595dbfe899c5471a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: