public KcpServerConnection(Socket socket, EndPoint remoteEndPoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT) { this.socket = socket; this.remoteEndpoint = remoteEndPoint; this.reusableSendEndPoint = reusableSendEndPoint; SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, 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, uint MaxRetransmits = Kcp.DEADLINK, bool MaximizeSendReceiveBuffersToOSLimit = false) : base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout, MaxRetransmits, MaximizeSendReceiveBuffersToOSLimit) { // create reusableClientEP either IPv4 or IPv6 reusableClientEP = DualMode ? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0) : new IPEndPointNonAlloc(IPAddress.Any, 0); }
protected override void CreateRemoteEndPoint(IPAddress[] addresses, ushort port) { // create reusableEP with same address family as remoteEndPoint. // otherwise ReceiveFrom_NonAlloc couldn't use it. reusableEP = new IPEndPointNonAlloc(addresses[0], port); base.CreateRemoteEndPoint(addresses, port); }
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) : base(OnConnected, OnData, OnDisconnected, DualMode, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout) { // create reusableClientEP either IPv4 or IPv6 reusableClientEP = DualMode ? new IPEndPointNonAlloc(IPAddress.IPv6Any, 0) : new IPEndPointNonAlloc(IPAddress.Any, 0); }
protected override KcpServerConnection CreateConnection() { // IPEndPointNonAlloc is reused all the time. // we can't store that as the connection's endpoint. // we need a new copy! IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint(); // for allocation free sending, we also need another // IPEndPointNonAlloc... IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port); // create a new KcpConnection NonAlloc version // -> where-allocation IPEndPointNonAlloc is reused. // need to create a new one from the temp address. return(new KcpServerConnectionNonAlloc(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout, MaxRetransmits)); }
public void Connect(string host, ushort port, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT) { Log.Info($"KcpClient: connect to {host}:{port}"); IPAddress[] ipAddress = Dns.GetHostAddresses(host); if (ipAddress.Length < 1) { throw new SocketException((int)SocketError.HostNotFound); } remoteEndpoint = new IPEndPoint(ipAddress[0], port); socket = new Socket(remoteEndpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); // create reusableEP with same address family as remoteEndPoint. // otherwise ReceiveFrom_NonAlloc couldn't use it. reusableEP = new IPEndPointNonAlloc(ipAddress[0], port); socket.Connect(remoteEndpoint); SetupKcp(noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout); // client should send handshake to server as very first message SendHandshake(); RawReceive(); }
public void TickIncoming() { while (socket != null && socket.Poll(0, SelectMode.SelectRead)) { try { // NOTE: ReceiveFrom allocates. // we pass our IPEndPoint to ReceiveFrom. // receive from calls newClientEP.Create(socketAddr). // IPEndPoint.Create always returns a new IPEndPoint. // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 //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)}"); // where-allocation nonalloc ReceiveFrom. int msgLength = socket.ReceiveFrom_NonAlloc(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, reusableClientEP); SocketAddress remoteAddress = reusableClientEP.temp; // calculate connectionId from endpoint // NOTE: IPEndPoint.GetHashCode() allocates. // it calls m_Address.GetHashCode(). // m_Address is an IPAddress. // GetHashCode() allocates for IPv6: // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 // // => using only newClientEP.Port wouldn't work, because // different connections can have the same port. //int connectionId = newClientEP.GetHashCode(); // where-allocation nonalloc GetHashCode int connectionId = remoteAddress.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)) { // IPEndPointNonAlloc is reused all the time. // we can't store that as the connection's endpoint. // we need a new copy! IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint(); // for allocation free sending, we also need another // IPEndPointNonAlloc... IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port); // create a new KcpConnection // -> where-allocation IPEndPointNonAlloc is reused. // need to create a new one from the temp address. connection = new KcpServerConnection(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); // 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); Log.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 Log.Info($"KCP: OnServerDisconnected({connectionId})"); OnDisconnected.Invoke(connectionId); }; // finally, call mirror OnConnected event Log.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 { Log.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(); }
public KcpServerConnectionNonAlloc(Socket socket, EndPoint remoteEndpoint, IPEndPointNonAlloc reusableSendEndPoint, bool noDelay, uint interval = Kcp.INTERVAL, int fastResend = 0, bool congestionWindow = true, uint sendWindowSize = Kcp.WND_SND, uint receiveWindowSize = Kcp.WND_RCV, int timeout = DEFAULT_TIMEOUT, uint maxRetransmits = Kcp.DEADLINK) : base(socket, remoteEndpoint, noDelay, interval, fastResend, congestionWindow, sendWindowSize, receiveWindowSize, timeout, maxRetransmits) { this.reusableSendEndPoint = reusableSendEndPoint; }