fix: #3307 kcp2k V1.24

This commit is contained in:
vis2k 2022-12-14 01:04:16 +01:00
parent 42a0c8ef67
commit 3e3a1cbb44
7 changed files with 191 additions and 188 deletions

View File

@ -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

View File

@ -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!");
}
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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();
}

View File

@ -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())

View File

@ -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;