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

标签 c# networking httpwebrequest

我很难确定在使用 .NET 的 HttpWebRequest 类调用远程服务器(特别是 REST Web 服务)时是否有办法处理潜在的连接问题。根据我的调查,WebClient 类的行为是相同的,这在某种程度上是意料之中的,因为它似乎只为 HttpWebRequest 提供了一个更简单的接口(interface)。

出于模拟目的,我编写了一个非常简单的 HTTP 服务器,它的行为不符合 HTTP 1.1 RFC。它所做的是接受客户端连接,然后发送适当的 HTTP 1.1 header 和“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 默认情况下会保持连接处于事件状态,并且如果服务器想要关闭连接,则它必须发送“连接:关闭”响应 header ,这对于客户端来说是意外的行为。客户端通过以下方式使用 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();
        }
    }
}

关于c# - HttpWebRequest 如何处理(过早)关闭底层 TCP 连接?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1390780/

相关文章:

windows - HttpWebRequest 和 HttpWebResponse 的 C++ 接口(interface)版本

c# - 使用 asp.net 和 C# 从 javascript 调用外部服务器上的 webservice

c# - 如何使用当前 Windows 登录用户对 UWP 应用程序进行身份验证

java - 如何在java中获取我的机器的IP地址?

c# - 将日期转换为日期时间格式c#

ssl - TLS/SSL 端点前的 TCP 负载平衡器

java - 连接到 NTP 服务器始终失败 (Java)

http - HybridHttpOrThreadLocalScoped 和 HttpContextScoped 有什么区别

c# - .Net 中的 Midi 实现

c# - 找不到类型 Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView 的 Blazor 默认构造函数