// call from transport update public void RawReceive() { try { if (socket != null) { while (socket.Poll(0, SelectMode.SelectRead)) { int msgLength = socket.ReceiveFrom(rawReceiveBuffer, ref remoteEndpoint); // IMPORTANT: detect if buffer was too small for the // received msgLength. otherwise the excess // data would be silently lost. // (see ReceiveFrom documentation) if (msgLength <= rawReceiveBuffer.Length) { //Log.Debug($"KCP: client raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); RawInput(rawReceiveBuffer, msgLength); } else { KCPLog.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); Disconnect(); } } } } // this is fine, the socket might have been closed in the other end catch (SocketException) {} }
void SendReliable(KcpHeader header, ArraySegment <byte> content) { // 1 byte header + content needs to fit into send buffer 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) { KCPLog.Warning($"Send failed with error={sent} for content with length={content.Count}"); } } // otherwise content is larger than MaxMessageSize. let user know! else { KCPLog.Error($"Failed to send reliable message of size {content.Count} because it's larger than ReliableMaxMessageSize={ReliableMaxMessageSize}"); } }
void SendUnreliable(ArraySegment <byte> message) { // message size needs to be <= unreliable max size if (message.Count <= UnreliableMaxMessageSize) { // copy channel header, data into raw send buffer, then send rawSendBuffer[0] = (byte)KcpChannel.Unreliable; Buffer.BlockCopy(message.Array, 0, rawSendBuffer, 1, message.Count); RawSend(rawSendBuffer, message.Count + 1); } // otherwise content is larger than MaxMessageSize. let user know! else { KCPLog.Error($"Failed to send unreliable message of size {message.Count} because it's larger than UnreliableMaxMessageSize={UnreliableMaxMessageSize}"); } }
public void TickIncoming() { uint time = (uint)refTime.ElapsedMilliseconds; try { switch (state) { case KcpState.Connected: { TickIncoming_Connected(time); break; } case KcpState.Authenticated: { TickIncoming_Authenticated(time); break; } case KcpState.Disconnected: { // do nothing while disconnected break; } } } catch (SocketException exception) { // this is ok, the connection was closed KCPLog.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (ObjectDisposedException exception) { // fine, socket was closed KCPLog.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (Exception ex) { // unexpected KCPLog.Error(ex.ToString()); Disconnect(); } }
public void TickIncoming() { while (socket != null && socket.Poll(0, SelectMode.SelectRead)) { try { int msgLength = socket.ReceiveFrom(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, ref newClientEP); //Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); // calculate connectionId from endpoint int connectionId = newClientEP.GetHashCode(); // IMPORTANT: detect if buffer was too small for the received // msgLength. otherwise the excess data would be // silently lost. // (see ReceiveFrom documentation) if (msgLength <= rawReceiveBuffer.Length) { // is this a new connection? if (!connections.TryGetValue(connectionId, out KcpServerConnection connection)) { // create a new KcpConnection connection = new KcpServerConnection(socket, newClientEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize); // DO NOT add to connections yet. only if the first message // is actually the kcp handshake. otherwise it's either: // * random data from the internet // * or from a client connection that we just disconnected // but that hasn't realized it yet, still sending data // from last session that we should absolutely ignore. // // // TODO this allocates a new KcpConnection for each new // internet connection. not ideal, but C# UDP Receive // already allocated anyway. // // expecting a MAGIC byte[] would work, but sending the raw // UDP message without kcp's reliability will have low // probability of being received. // // for now, this is fine. // setup authenticated event that also adds to connections connection.OnAuthenticated = () => { // only send handshake to client AFTER we received his // handshake in OnAuthenticated. // we don't want to reply to random internet messages // with handshakes each time. connection.SendHandshake(); // add to connections dict after being authenticated. connections.Add(connectionId, connection); KCPLog.Info($"KCP: server added connection({connectionId}): {newClientEP}"); // setup Data + Disconnected events only AFTER the // handshake. we don't want to fire OnServerDisconnected // every time we receive invalid random data from the // internet. // setup data event connection.OnData = (message) => { // call mirror event //Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})"); OnData.Invoke(connectionId, message); }; // setup disconnected event connection.OnDisconnected = () => { // flag for removal // (can't remove directly because connection is updated // and event is called while iterating all connections) connectionsToRemove.Add(connectionId); // call mirror event KCPLog.Info($"KCP: OnServerDisconnected({connectionId})"); OnDisconnected.Invoke(connectionId); }; // finally, call mirror OnConnected event KCPLog.Info($"KCP: OnServerConnected({connectionId})"); OnConnected.Invoke(connectionId); }; // now input the message & process received ones // connected event was set up. // tick will process the first message and adds the // connection if it was the handshake. connection.RawInput(rawReceiveBuffer, msgLength); connection.TickIncoming(); // again, do not add to connections. // if the first message wasn't the kcp handshake then // connection will simply be garbage collected. } // existing connection: simply input the message into kcp else { connection.RawInput(rawReceiveBuffer, msgLength); } } else { KCPLog.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}."); Disconnect(connectionId); } } // this is fine, the socket might have been closed in the other end catch (SocketException) {} } // process inputs for all server connections // (even if we didn't receive anything. need to tick ping etc.) foreach (KcpServerConnection connection in connections.Values) { connection.TickIncoming(); } // remove disconnected connections // (can't do it in connection.OnDisconnected because Tick is called // while iterating connections) foreach (int connectionId in connectionsToRemove) { connections.Remove(connectionId); } connectionsToRemove.Clear(); }