Exemple #1
0
        /// <summary>
        /// Send QoS requests to the given endpoint.
        /// </summary>
        /// <param name="remoteEndpoint">Server that QoS requests should be sent to</param>
        /// <param name="expireTime">When to stop trying to send requests</param>
        /// <param name="result">Results from the send side of the check (packets sent)</param>
        /// <returns>
        /// errorcode - the last error code generated (if any).  0 indicates no error.
        /// </returns>
        private int SendQosRequests(NetworkEndPoint remoteEndpoint, ushort identifier, DateTime expireTime, ref QosResult result)
        {
            QosRequest request = new QosRequest
            {
                Title      = m_TitleBytesUtf8.ToArray(),
                Identifier = identifier
            };

#if UNITY_EDITOR || DEVELOPMENT_BUILD
            Debug.Log($"Identifier for this QoS check is: 0x{request.Identifier:X4}");
#endif

            // Send all requests.
            result.RequestsSent = 0;
            while (result.RequestsSent < RequestsPerEndpoint && !QosHelper.ExpiredUtc(expireTime))
            {
                request.Timestamp = (ulong)(DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond);
                request.Sequence  = (byte)result.RequestsSent;

                int errorcode = 0;
                int sent      = 0;
                (sent, errorcode) = request.Send(m_Socket, ref remoteEndpoint, expireTime);
                if (errorcode != 0)
                {
                    Debug.LogError($"Send returned error code {errorcode}, can't continue");
                    return(errorcode);
                }
                else if (sent != request.Length)
                {
                    Debug.LogWarning($"Sent {sent} of {request.Length} bytes, ignoring this request");
                    ++result.InvalidRequests;
                }
                else
                {
                    ++result.RequestsSent;
                }
            }

            return(0);
        }
Exemple #2
0
        public void Execute()
        {
            if (m_QosServers.Length == 0)
            {
                return;    // Nothing to do.
            }
            m_JobExpireTimeUtc = DateTime.UtcNow.AddMilliseconds(TimeoutMs);

            // Create the local socket
            int errorcode = 0;

            (m_Socket, errorcode) = CreateAndBindSocket();
            if (m_Socket == -1 || errorcode != 0)
            {
                // Can't run the job
                Debug.LogError("Failed to create and bind the local socket for QoS Check");
            }
            else
            {
                m_Identifier = (ushort)new Random().Next(ushort.MinValue, ushort.MaxValue);
                for (int i = 0; i < m_QosServers.Length; ++i)
                {
                    QosResult         result = QosResults[i];
                    InternalQosServer server = m_QosServers[i];

                    if (QosHelper.ExpiredUtc(m_JobExpireTimeUtc))
                    {
                        Debug.LogWarning($"Ran out of time to finish remaining QoS Check for endpoint {i}.");
                        break;
                    }

                    // If we've already visited this server, just copy those results here.
                    if (QosServerVisited(server.Id))
                    {
                        if (TryCopyResult(server.Id, ref result) == false)
                        {
                            Debug.LogError($"Visited server must have a previous result available");
                            break;
                        }
                    }
                    else if (DateTime.UtcNow > server.BackoffUntilUtc) // Only contact this server if we are allowed
                    {
                        // For each iteration of the loop, give the remaining endpoints an equal fraction of the remaining
                        // overall job time.  For example if there are five endpoints that each get 1000ms (5000ms total),
                        // and the first endpoint finishes in 200ms, the remaining endpoints will get 1200ms to complete
                        // (4800ms remaining / 4 endpoints = 1200ms/endpoint).
                        double   allottedTimeMs = QosHelper.RemainingUtc(m_JobExpireTimeUtc).TotalMilliseconds / (m_QosServers.Length - i);
                        DateTime startTimeUtc   = DateTime.UtcNow;
                        DateTime expireTimeUtc  = DateTime.UtcNow.AddMilliseconds(allottedTimeMs);

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                        Debug.Log($"QoS Check {i} gets {(expireTimeUtc - DateTime.UtcNow).TotalMilliseconds:F0}ms to complete.");
#endif

                        ++m_Identifier;
                        int err = SendQosRequests(server.RemoteEndpoint, m_Identifier, expireTimeUtc, ref result);
                        if (err != 0)
                        {
                            Debug.LogError($"Error {err} sending QoS requests.  Will attempt to receive responses anyway.");
                        }
                        err = RecvQosResponses(server.RemoteEndpoint, m_Identifier, expireTimeUtc, ref result);
                        if (err != 0)
                        {
                            Debug.LogError($"Error {err} receiving QoS responses. Will attempt to continue anyway.");
                        }

                        Debug.Log($"Received {result.ResponsesReceived}/{result.RequestsSent} responses from endpoint {i} in {(DateTime.UtcNow - startTimeUtc).TotalMilliseconds:F0}ms");

                        // Mark this server as visited

                        SetQosServerVisited(server.Id);
                    }
                    else
                    {
                        Debug.LogWarning($"Did not contact endpoint {i} due to backoff restrictions.");
                    }

                    // Save the result (even if we didn't contact the server)
                    QosResults[i] = result;
                }
            }

            NativeBindings.network_close(ref m_Socket, ref errorcode);
        }
Exemple #3
0
        /// <summary>
        /// Receive QoS responses from the given endpoint
        /// </summary>
        /// <param name="remoteEndpoint">Where to expect responses to come from</param>
        /// <param name="identifier">Identifier that will accompany a valid response</param>
        /// <param name="expireTimeUtc">How long to wait for responses</param>
        /// <param name="result">Results from the receive side of the check (packets received, latency, packet loss)</param>
        /// <returns>
        /// errorcode - the last error code (if any). 0 means no error.
        /// </returns>
        private int RecvQosResponses(NetworkEndPoint remoteEndpoint, ushort identifier, DateTime expireTimeUtc, ref QosResult result)
        {
            if (result.RequestsSent == 0)
            {
                return(0); // Not expecting any responses
            }

            NativeArray <int> responseLatency = new NativeArray <int>((int)result.RequestsSent, Allocator.Temp);

            for (int i = 0; i < responseLatency.Length; ++i)
            {
                responseLatency[i] = -1;
            }

            QosResponse response  = new QosResponse();
            int         errorcode = 0;

            while (result.ResponsesReceived < result.RequestsSent && !QosHelper.ExpiredUtc(expireTimeUtc))
            {
                errorcode = 0;
                int received = 0;
                (received, errorcode) = response.Recv(m_Socket, remoteEndpoint, expireTimeUtc);
                if (received == -1)
                {
                    Debug.LogError($"Invalid or no response received (errorcode = {errorcode})");
                }
                else if (!response.Verify())
                {
                    Debug.LogWarning("Ignoring invalid QosResponse");
                    ++result.InvalidResponses;
                }
                else if (response.Identifier != identifier)
                {
                    Debug.LogWarning($"Identifier 0x{response.Identifier:X4} != expected identifier 0x{identifier:X4}; ignoring...");
                    ++result.InvalidResponses;
                }
                else
                {
                    if (response.Sequence >= result.RequestsSent) // Sequence can't be more than number of requests sent
                    {
                        Debug.LogWarning($"Ignoring response with sequence {response.Sequence} that is higher than max sequence expected");
                        ++result.InvalidResponses;
                    }
                    else if (responseLatency[response.Sequence] == -1)
                    {
                        responseLatency[response.Sequence] = response.LatencyMs;
                        ++result.ResponsesReceived;
                    }
                    else
                    {
                        Debug.Log($"Duplicate response {response.Sequence} received for QosCheck identifier 0x{response.Identifier:X4}");
                        ++result.DuplicateResponses;
                    }

                    // Determine if we've had flow control applied to us.  If so, save the most significant result based
                    // on the unit count.  In this version, both Ban and Throttle have the same result: client back-off.
                    var fc = response.ParseFlowControl();
                    if (fc.type != FcType.None && fc.units > result.FcUnits)
                    {
                        result.FcType  = fc.type;
                        result.FcUnits = fc.units;
                    }
                }
            }

            // Calculate average latency and log results
            result.AverageLatencyMs = 0;
            if (result.ResponsesReceived > 0)
            {
                uint validResponses = 0;
                for (int i = 0, length = responseLatency.Length; i < length; i++)
                {
                    var latency = responseLatency[i];
                    if (latency >= 0)
                    {
                        result.AverageLatencyMs += (uint)latency;
                        validResponses++;
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                        Debug.Log($"Received response {i} for QosCheck identifier 0x{identifier:X4} with latency {latency}ms");
#endif
                    }
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    else
                    {
                        Debug.LogWarning($"Failed to receive response {i} for QosCheck identifier 0x{identifier:X4}");
                    }
#endif
                }

                result.AverageLatencyMs /= validResponses;
            }

            result.UpdatePacketLoss();
            responseLatency.Dispose();
            return(errorcode);
        }
        /// <summary>
        /// Receive a QosResponse if one is available
        /// </summary>
        /// <param name="socket">Native socket descriptor</param>
        /// <param name="endpoint">Remote endpoint from which to receive the response</param>
        /// <param name="expireTimeUtc">When to stop waiting for a response</param>
        /// <returns>
        /// (received, errorcode) where received is the number of bytes received and errorcode is the error code if any.
        /// 0 means no error.
        /// </returns>
        public (int received, int errorcode) Recv(long socket, NetworkEndPoint endpoint, DateTime expireTimeUtc)
        {
            m_PacketLength = 0;
            int             errorcode = 0;
            int             received  = -1;
            NetworkEndPoint remote    = NetworkEndPoint.AnyIpv4;

            unsafe
            {
                fixed(void *pMagic = &m_Magic, pVerAndFlow = &m_VerAndFlow, pSequence = &m_Sequence, pIdentifier =
                      &m_Identifier, pTimestamp = &m_Timestamp)
                {
                    var iov = stackalloc network_iovec[5];

                    iov[0].buf = pMagic;
                    iov[0].len = sizeof(byte);

                    iov[1].buf = pVerAndFlow;
                    iov[1].len = sizeof(byte);

                    iov[2].buf = pSequence;
                    iov[2].len = sizeof(byte);

                    iov[3].buf = pIdentifier;
                    iov[3].len = sizeof(ushort);

                    // Everything below here is user-specified data and not part of the QosResponse header

                    iov[4].buf = pTimestamp;
                    iov[4].len = sizeof(ulong);

                    // TODO: May need to introduce artificial latency here to prevent spinning on WouldBlock()
                    while (!QosHelper.ExpiredUtc(expireTimeUtc))
                    {
                        errorcode = 0;
                        received  = NativeBindings.network_recvmsg(socket, iov, 5, ref remote, ref errorcode);

                        // If we'd block, retry.  If we got a response from the wrong endpoint, ignore it and retry.
                        // N.B.: Connecting to loopback at nonstandard (but technically correct) addresses like
                        // 127.0.0.2 will return a remote address of 127.0.0.1, which will cause a mismatch.
                        // Should special-case those, but there is currently no way to get the address information
                        // out of a NetworkEndPoint, so we can't address it.
                        if (received == -1 && QosHelper.WouldBlock(errorcode))
                        {
                            continue;
                        }
                        if (received != -1 && remote != endpoint)
                        {
                            continue;
                        }
                        break; // Got a response, or a non-retryable error
                    }

                    if (received == -1)
                    {
                        Debug.LogError($"network_recvmsg returned {received} with error code {errorcode}");
                        return(received, errorcode);
                    }

                    m_PacketLength = (ushort)received;
                }
            }

            m_LatencyMs = (Length >= MinPacketLen) ? (int)((ulong)(DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond) - m_Timestamp) : -1;
            return(received, errorcode);
        }