Skip to content

Commit

Permalink
KCP LowLevel
Browse files Browse the repository at this point in the history
  • Loading branch information
neuecc committed Apr 23, 2024
1 parent 7cb75b2 commit 58475e1
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 57 deletions.
125 changes: 125 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,131 @@ public sealed record class KcpClientConnectionOptions : KcpOptions
}
```

LowLevel API
---
The KcpTransport provides a low-level API that directly uses the methods from ikcp.c and defined in ikcp.h.

```c
ikcpcb* ikcp_create(IUINT32 conv, void *user);
void ikcp_release(ikcpcb *kcp);
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len, ikcpcb *kcp, void *user));
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
void ikcp_update(ikcpcb *kcp, IUINT32 current);
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
int ikcp_input(ikcpcb *kcp, const char *data, long size);
void ikcp_flush(ikcpcb *kcp);
int ikcp_peeksize(const ikcpcb *kcp);
int ikcp_setmtu(ikcpcb *kcp, int mtu);
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
int ikcp_waitsnd(const ikcpcb *kcp);
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
IUINT32 ikcp_getconv(const void *ptr);
```
By using static `KcpTransport.LowLevel.KcpMethods`, you can use the `ikcp_***` functions.
```csharp
using KcpTransport;
using KcpTransport.LowLevel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using static KcpTransport.LowLevel.KcpMethods; // use ikcp methods
public class SampleLowLevel : IDisposable
{
GCHandle user;
unsafe IKCPCB* kcp;
bool isDisposed;
readonly long startingTimestamp;
// void* is user, you can cast by GCHandle.FromIntPtr((IntPtr)ptr).Target
public unsafe SampleLowLevel(delegate* managed<byte*, int, IKCPCB*, void*, int> output, object user)
{
this.user = GCHandle.Alloc(this);
this.kcp = ikcp_create(conv: 0, user: (void*)GCHandle.ToIntPtr(this.user));
ikcp_setoutput(kcp, output);
this.startingTimestamp = Stopwatch.GetTimestamp();
Update();
}
public unsafe int Send(ReadOnlySpan<byte> data)
{
fixed (byte* ptr = data)
{
return ikcp_send(kcp, ptr, data.Length);
}
}
public unsafe int InputData(ReadOnlySpan<byte> data)
{
fixed (byte* ptr = data)
{
return ikcp_input(kcp, ptr, data.Length);
}
}
public unsafe int PeekSize()
{
return ikcp_peeksize(kcp);
}
public unsafe int ReceiveData(Span<byte> buffer)
{
fixed (byte* ptr = buffer)
{
return ikcp_recv(kcp, ptr, buffer.Length);
}
}
public unsafe void Update()
{
var elapsed = Stopwatch.GetElapsedTime(startingTimestamp);
var currentTimestampMilliseconds = (uint)elapsed.TotalMilliseconds;
ikcp_update(kcp, currentTimestampMilliseconds);
}
public unsafe void Flush()
{
ikcp_flush(kcp);
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
if (disposing)
{
// cleanup managed.
}
// cleanup unmanaged.
unsafe
{
user.Free();
user = default;
ikcp_release(kcp);
kcp = null;
}
isDisposed = true;
}
}
~SampleLowLevel()
{
Dispose(disposing: false);
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
```

License
---
This library is under the MIT License.
8 changes: 1 addition & 7 deletions sandbox/ConsoleApp1/KcpSandbox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static async Task KcpEchoClient(int id)

var buffer = new byte[1024];
var stream = await connection.OpenOutboundStreamAsync();
//while (true)
while (true)
{
// var inputText = Console.ReadLine();
var inputText = id + ":" + Random.Shared.Next().ToString();
Expand All @@ -76,12 +76,6 @@ public static async Task KcpEchoClient(int id)
Console.WriteLine("NG");
throw new Exception("Invalid Data Received");
}

//connection.Disconnect();
// await Task.Delay(1000);
connection.Dispose();

Console.Read();
}
}
}
Expand Down
96 changes: 96 additions & 0 deletions sandbox/ReadMeSample/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using KcpTransport;
using KcpTransport.LowLevel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using static KcpTransport.LowLevel.KcpMethods;

var server = RunEchoServer();
var client = RunEchoClient();
Expand Down Expand Up @@ -73,3 +77,95 @@ static async Task RunEchoClient()
Console.WriteLine($"Client Response Received: " + str);
}
}

public class SampleLowLevel : IDisposable
{
GCHandle user;
unsafe IKCPCB* kcp;
bool isDisposed;
readonly long startingTimestamp;

// void* is user, you can cast by GCHandle.FromIntPtr((IntPtr)ptr).Target
public unsafe SampleLowLevel(delegate* managed<byte*, int, IKCPCB*, void*, int> output, object user)
{
this.user = GCHandle.Alloc(this);
this.kcp = ikcp_create(conv: 0, user: (void*)GCHandle.ToIntPtr(this.user));
ikcp_setoutput(kcp, output);
this.startingTimestamp = Stopwatch.GetTimestamp();
Update();
}

public unsafe int Send(ReadOnlySpan<byte> data)
{
fixed (byte* ptr = data)
{
return ikcp_send(kcp, ptr, data.Length);
}
}

public unsafe int InputData(ReadOnlySpan<byte> data)
{
fixed (byte* ptr = data)
{
return ikcp_input(kcp, ptr, data.Length);
}
}

public unsafe int PeekSize()
{
return ikcp_peeksize(kcp);
}

public unsafe int ReceiveData(Span<byte> buffer)
{
fixed (byte* ptr = buffer)
{
return ikcp_recv(kcp, ptr, buffer.Length);
}
}

public unsafe void Update()
{
var elapsed = Stopwatch.GetElapsedTime(startingTimestamp);
var currentTimestampMilliseconds = (uint)elapsed.TotalMilliseconds;
ikcp_update(kcp, currentTimestampMilliseconds);
}

public unsafe void Flush()
{
ikcp_flush(kcp);
}

protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
if (disposing)
{
// cleanup managed.
}

// cleanup unmanaged.
unsafe
{
user.Free();
user = default;
ikcp_release(kcp);
kcp = null;
}

isDisposed = true;
}
}

~SampleLowLevel()
{
Dispose(disposing: false);
}

public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
1 change: 1 addition & 0 deletions sandbox/ReadMeSample/ReadMeSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
Expand Down
16 changes: 9 additions & 7 deletions src/KcpTransport/KcpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ unsafe KcpConnection(Socket socket, uint conversationId, KcpClientConnectionOpti
{
this.conversationId = conversationId;
this.keepAliveDelay = options.KeepAliveDelay;
this.kcp = ikcp_create(conversationId);
this.kcp = ikcp_create(conversationId, GCHandle.ToIntPtr(GCHandle.Alloc(this)).ToPointer());
this.kcp->output = &KcpOutputCallback;
ConfigKcpWorkMode(options.EnableNoDelay, options.IntervalMilliseconds, options.Resend, options.EnableFlowControl);
ConfigKcpWindowSize(options.WindowSize.SendWindow, options.WindowSize.ReceiveWindow);
Expand All @@ -84,7 +84,7 @@ internal unsafe KcpConnection(uint conversationId, KcpListenerOptions options, S
{
this.conversationId = conversationId;
this.keepAliveDelay = options.KeepAliveDelay;
this.kcp = ikcp_create(conversationId);
this.kcp = ikcp_create(conversationId, GCHandle.ToIntPtr(GCHandle.Alloc(this)).ToPointer());
this.kcp->output = &KcpOutputCallback;
ConfigKcpWorkMode(options.EnableNoDelay, options.IntervalMilliseconds, options.Resend, options.EnableFlowControl);
ConfigKcpWindowSize(options.WindowSize.SendWindow, options.WindowSize.ReceiveWindow);
Expand Down Expand Up @@ -365,19 +365,19 @@ internal unsafe void KcpFlush()
{
if (isDisposed) return;

ikcp_flush(kcp, this);
ikcp_flush(kcp);
}
}

internal unsafe void UpdateKcp()
{
var elapsed = Stopwatch.GetElapsedTime(startingTimestamp);
var currentTimestampMillisec = (uint)elapsed.TotalMilliseconds;
var currentTimestampMilliseconds = (uint)elapsed.TotalMilliseconds;
lock (gate)
{
if (isDisposed) return;

ikcp_update(kcp, currentTimestampMillisec, this);
ikcp_update(kcp, currentTimestampMilliseconds);
}
}

Expand Down Expand Up @@ -547,6 +547,7 @@ unsafe void Dispose(bool disposing)
connectionCancellationTokenSource.Cancel();
connectionCancellationTokenSource.Dispose();

GCHandle.FromIntPtr((nint)kcp->user).Free();
ikcp_release(kcp);
kcp = null;

Expand All @@ -558,6 +559,7 @@ unsafe void Dispose(bool disposing)
else
{
// only cleanup unmanaged resource
GCHandle.FromIntPtr((nint)kcp->user).Free();
ikcp_release(kcp);
}
}
Expand All @@ -569,9 +571,9 @@ unsafe void Dispose(bool disposing)
}


static unsafe int KcpOutputCallback(byte* buf, int len, IKCPCB* kcp, object user)
static unsafe int KcpOutputCallback(byte* buf, int len, IKCPCB* kcp, void* user)
{
var self = (KcpConnection)user;
var self = (KcpConnection)GCHandle.FromIntPtr((IntPtr)user).Target!;
var buffer = new Span<byte>(buf, len);

var sent = self.socket.Send(buffer);
Expand Down
1 change: 1 addition & 0 deletions src/KcpTransport/KcpListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ async Task StartSocketEventLoopAsync(KcpListenerOptions options, int id)
catch (Exception ex)
{
// TODO: log?
_ = ex;
}
}

Expand Down
Loading

0 comments on commit 58475e1

Please sign in to comment.