mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
fix: #3307 kcp2k V1.24
This commit is contained in:
parent
42a0c8ef67
commit
3e3a1cbb44
@ -1,3 +1,7 @@
|
||||
V1.24 [2022-12-14]
|
||||
- KcpClient: fixed NullReferenceException when connection without a server.
|
||||
added test coverage to ensure this never happens again.
|
||||
|
||||
V1.23 [2022-12-07]
|
||||
- KcpClient: rawReceiveBuffer exposed
|
||||
- fix: KcpServer RawSend uses connection.remoteEndPoint instead of the helper
|
||||
|
@ -16,7 +16,7 @@ public static bool ResolveHostname(string hostname, out IPAddress[] addresses)
|
||||
}
|
||||
catch (SocketException exception)
|
||||
{
|
||||
Log.Info($"[KCP] Failed to resolve host: {hostname} reason: {exception}");
|
||||
Log.Info($"Failed to resolve host: {hostname} reason: {exception}");
|
||||
addresses = null;
|
||||
return false;
|
||||
}
|
||||
@ -34,7 +34,7 @@ public static void MaximizeSocketBuffers(Socket socket)
|
||||
socket.SetReceiveBufferToOSLimit();
|
||||
socket.SetSendBufferToOSLimit();
|
||||
|
||||
Log.Info($"[KCP] RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x) maximized to OS limits!");
|
||||
Log.Info($"Kcp: RecvBuf = {initialReceive}=>{socket.ReceiveBufferSize} ({socket.ReceiveBufferSize/initialReceive}x) SendBuf = {initialSend}=>{socket.SendBufferSize} ({socket.SendBufferSize/initialSend}x) maximized to OS limits!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,21 +24,20 @@ public class KcpClient
|
||||
// to reuse the buffer.
|
||||
protected readonly byte[] rawReceiveBuffer = new byte[Kcp.MTU_DEF];
|
||||
|
||||
// events
|
||||
// callbacks
|
||||
// even for errors, to allow liraries to show popups etc.
|
||||
// instead of logging directly.
|
||||
// (string instead of Exception for ease of use and to avoid user panic)
|
||||
public Action OnConnected;
|
||||
public Action<ArraySegment<byte>, KcpChannel> OnData;
|
||||
public Action OnDisconnected;
|
||||
// error callback instead of logging.
|
||||
// allows libraries to show popups etc.
|
||||
// (string instead of Exception for ease of use and to avoid user panic)
|
||||
public Action<ErrorCode, string> OnError;
|
||||
|
||||
// state
|
||||
public bool connected;
|
||||
|
||||
public KcpClient(Action OnConnected,
|
||||
Action<ArraySegment<byte>,
|
||||
KcpChannel> OnData,
|
||||
Action<ArraySegment<byte>, KcpChannel> OnData,
|
||||
Action OnDisconnected,
|
||||
Action<ErrorCode, string> OnError)
|
||||
{
|
||||
@ -62,7 +61,7 @@ public void Connect(string address,
|
||||
{
|
||||
if (connected)
|
||||
{
|
||||
Log.Warning("[KCP] client already connected!");
|
||||
Log.Warning("KCP: client already connected!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -72,7 +71,7 @@ public void Connect(string address,
|
||||
// setup events
|
||||
peer.OnAuthenticated = () =>
|
||||
{
|
||||
Log.Info($"[KCP] OnClientConnected");
|
||||
Log.Info($"KCP: OnClientConnected");
|
||||
connected = true;
|
||||
OnConnected();
|
||||
};
|
||||
@ -83,7 +82,7 @@ public void Connect(string address,
|
||||
};
|
||||
peer.OnDisconnected = () =>
|
||||
{
|
||||
Log.Info($"[KCP] OnClientDisconnected");
|
||||
Log.Info($"KCP: OnClientDisconnected");
|
||||
connected = false;
|
||||
peer = null;
|
||||
socket?.Close();
|
||||
@ -96,13 +95,13 @@ public void Connect(string address,
|
||||
OnError(error, reason);
|
||||
};
|
||||
|
||||
Log.Info($"[KCP] Client connecting to {address}:{port}");
|
||||
Log.Info($"KcpClient: connect to {address}:{port}");
|
||||
|
||||
// try resolve host name
|
||||
if (!Common.ResolveHostname(address, out IPAddress[] addresses))
|
||||
{
|
||||
// pass error to user callback. no need to log it manually.
|
||||
peer.OnError(ErrorCode.DnsResolve, $"[KCP] Failed to resolve host: {address}");
|
||||
peer.OnError(ErrorCode.DnsResolve, $"Failed to resolve host: {address}");
|
||||
peer.OnDisconnected();
|
||||
return;
|
||||
}
|
||||
@ -119,7 +118,7 @@ public void Connect(string address,
|
||||
Common.MaximizeSocketBuffers(socket);
|
||||
}
|
||||
// otherwise still log the defaults for info.
|
||||
else Log.Info($"[KCP] Client RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(maximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit.");
|
||||
else Log.Info($"KcpClient: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(maximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit.");
|
||||
|
||||
// bind to endpoint so we can use send/recv instead of sendto/recvfrom.
|
||||
socket.Connect(remoteEndPoint);
|
||||
@ -156,7 +155,7 @@ protected virtual void RawReceive()
|
||||
// the other end closing the connection is not an 'error'.
|
||||
// but connections should never just end silently.
|
||||
// at least log a message for easier debugging.
|
||||
Log.Info($"[KCP] ClientConnection: looks like the other end has closed the connection. This is fine: {ex}");
|
||||
Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}");
|
||||
peer.Disconnect();
|
||||
}
|
||||
}
|
||||
@ -172,7 +171,7 @@ public void Send(ArraySegment<byte> segment, KcpChannel channel)
|
||||
{
|
||||
if (!connected)
|
||||
{
|
||||
Log.Warning("[KCP] Can't send because client not connected!");
|
||||
Log.Warning("KCP: can't send because client not connected!");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -198,11 +197,10 @@ public void TickIncoming()
|
||||
// recv on socket first, then process incoming
|
||||
// (even if we didn't receive anything. need to tick ping etc.)
|
||||
// (connection is null if not active)
|
||||
if (peer != null)
|
||||
{
|
||||
RawReceive();
|
||||
peer.TickIncoming();
|
||||
}
|
||||
if (peer != null) RawReceive();
|
||||
|
||||
// RawReceive may have disconnected peer. null check again.
|
||||
peer?.TickIncoming();
|
||||
}
|
||||
|
||||
// process outgoing messages. should be called after updating the world.
|
||||
|
@ -8,7 +8,7 @@ public enum KcpHeader : byte
|
||||
{
|
||||
// don't react on 0x00. might help to filter out random noise.
|
||||
Handshake = 1,
|
||||
// ping goes over reliable & KcpHeader for now. could go over reliable
|
||||
// ping goes over reliable & KcpHeader for now. could go over unreliable
|
||||
// too. there is no real difference except that this is easier because
|
||||
// we already have a KcpHeader for reliable messages.
|
||||
// ping is only used to keep it alive, so latency doesn't matter.
|
||||
|
@ -39,7 +39,7 @@ public class KcpPeer
|
||||
// internal time.
|
||||
// StopWatch offers ElapsedMilliSeconds and should be more precise than
|
||||
// Unity's time.deltaTime over long periods.
|
||||
readonly Stopwatch refTime = new Stopwatch();
|
||||
readonly Stopwatch watch = new Stopwatch();
|
||||
|
||||
// we need to subtract the channel byte from every MaxMessageSize
|
||||
// calculation.
|
||||
@ -116,9 +116,9 @@ public static int ReliableMaxMessageSize(uint rcv_wnd) =>
|
||||
internal const int QueueDisconnectThreshold = 10000;
|
||||
|
||||
// getters for queue and buffer counts, used for debug info
|
||||
public int SendQueueCount => kcp.snd_queue.Count;
|
||||
public int ReceiveQueueCount => kcp.rcv_queue.Count;
|
||||
public int SendBufferCount => kcp.snd_buf.Count;
|
||||
public int SendQueueCount => kcp.snd_queue.Count;
|
||||
public int ReceiveQueueCount => kcp.rcv_queue.Count;
|
||||
public int SendBufferCount => kcp.snd_buf.Count;
|
||||
public int ReceiveBufferCount => kcp.rcv_buf.Count;
|
||||
|
||||
// maximum send rate per second can be calculated from kcp parameters
|
||||
@ -176,7 +176,7 @@ public KcpPeer(
|
||||
|
||||
this.timeout = timeout;
|
||||
|
||||
refTime.Start();
|
||||
watch.Start();
|
||||
}
|
||||
|
||||
void HandleTimeout(uint time)
|
||||
@ -223,7 +223,7 @@ void HandleChoked()
|
||||
// see QueueSizeDisconnect comments.
|
||||
// => include all of kcp's buffers and the unreliable queue!
|
||||
int total = kcp.rcv_queue.Count + kcp.snd_queue.Count +
|
||||
kcp.rcv_buf.Count + kcp.snd_buf.Count;
|
||||
kcp.rcv_buf.Count + kcp.snd_buf.Count;
|
||||
if (total >= QueueDisconnectThreshold)
|
||||
{
|
||||
// pass error to user callback. no need to log it manually.
|
||||
@ -248,45 +248,41 @@ void HandleChoked()
|
||||
// -> to avoid buffering, unreliable messages call OnData directly.
|
||||
bool ReceiveNextReliable(out KcpHeader header, out ArraySegment<byte> message)
|
||||
{
|
||||
int msgSize = kcp.PeekSize();
|
||||
if (msgSize > 0)
|
||||
{
|
||||
// only allow receiving up to buffer sized messages.
|
||||
// otherwise we would get BlockCopy ArgumentException anyway.
|
||||
if (msgSize <= kcpMessageBuffer.Length)
|
||||
{
|
||||
// receive from kcp
|
||||
int received = kcp.Receive(kcpMessageBuffer, msgSize);
|
||||
if (received >= 0)
|
||||
{
|
||||
// extract header & content without header
|
||||
header = (KcpHeader)kcpMessageBuffer[0];
|
||||
message = new ArraySegment<byte>(kcpMessageBuffer, 1, msgSize - 1);
|
||||
lastReceiveTime = (uint)refTime.ElapsedMilliseconds;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if receive failed, close everything
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={received}. closing connection.");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
// we don't allow sending messages > Max, so this must be an
|
||||
// attacker. let's disconnect to avoid allocation attacks etc.
|
||||
else
|
||||
{
|
||||
// pass error to user callback. no need to log it manually.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
message = default;
|
||||
header = KcpHeader.Disconnect;
|
||||
return false;
|
||||
|
||||
int msgSize = kcp.PeekSize();
|
||||
if (msgSize <= 0) return false;
|
||||
|
||||
// only allow receiving up to buffer sized messages.
|
||||
// otherwise we would get BlockCopy ArgumentException anyway.
|
||||
if (msgSize > kcpMessageBuffer.Length)
|
||||
{
|
||||
// we don't allow sending messages > Max, so this must be an
|
||||
// attacker. let's disconnect to avoid allocation attacks etc.
|
||||
// pass error to user callback. no need to log it manually.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
// receive from kcp
|
||||
int received = kcp.Receive(kcpMessageBuffer, msgSize);
|
||||
if (received < 0)
|
||||
{
|
||||
// if receive failed, close everything
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: Receive failed with error={received}. closing connection.");
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract header & content without header
|
||||
header = (KcpHeader)kcpMessageBuffer[0];
|
||||
message = new ArraySegment<byte>(kcpMessageBuffer, 1, msgSize - 1);
|
||||
lastReceiveTime = (uint)watch.ElapsedMilliseconds;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TickIncoming_Connected(uint time)
|
||||
@ -308,7 +304,7 @@ void TickIncoming_Connected(uint time)
|
||||
// we were waiting for a handshake.
|
||||
// it proves that the other end speaks our protocol.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"[KCP] {GetType()}: received handshake");
|
||||
Log.Info($"{GetType()}: received handshake");
|
||||
state = KcpState.Authenticated;
|
||||
OnAuthenticated?.Invoke();
|
||||
break;
|
||||
@ -350,7 +346,7 @@ void TickIncoming_Authenticated(uint time)
|
||||
{
|
||||
// should never receive another handshake after auth
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Warning($"[KCP] {GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection.");
|
||||
Log.Warning($"{GetType()}: received invalid header {header} while Authenticated. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
@ -381,7 +377,7 @@ void TickIncoming_Authenticated(uint time)
|
||||
{
|
||||
// disconnect might happen
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"[KCP] {GetType()}: received disconnect message");
|
||||
Log.Info($"{GetType()}: received disconnect message");
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
@ -391,7 +387,7 @@ void TickIncoming_Authenticated(uint time)
|
||||
|
||||
public void TickIncoming()
|
||||
{
|
||||
uint time = (uint)refTime.ElapsedMilliseconds;
|
||||
uint time = (uint)watch.ElapsedMilliseconds;
|
||||
|
||||
try
|
||||
{
|
||||
@ -443,7 +439,7 @@ public void TickIncoming()
|
||||
|
||||
public void TickOutgoing()
|
||||
{
|
||||
uint time = (uint)refTime.ElapsedMilliseconds;
|
||||
uint time = (uint)watch.ElapsedMilliseconds;
|
||||
|
||||
try
|
||||
{
|
||||
@ -495,78 +491,78 @@ public void TickOutgoing()
|
||||
// feed the rest to kcp.
|
||||
public void RawInput(byte[] buffer, int offset, int size)
|
||||
{
|
||||
// parse channel
|
||||
if (size > 0)
|
||||
{
|
||||
byte channel = buffer[offset + 0];
|
||||
switch (channel)
|
||||
{
|
||||
case (byte)KcpChannel.Reliable:
|
||||
{
|
||||
// input into kcp, but skip channel byte
|
||||
int input = kcp.Input(buffer, offset + 1, size - 1);
|
||||
if (input != 0)
|
||||
{
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Warning($"[KCP] {GetType()}: Input failed with error={input} for buffer with length={size - 1}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (byte)KcpChannel.Unreliable:
|
||||
{
|
||||
// ideally we would queue all unreliable messages and
|
||||
// then process them in ReceiveNext() together with the
|
||||
// reliable messages, but:
|
||||
// -> queues/allocations/pools are slow and complex.
|
||||
// -> DOTSNET 10k is actually slower if we use pooled
|
||||
// unreliable messages for transform messages.
|
||||
//
|
||||
// DOTSNET 10k benchmark:
|
||||
// reliable-only: 170 FPS
|
||||
// unreliable queued: 130-150 FPS
|
||||
// unreliable direct: 183 FPS(!)
|
||||
//
|
||||
// DOTSNET 50k benchmark:
|
||||
// reliable-only: FAILS (queues keep growing)
|
||||
// unreliable direct: 18-22 FPS(!)
|
||||
//
|
||||
// -> all unreliable messages are DATA messages anyway.
|
||||
// -> let's skip the magic and call OnData directly if
|
||||
// the current state allows it.
|
||||
if (state == KcpState.Authenticated)
|
||||
{
|
||||
ArraySegment<byte> message = new ArraySegment<byte>(buffer, offset + 1, size - 1);
|
||||
OnData?.Invoke(message, KcpChannel.Unreliable);
|
||||
// ensure valid size: at least 1 byte for channel
|
||||
if (size <= 0) return;
|
||||
|
||||
// set last receive time to avoid timeout.
|
||||
// -> we do this in ANY case even if not enabled.
|
||||
// a message is a message.
|
||||
// -> we set last receive time for both reliable and
|
||||
// unreliable messages. both count.
|
||||
// otherwise a connection might time out even
|
||||
// though unreliable were received, but no
|
||||
// reliable was received.
|
||||
lastReceiveTime = (uint)refTime.ElapsedMilliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// should never happen
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: received unreliable message in state {state}. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// parse channel
|
||||
byte channel = buffer[offset + 0];
|
||||
switch (channel)
|
||||
{
|
||||
case (byte)KcpChannel.Reliable:
|
||||
{
|
||||
// input into kcp, but skip channel byte
|
||||
int input = kcp.Input(buffer, offset + 1, size - 1);
|
||||
if (input != 0)
|
||||
{
|
||||
// not a valid channel. random data or attacks.
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: Disconnecting connection because of invalid channel header: {channel}");
|
||||
Disconnect();
|
||||
break;
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Warning($"{GetType()}: Input failed with error={input} for buffer with length={size - 1}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case (byte)KcpChannel.Unreliable:
|
||||
{
|
||||
// ideally we would queue all unreliable messages and
|
||||
// then process them in ReceiveNext() together with the
|
||||
// reliable messages, but:
|
||||
// -> queues/allocations/pools are slow and complex.
|
||||
// -> DOTSNET 10k is actually slower if we use pooled
|
||||
// unreliable messages for transform messages.
|
||||
//
|
||||
// DOTSNET 10k benchmark:
|
||||
// reliable-only: 170 FPS
|
||||
// unreliable queued: 130-150 FPS
|
||||
// unreliable direct: 183 FPS(!)
|
||||
//
|
||||
// DOTSNET 50k benchmark:
|
||||
// reliable-only: FAILS (queues keep growing)
|
||||
// unreliable direct: 18-22 FPS(!)
|
||||
//
|
||||
// -> all unreliable messages are DATA messages anyway.
|
||||
// -> let's skip the magic and call OnData directly if
|
||||
// the current state allows it.
|
||||
if (state == KcpState.Authenticated)
|
||||
{
|
||||
ArraySegment<byte> message = new ArraySegment<byte>(buffer, offset + 1, size - 1);
|
||||
OnData?.Invoke(message, KcpChannel.Unreliable);
|
||||
|
||||
// set last receive time to avoid timeout.
|
||||
// -> we do this in ANY case even if not enabled.
|
||||
// a message is a message.
|
||||
// -> we set last receive time for both reliable and
|
||||
// unreliable messages. both count.
|
||||
// otherwise a connection might time out even
|
||||
// though unreliable were received, but no
|
||||
// reliable was received.
|
||||
lastReceiveTime = (uint)watch.ElapsedMilliseconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
// should never happen
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: received unreliable message in state {state}. Disconnecting the connection.");
|
||||
Disconnect();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// not a valid channel. random data or attacks.
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidReceive, $"{GetType()}: Disconnecting connection because of invalid channel header: {channel}");
|
||||
Disconnect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -586,42 +582,46 @@ void RawSendReliable(byte[] data, int length)
|
||||
void SendReliable(KcpHeader header, ArraySegment<byte> content)
|
||||
{
|
||||
// 1 byte header + content needs to fit into send buffer
|
||||
if (1 + content.Count <= kcpSendBuffer.Length) // TODO
|
||||
if (1 + content.Count > kcpSendBuffer.Length) // TODO
|
||||
{
|
||||
// copy header, content (if any) into send buffer
|
||||
kcpSendBuffer[0] = (byte)header;
|
||||
if (content.Count > 0)
|
||||
Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count);
|
||||
|
||||
// send to kcp for processing
|
||||
int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count);
|
||||
if (sent < 0)
|
||||
{
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={sent} for content with length={content.Count}");
|
||||
}
|
||||
// otherwise content is larger than MaxMessageSize. let user know!
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}");
|
||||
return;
|
||||
}
|
||||
|
||||
// copy header, content (if any) into send buffer
|
||||
kcpSendBuffer[0] = (byte)header;
|
||||
if (content.Count > 0)
|
||||
Buffer.BlockCopy(content.Array, content.Offset, kcpSendBuffer, 1, content.Count);
|
||||
|
||||
// send to kcp for processing
|
||||
int sent = kcp.Send(kcpSendBuffer, 0, 1 + content.Count);
|
||||
if (sent < 0)
|
||||
{
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidSend, $"{GetType()}: Send failed with error={sent} for content with length={content.Count}");
|
||||
}
|
||||
// otherwise content is larger than MaxMessageSize. let user know!
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
else OnError(ErrorCode.InvalidSend, $"{GetType()}: Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize(kcp.rcv_wnd)}");
|
||||
}
|
||||
|
||||
void SendUnreliable(ArraySegment<byte> message)
|
||||
{
|
||||
// message size needs to be <= unreliable max size
|
||||
if (message.Count <= UnreliableMaxMessageSize)
|
||||
if (message.Count > UnreliableMaxMessageSize)
|
||||
{
|
||||
// copy channel header, data into raw send buffer, then send
|
||||
rawSendBuffer[0] = (byte)KcpChannel.Unreliable;
|
||||
Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1, message.Count);
|
||||
|
||||
// IO send
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, message.Count + 1);
|
||||
RawSend(segment);
|
||||
// otherwise content is larger than MaxMessageSize. let user know!
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Error($"{GetType()}: Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}");
|
||||
return;
|
||||
}
|
||||
// otherwise content is larger than MaxMessageSize. let user know!
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
else Log.Error($"[KCP] {GetType()}: Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}");
|
||||
|
||||
// copy channel header, data into raw send buffer, then send
|
||||
rawSendBuffer[0] = (byte)KcpChannel.Unreliable;
|
||||
Buffer.BlockCopy(message.Array, message.Offset, rawSendBuffer, 1, message.Count);
|
||||
|
||||
// IO send
|
||||
ArraySegment<byte> segment = new ArraySegment<byte>(rawSendBuffer, 0, message.Count + 1);
|
||||
RawSend(segment);
|
||||
}
|
||||
|
||||
// server & client need to send handshake at different times, so we need
|
||||
@ -632,8 +632,8 @@ void SendUnreliable(ArraySegment<byte> message)
|
||||
// => handshake info needs to be delivered, so it goes over reliable.
|
||||
public void SendHandshake()
|
||||
{
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"[KCP] {GetType()}: sending Handshake to other end!");
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"{GetType()}: sending Handshake to other end!");
|
||||
SendReliable(KcpHeader.Handshake, default);
|
||||
}
|
||||
|
||||
@ -647,7 +647,7 @@ public void SendData(ArraySegment<byte> data, KcpChannel channel)
|
||||
{
|
||||
// pass error to user callback. no need to log it manually.
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
OnError(ErrorCode.InvalidSend, $"[KCP] {GetType()}: tried sending empty message. This should never happen. Disconnecting.");
|
||||
OnError(ErrorCode.InvalidSend, $"{GetType()}: tried sending empty message. This should never happen. Disconnecting.");
|
||||
Disconnect();
|
||||
return;
|
||||
}
|
||||
@ -700,7 +700,7 @@ public void Disconnect()
|
||||
|
||||
// set as Disconnected, call event
|
||||
// GetType() shows Server/ClientConn instead of just Connection.
|
||||
Log.Info($"[KCP] {GetType()}: Disconnected.");
|
||||
Log.Info($"{GetType()}: Disconnected.");
|
||||
state = KcpState.Disconnected;
|
||||
OnDisconnected?.Invoke();
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ namespace kcp2k
|
||||
{
|
||||
public class KcpServer
|
||||
{
|
||||
// events
|
||||
// callbacks
|
||||
// even for errors, to allow liraries to show popups etc.
|
||||
// instead of logging directly.
|
||||
// (string instead of Exception for ease of use and to avoid user panic)
|
||||
public Action<int> OnConnected;
|
||||
public Action<int, ArraySegment<byte>, KcpChannel> OnData;
|
||||
public Action<int> OnDisconnected;
|
||||
// error callback instead of logging.
|
||||
// allows libraries to show popups etc.
|
||||
// (string instead of Exception for ease of use and to avoid user panic)
|
||||
public Action<int, ErrorCode, string> OnError;
|
||||
|
||||
// socket configuration
|
||||
@ -101,7 +101,7 @@ public KcpServer(Action<int> OnConnected,
|
||||
// create newClientEP either IPv4 or IPv6
|
||||
newClientEP = DualMode
|
||||
? new IPEndPoint(IPAddress.IPv6Any, 0)
|
||||
: new IPEndPoint(IPAddress.Any, 0);
|
||||
: new IPEndPoint(IPAddress.Any, 0);
|
||||
}
|
||||
|
||||
public virtual bool IsActive() => socket != null;
|
||||
@ -111,23 +111,21 @@ public virtual void Start(ushort port)
|
||||
// only start once
|
||||
if (socket != null)
|
||||
{
|
||||
Log.Warning("[KCP] server already started!");
|
||||
Log.Warning("KCP: server already started!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Info($"[KCP] Starting server on port {port}");
|
||||
|
||||
// listen
|
||||
if (DualMode)
|
||||
{
|
||||
// IPv6 socket with DualMode
|
||||
// IPv6 socket with DualMode @ "::" : port
|
||||
socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.DualMode = true;
|
||||
socket.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPv4 socket
|
||||
// IPv4 socket @ "0.0.0.0" : port
|
||||
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
socket.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
}
|
||||
@ -140,7 +138,7 @@ public virtual void Start(ushort port)
|
||||
Common.MaximizeSocketBuffers(socket);
|
||||
}
|
||||
// otherwise still log the defaults for info.
|
||||
else Log.Info($"[KCP] Server: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(MaximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit.");
|
||||
else Log.Info($"KcpServer: RecvBuf = {socket.ReceiveBufferSize} SendBuf = {socket.SendBufferSize}. If connections drop under heavy load, enable {nameof(MaximizeSendReceiveBuffersToOSLimit)} to increase it to OS limit. If they still drop, increase the OS limit.");
|
||||
}
|
||||
|
||||
public void Send(int connectionId, ArraySegment<byte> segment, KcpChannel channel)
|
||||
@ -172,7 +170,7 @@ public IPEndPoint GetClientEndPoint(int connectionId)
|
||||
// io - poll.
|
||||
// return true if there is data to read.
|
||||
// after which RawReceive will be called.
|
||||
// virtual because for relays,
|
||||
// virtual for relays.
|
||||
protected virtual bool RawPoll() =>
|
||||
socket != null && socket.Poll(0, SelectMode.SelectRead);
|
||||
|
||||
@ -211,7 +209,7 @@ protected virtual void RawSend(int connectionId, ArraySegment<byte> data)
|
||||
// get the connection's endpoint
|
||||
if (!connections.TryGetValue(connectionId, out KcpServerConnection connection))
|
||||
{
|
||||
Debug.LogWarning($"[KCP] Server.RawSend: invalid connectionId={connectionId}");
|
||||
Debug.LogWarning($"KcpServer.RawSend: invalid connectionId={connectionId}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -283,7 +281,7 @@ void ProcessNext()
|
||||
|
||||
// add to connections dict after being authenticated.
|
||||
connections.Add(connectionId, connection);
|
||||
Log.Info($"[KCP] server added connection({connectionId})");
|
||||
Log.Info($"KCP: server added connection({connectionId})");
|
||||
|
||||
// setup Data + Disconnected events only AFTER the
|
||||
// handshake. we don't want to fire OnServerDisconnected
|
||||
@ -307,7 +305,7 @@ void ProcessNext()
|
||||
connectionsToRemove.Add(connectionId);
|
||||
|
||||
// call mirror event
|
||||
Log.Info($"[KCP] OnServerDisconnected({connectionId})");
|
||||
Log.Info($"KCP: OnServerDisconnected({connectionId})");
|
||||
OnDisconnected(connectionId);
|
||||
};
|
||||
|
||||
@ -318,7 +316,7 @@ void ProcessNext()
|
||||
};
|
||||
|
||||
// finally, call mirror OnConnected event
|
||||
Log.Info($"[KCP] OnServerConnected({connectionId})");
|
||||
Log.Info($"KCP: OnServerConnected({connectionId})");
|
||||
OnConnected(connectionId);
|
||||
};
|
||||
|
||||
@ -345,13 +343,13 @@ void ProcessNext()
|
||||
// the other end closing the connection is not an 'error'.
|
||||
// but connections should never just end silently.
|
||||
// at least log a message for easier debugging.
|
||||
Log.Info($"[KCP] ClientConnection: looks like the other end has closed the connection. This is fine: {ex}");
|
||||
Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// process incoming messages. should be called before updating the world.
|
||||
// virtual because relay may need to inject their own ping or similar.
|
||||
HashSet<int> connectionsToRemove = new HashSet<int>();
|
||||
readonly HashSet<int> connectionsToRemove = new HashSet<int>();
|
||||
public virtual void TickIncoming()
|
||||
{
|
||||
while (RawPoll())
|
||||
|
@ -907,6 +907,9 @@ void FlushBuffer()
|
||||
//
|
||||
// 'current' - current timestamp in millisec. pass it to Kcp so that
|
||||
// Kcp doesn't have to do any stopwatch/deltaTime/etc. code
|
||||
//
|
||||
// time as uint, likely to minimize bandwidth.
|
||||
// uint.max = 4294967295 ms = 1193 hours = 49 days
|
||||
public void Update(uint currentTimeMilliSeconds)
|
||||
{
|
||||
current = currentTimeMilliSeconds;
|
||||
|
Loading…
Reference in New Issue
Block a user