繁体   English   中英

HttpWebRequest如何处理(过早)关闭底层TCP连接?

[英]HttpWebRequest How to handle (premature) closure of underlying TCP connection?

在使用.NET的HttpWebRequest类调用远程服务器(特别是REST Web服务)时,我很难搞清楚是否有办法处理潜在的连接问题。 从我的调查来看,WebClient类的行为是相同的,这是有点预期的,因为它似乎只提供了一个更简单的HttpWebRequest接口。

出于模拟目的,我编写了一个非常简单的HTTP服务器,它不符合HTTP 1.1 RFC。 它的作用是接受客户端连接,然后发送适当的HTTP 1.1头和“Hello World!”。 有效负载返回客户端并关闭套接字,服务器端接受客户端连接的线程如下所示:

    private const string m_defaultResponse = "<html><body><h1>Hello World!</h1></body></html>";
    private void Listen()
    {
        while (true)
        {
            using (TcpClient clientConnection = m_listener.AcceptTcpClient())
            {
                NetworkStream stream = clientConnection.GetStream();
                StringBuilder httpData = new StringBuilder("HTTP/1.1 200 OK\r\nServer: ivy\r\nContent-Type: text/html\r\n");
                httpData.AppendFormat("Content-Length: {0}\r\n\r\n", m_defaultResponse.Length);
                httpData.AppendFormat(m_defaultResponse);

                Thread.Sleep(3000); // Sleep to simulate latency

                stream.Write(Encoding.ASCII.GetBytes(httpData.ToString()), 0, httpData.Length);

                stream.Close();

                clientConnection.Close();
            }
        }
    }

由于HTTP 1.1 RFC声明HTTP 1.1默认保持连接活动,并且服务器必须发送“Connection:Close”响应头,如果它想要关闭连接,这是客户端的意外行为。 客户端以下列方式使用HttpWebRequest:

    private static void SendRequest(object _state)
    {
        WebResponse resp = null;

        try
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://192.168.0.32:7070/asdasd");
            request.Timeout = 50 * 1000;

            DateTime requestStart = DateTime.Now;
            resp = request.GetResponse();
            TimeSpan requestDuration = DateTime.Now - requestStart;

            Console.WriteLine("OK. Request took: " + (int)requestDuration.TotalMilliseconds + " ms.");
        }
        catch (WebException ex)
        {
            if (ex.Status == WebExceptionStatus.Timeout)
            {
                Console.WriteLine("Timeout occurred");
            }
            else
            {
                Console.WriteLine(ex);
            }
        }
        finally
        {
            if (resp != null)
            {
                resp.Close();
            }

            ((ManualResetEvent)_state).Set();
        }
    }

上述方法通过ThreadPool.QueueUserWorkItem(waitCallback,stateObject)排队。 ManualResetEvent用于控制排队行为,以便整个线程池不会被等待任务填满(因为HttpWebRequest隐式使用工作线程,因为它在内部异步运行以实现超时功能)。

所有这一切的问题是,一旦HttpWebRequest的底层ServicePoint的所有连接都“用完”(即由远程服务器关闭),就不会打开新的连接。 如果ServicePoint的ConnectionLeaseTimeout设置为低值(10秒)也无关紧要。 一旦系统进入此状态,它将不再正常运行,因为它不会自动重新连接,并且所有后续HttpWebRequests都将超时。 现在的问题是,如果有办法通过某种方式在某些条件下销毁ServicePoint或关闭底层连接来解决这个问题(我对ServicePoint.CloseConnectionGroup()没有任何好运,那么该方法在如何解释方面也很简单正确使用它)。

有谁知道我怎么能解决这个问题?

我根据这里的一些想法提出的解决方案是自己管理连接。 如果将唯一的ConnectionGroupName分配给WebRequest(例如Guid.NewGuid()。ToString()),则将在ServicePoint中为请求创建具有一个连接的新连接组。 请注意,此时不再有连接限制,因为.NET限制每个连接组而不是每个ServicePoint,因此您必须自己处理它。 您将需要重用连接组,以便重用与KeepAlive的现有连接,但是如果发生WebException异常,则应该销毁请求的连接组,因为它可能是陈旧的。 这样的事情(为每个主机名创建一个新实例):

public class ConnectionManager {
    private const int _maxConnections = 4;

    private Semaphore _semaphore = new Semaphore(_maxConnections, _maxConnections);
    private Stack<string> _groupNames = new Stack<string>();

    public string ObtainConnectionGroupName() {
        _semaphore.WaitOne();
        return GetConnectionGroupName();
    }

    public void ReleaseConnectionGroupName(string name) {
        lock (_groupNames) {
            _groupNames.Push(name);
        }
        _semaphore.Release();
    }

    public string SwapForFreshConnection(string name, Uri uri) {
        ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
        servicePoint.CloseConnectionGroup(name);
        return GetConnectionGroupName();
    }

    private string GetConnectionGroupName() {
        lock (_groupNames) {
            return _groupNames.Count != 0 ? _groupNames.Pop() : Guid.NewGuid().ToString();
        }
    }
}

这是一个可怕的黑客,但它的确有效。 如果您发现连接卡住了,请定期打电话。

    static public void SetIdle(object request)
    {
        MethodInfo getConnectionGroupLine = request.GetType().GetMethod("GetConnectionGroupLine", BindingFlags.Instance | BindingFlags.NonPublic);
        string connectionName = (string)getConnectionGroupLine.Invoke(request, null);

        ServicePoint servicePoint = ((HttpWebRequest)request).ServicePoint;
        MethodInfo findConnectionGroup = servicePoint.GetType().GetMethod("FindConnectionGroup", BindingFlags.Instance | BindingFlags.NonPublic);
        object connectionGroup;
        lock (servicePoint)
        {
            connectionGroup = findConnectionGroup.Invoke(servicePoint, new object[] { connectionName, false });
        }

        PropertyInfo currentConnections = connectionGroup.GetType().GetProperty("CurrentConnections", BindingFlags.Instance | BindingFlags.NonPublic);
        PropertyInfo connectionLimit = connectionGroup.GetType().GetProperty("ConnectionLimit", BindingFlags.Instance | BindingFlags.NonPublic);

        MethodInfo disableKeepAliveOnConnections = connectionGroup.GetType().GetMethod("DisableKeepAliveOnConnections", BindingFlags.Instance | BindingFlags.NonPublic);

        if (((int)currentConnections.GetValue(connectionGroup, null)) ==
            ((int)connectionLimit.GetValue(connectionGroup, null)))
        {
            disableKeepAliveOnConnections.Invoke(connectionGroup, null);
        }

        MethodInfo connectionGoneIdle = connectionGroup.GetType().GetMethod("ConnectionGoneIdle", BindingFlags.Instance | BindingFlags.NonPublic);
        connectionGoneIdle.Invoke(connectionGroup, null);
    }

这是我的建议。 我没有测试过。 改变reference.cs

    protected override WebResponse GetWebResponse(WebRequest request)
    {
        try
        {
            return base.GetWebResponse(request);
        }
        catch (WebException)
        {
            HttpWebRequest httpWebRequest = request as HttpWebRequest;
            if (httpWebRequest != null && httpWebRequest.ServicePoint != null)
                httpWebRequest.ServicePoint.CloseConnectionGroup(httpWebRequest.ConnectionGroupName);

            throw;
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM