fix: kcp2k V1.13

- uncorks max message size from 144 KB to as much as we want based on
  receive window size.
    fixes https://github.com/vis2k/kcp2k/issues/22
    fixes https://github.com/skywind3000/kcp/pull/291
- feature: OnData now includes channel it was received on
- fixes #2989
This commit is contained in:
vis2k 2021-11-28 16:53:03 +01:00
parent 52111d1cb8
commit 0a1bfade25
8 changed files with 59 additions and 57 deletions

View File

@ -50,6 +50,13 @@ public class KcpTransport : Transport
// log statistics for headless servers that can't show them in GUI
public bool statisticsLog;
// translate Kcp <-> Mirror channels
static int KcpToMirrorChannel(KcpChannel channel) =>
channel == KcpChannel.Reliable ? Channels.Reliable : Channels.Unreliable;
static KcpChannel MirrorToKcpChannel(int channel) =>
channel == Channels.Reliable ? KcpChannel.Reliable : KcpChannel.Unreliable;
void Awake()
{
// logging
@ -66,18 +73,18 @@ void Awake()
client = NonAlloc
? new KcpClientNonAlloc(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
(message, channel) => OnClientDataReceived.Invoke(message, KcpToMirrorChannel(channel)),
() => OnClientDisconnected.Invoke())
: new KcpClient(
() => OnClientConnected.Invoke(),
(message) => OnClientDataReceived.Invoke(message, Channels.Reliable),
(message, channel) => OnClientDataReceived.Invoke(message, KcpToMirrorChannel(channel)),
() => OnClientDisconnected.Invoke());
// server
server = NonAlloc
? new KcpServerNonAlloc(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, KcpToMirrorChannel(channel)),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
@ -89,7 +96,7 @@ void Awake()
Timeout)
: new KcpServer(
(connectionId) => OnServerConnected.Invoke(connectionId),
(connectionId, message) => OnServerDataReceived.Invoke(connectionId, message, Channels.Reliable),
(connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, KcpToMirrorChannel(channel)),
(connectionId) => OnServerDisconnected.Invoke(connectionId),
DualMode,
NoDelay,
@ -126,18 +133,7 @@ public override void ClientConnect(Uri uri)
}
public override void ClientSend(ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
client.Send(segment, KcpChannel.Unreliable);
break;
default:
client.Send(segment, KcpChannel.Reliable);
break;
}
client.Send(segment, MirrorToKcpChannel(channelId));
}
public override void ClientDisconnect() => client.Disconnect();
// process incoming in early update
@ -185,18 +181,7 @@ public override Uri ServerUri()
public override void ServerStart() => server.Start(Port);
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
{
// switch to kcp channel.
// unreliable or reliable.
// default to reliable just to be sure.
switch (channelId)
{
case Channels.Unreliable:
server.Send(connectionId, segment, KcpChannel.Unreliable);
break;
default:
server.Send(connectionId, segment, KcpChannel.Reliable);
break;
}
server.Send(connectionId, segment, MirrorToKcpChannel(channelId));
}
public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId);
public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
@ -227,7 +212,7 @@ public override int GetMaxPacketSize(int channelId = Channels.Reliable)
case Channels.Unreliable:
return KcpConnection.UnreliableMaxMessageSize;
default:
return KcpConnection.ReliableMaxMessageSize;
return KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize);
}
}

View File

@ -1,10 +1,19 @@
V1.13 [2021-11-28]
- fix: perf: uncork max message size from 144 KB to as much as we want based on
receive window size.
fixes https://github.com/vis2k/kcp2k/issues/22
fixes https://github.com/skywind3000/kcp/pull/291
- feature: OnData now includes channel it was received on
V1.12 [2021-07-16]
- where-allocation removed. will be optional in the future.
- Tests: don't depend on Unity anymore
- fix: #26 - Kcp now catches exception if host couldn't be resolved, and calls
OnDisconnected to let the user now.
- fix: KcpServer.DualMode is now configurable in the constructor instead of
using #if UNITY_SWITCH. makes it run on all other non dual mode platforms too.
- fix: where-allocation made optional via virtuals and inheriting
KcpServer/Client/Connection NonAlloc classes. fixes a bug where some platforms
might not support where-allocation.
V1.11 rollback [2021-06-01]
- perf: Segment MemoryStream initial capacity set to MTU to avoid early runtime

View File

@ -8,14 +8,14 @@ public class KcpClient
{
// events
public Action OnConnected;
public Action<ArraySegment<byte>> OnData;
public Action<ArraySegment<byte>, KcpChannel> OnData;
public Action OnDisconnected;
// state
public KcpClientConnection connection;
public bool connected;
public KcpClient(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
public KcpClient(Action OnConnected, Action<ArraySegment<byte>, KcpChannel> OnData, Action OnDisconnected)
{
this.OnConnected = OnConnected;
this.OnData = OnData;
@ -45,10 +45,10 @@ public void Connect(string address, ushort port, bool noDelay, uint interval, in
connected = true;
OnConnected.Invoke();
};
connection.OnData = (message) =>
connection.OnData = (message, channel) =>
{
//Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(message);
OnData.Invoke(message, channel);
};
connection.OnDisconnected = () =>
{

View File

@ -17,7 +17,7 @@ public abstract class KcpConnection
KcpState state = KcpState.Disconnected;
public Action OnAuthenticated;
public Action<ArraySegment<byte>> OnData;
public Action<ArraySegment<byte>, KcpChannel> OnData;
public Action OnDisconnected;
// Mirror needs a way to stop the kcp message processing while loop
@ -43,19 +43,20 @@ public abstract class KcpConnection
const int CHANNEL_HEADER_SIZE = 1;
// reliable channel (= kcp) MaxMessageSize so the outside knows largest
// allowed message to send the calculation in Send() is not obvious at
// allowed message to send. the calculation in Send() is not obvious at
// all, so let's provide the helper here.
//
// kcp does fragmentation, so max message is way larger than MTU.
//
// -> runtime MTU changes are disabled: mss is always MTU_DEF-OVERHEAD
// -> Send() checks if fragment count < WND_RCV, so we use WND_RCV - 1.
// note that Send() checks WND_RCV instead of wnd_rcv which may or
// may not be a bug in original kcp. but since it uses the define, we
// can use that here too.
// -> Send() checks if fragment count < rcv_wnd, so we use rcv_wnd - 1.
// NOTE that original kcp has a bug where WND_RCV default is used
// instead of configured rcv_wnd, limiting max message size to 144 KB
// https://github.com/skywind3000/kcp/pull/291
// we fixed this in kcp2k.
// -> we add 1 byte KcpHeader enum to each message, so -1
//
// IMPORTANT: max message is MTU * WND_RCV, in other words it completely
// IMPORTANT: max message is MTU * rcv_wnd, in other words it completely
// fills the receive window! due to head of line blocking,
// all other messages have to wait while a maxed size message
// is being delivered.
@ -63,7 +64,7 @@ public abstract class KcpConnection
// for batching.
// => sending UNRELIABLE max message size most of the time is
// best for performance (use that one for batching!)
public const int ReliableMaxMessageSize = (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * (Kcp.WND_RCV - 1) - 1;
public static int ReliableMaxMessageSize(uint rcv_wnd) => (Kcp.MTU_DEF - Kcp.OVERHEAD - CHANNEL_HEADER_SIZE) * ((int)rcv_wnd - 1) - 1;
// unreliable max message size is simply MTU - channel header size
public const int UnreliableMaxMessageSize = Kcp.MTU_DEF - CHANNEL_HEADER_SIZE;
@ -71,13 +72,13 @@ public abstract class KcpConnection
// buffer to receive kcp's processed messages (avoids allocations).
// IMPORTANT: this is for KCP messages. so it needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpMessageBuffer;// = new byte[1 + ReliableMaxMessageSize];
// send buffer for handing user messages to kcp for processing.
// (avoids allocations).
// IMPORTANT: needs to be of size:
// 1 byte header + MaxMessageSize content
byte[] kcpSendBuffer = new byte[1 + ReliableMaxMessageSize];
byte[] kcpSendBuffer;// = new byte[1 + ReliableMaxMessageSize];
// raw send buffer is exactly MTU.
byte[] rawSendBuffer = new byte[Kcp.MTU_DEF];
@ -143,6 +144,11 @@ protected void SetupKcp(bool noDelay, uint interval = Kcp.INTERVAL, int fastRese
// message afterwards.
kcp.SetMtu(Kcp.MTU_DEF - CHANNEL_HEADER_SIZE);
// create message buffers AFTER window size is set
// see comments on buffer definition for the "+1" part
kcpMessageBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];
kcpSendBuffer = new byte[1 + ReliableMaxMessageSize(receiveWindowSize)];
this.timeout = timeout;
state = KcpState.Connected;
@ -328,7 +334,7 @@ void TickIncoming_Authenticated(uint time)
if (message.Count > 0)
{
//Log.Warning($"Kcp recv msg: {BitConverter.ToString(message.Array, message.Offset, message.Count)}");
OnData?.Invoke(message);
OnData?.Invoke(message, KcpChannel.Reliable);
}
// empty data = attacker, or something went wrong
else
@ -489,7 +495,7 @@ public void RawInput(byte[] buffer, int msgLength)
if (!paused)
{
ArraySegment<byte> message = new ArraySegment<byte>(buffer, 1, msgLength - 1);
OnData?.Invoke(message);
OnData?.Invoke(message, KcpChannel.Unreliable);
}
// set last receive time to avoid timeout.
@ -551,7 +557,7 @@ void SendReliable(KcpHeader header, ArraySegment<byte> content)
}
}
// otherwise content is larger than MaxMessageSize. let user know!
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}");
else Log.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}");
}
void SendUnreliable(ArraySegment<byte> message)

View File

@ -11,7 +11,7 @@ public class KcpServer
{
// events
public Action<int> OnConnected;
public Action<int, ArraySegment<byte>> OnData;
public Action<int, ArraySegment<byte>, KcpChannel> OnData;
public Action<int> OnDisconnected;
// configuration
@ -57,7 +57,7 @@ public class KcpServer
public Dictionary<int, KcpServerConnection> connections = new Dictionary<int, KcpServerConnection>();
public KcpServer(Action<int> OnConnected,
Action<int, ArraySegment<byte>> OnData,
Action<int, ArraySegment<byte>, KcpChannel> OnData,
Action<int> OnDisconnected,
bool DualMode,
bool NoDelay,
@ -226,11 +226,11 @@ public void TickIncoming()
// internet.
// setup data event
connection.OnData = (message) =>
connection.OnData = (message, channel) =>
{
// call mirror event
//Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})");
OnData.Invoke(connectionId, message);
OnData.Invoke(connectionId, message, channel);
};
// setup disconnected event

View File

@ -6,7 +6,7 @@ namespace kcp2k
{
public class KcpClientNonAlloc : KcpClient
{
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>> OnData, Action OnDisconnected)
public KcpClientNonAlloc(Action OnConnected, Action<ArraySegment<byte>, KcpChannel> OnData, Action OnDisconnected)
: base(OnConnected, OnData, OnDisconnected)
{
}

View File

@ -11,7 +11,7 @@ public class KcpServerNonAlloc : KcpServer
{
IPEndPointNonAlloc reusableClientEP;
public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT)
public KcpServerNonAlloc(Action<int> OnConnected, Action<int, ArraySegment<byte>, KcpChannel> OnData, Action<int> OnDisconnected, bool DualMode, bool NoDelay, uint Interval, int FastResend = 0, bool CongestionWindow = true, uint SendWindowSize = Kcp.WND_SND, uint ReceiveWindowSize = Kcp.WND_RCV, int Timeout = KcpConnection.DEFAULT_TIMEOUT)
: base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout)
{
// create reusableClientEP either IPv4 or IPv6

View File

@ -262,10 +262,12 @@ public int Send(byte[] buffer, int offset, int len)
if (len <= mss) count = 1;
else count = (int)((len + mss - 1) / mss);
// original kcp uses WND_RCV const even though rcv_wnd is the
// runtime variable. may or may not be correct, see also:
// see also: https://github.com/skywind3000/kcp/pull/291/files
if (count >= WND_RCV) return -2;
// original kcp uses WND_RCV const instead of rcv_wnd runtime:
// https://github.com/skywind3000/kcp/pull/291/files
// which always limits max message size to 144 KB:
//if (count >= WND_RCV) return -2;
// using configured rcv_wnd uncorks max message size to 'any':
if (count >= rcv_wnd) return -2;
if (count == 0) count = 1;