C# 绑定IP地址只是第一次起作用

C# 绑定IP地址只是第一次起作用,c#,binding,ip-address,multihomed,C#,Binding,Ip Address,Multihomed,我想从服务器上的一个可用IP地址发出web请求,因此我使用此类: public class UseIP { public string IP { get; private set; } public UseIP(string IP) { this.IP = IP; } public HttpWebRequest CreateWebRequest(Uri uri) { ServicePoint servicePoi

我想从服务器上的一个可用IP地址发出web请求,因此我使用此类:

public class UseIP
{
    public string IP { get; private set; }

    public UseIP(string IP)
    {
        this.IP = IP;
    }

    public HttpWebRequest CreateWebRequest(Uri uri)
    {
        ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
        servicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
        return WebRequest.Create(uri) as HttpWebRequest;
    }

    private IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
    {
        IPAddress address = IPAddress.Parse(this.IP);
        return new IPEndPoint(address, 0);
    }
}
然后:


但是这个解决方案第一次就奏效了

我对您的示例做了一些更改,使其在我的机器上运行:

public HttpWebRequest CreateWebRequest(Uri uri)
{
    HttpWebRequest wr = WebRequest.Create(uri) as HttpWebRequest;
    wr.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
    return wr;
}
我这样做是因为:

  • 我认为对
    FindServicePoint
    的调用实际上使用“默认”ip对指定的URI执行请求,甚至不调用绑定委托。至少在我的机器中,
    BindIPEndPointDelegate
    没有以您提供的方式被调用(我知道请求是因为我没有设置代理,并且得到了代理身份验证错误)
  • 在的文档中,它指出“如果该主机和方案存在现有ServicePoint对象,ServicePointManager对象将返回现有ServicePoint对象;否则,ServicePointManager对象将创建新的ServicePoint对象”,如果URI相同,它可能会始终返回相同的ServicePoint对象(可能解释了为什么后续调用发生在同一端点中)
  • 通过这种方式,我们可以确保,即使已经请求了URI,它也将使用所需的IP,而不是使用
    ServicePointManager
    以前的一些“缓存”
  • 理论:

    HttpWebRequest依赖于基础ServicePoint。ServicePoint表示与URL的实际连接。这与浏览器在请求之间保持与URL的连接打开并重用该连接的方式大致相同(以消除每次请求打开和关闭连接的开销),ServicePoint对HttpWebRequest执行相同的功能

    我认为在每次使用HttpWebRequest时都不会调用您为ServicePoint设置的BindIPEndPointDelegate,因为ServicePoint正在重用连接。如果您可以强制关闭连接,那么对该URL的下一次调用应该会导致ServicePoint需要再次调用BindIPEndPointDelegate

    不幸的是,ServicePoint接口似乎没有提供直接强制关闭连接的功能

    两种解决方案(每个方案的结果略有不同)

    1) 对于每个请求,设置HttpWebRequest.KeepAlive=false。在我的测试中,这导致绑定委托在每个请求中被逐个调用

    2) 将ServicePoint ConnectionLeaseTimeout属性设置为零或某个小值。这将定期强制调用绑定委托(而不是每个请求一对一)

    从:

    可以使用此属性确保ServicePoint对象的 活动连接不会无限期保持打开状态。这个物业是 适用于应断开连接并 定期重新建立,例如负载平衡场景

    默认情况下,当请求的KeepAlive为true时,MaxIdleTime 属性设置由于以下原因关闭ServicePoint连接的超时: 不活动。如果ServicePoint具有活动连接,则MaxIdleTime 无效,连接将无限期保持打开状态

    当ConnectionLeaseTimeout属性设置为除 -1,并在指定的时间过后,通过将KeepAlive设置为,在为请求提供服务后关闭活动的ServicePoint连接 这个请求是错误的

    设置此值会影响ServicePoint对象管理的所有连接

    public class UseIP
    {
        public string IP { get; private set; }
    
        public UseIP(string IP)
        {
            this.IP = IP;
        }
    
        public HttpWebRequest CreateWebRequest(Uri uri)
        {
            ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
            servicePoint.BindIPEndPointDelegate = (servicePoint, remoteEndPoint, retryCount) =>
            {
                IPAddress address = IPAddress.Parse(this.IP);
                return new IPEndPoint(address, 0);
            };
    
            //Will cause bind to be called periodically
            servicePoint.ConnectionLeaseTimeout = 0;
    
            HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
            //will cause bind to be called for each request (as long as the consumer of the request doesn't set it back to true!
            req.KeepAlive = false;
    
            return req;
        }
    }
    
    每个请求都会调用绑定委托中的以下(基本)测试结果:

    static void Main(string[] args)
        {
            //Note, I don't have a multihomed machine, so I'm not using the IP in my test implementation.  The bind delegate increments a counter and returns IPAddress.Any.
            UseIP ip = new UseIP("111.111.111.111");
    
            for (int i = 0; i < 100; ++i)
            {
                HttpWebRequest req = ip.CreateWebRequest(new Uri("http://www.yahoo.com"));
                using (WebResponse response = req.GetResponse())
                {
                }
            }
    
            Console.WriteLine(string.Format("Req: {0}", UseIP.RequestCount));
            Console.WriteLine(string.Format("Bind: {0}", UseIP.BindCount));
        }
    
    static void Main(字符串[]args)
    {
    //请注意,我没有多宿主计算机,因此我在测试实现中没有使用IP。绑定委托递增一个计数器并返回IPAddress.Any。
    UseIP ip=新的UseIP(“111.111.111.111”);
    对于(int i=0;i<100;++i)
    {
    HttpWebRequest req=ip.CreateWebRequest(新Uri(“http://www.yahoo.com"));
    使用(WebResponse-response=req.GetResponse())
    {
    }
    }
    WriteLine(string.Format(“Req:{0}”,UseIP.RequestCount));
    WriteLine(string.Format(“Bind:{0}”,UseIP.BindCount));
    }
    
    问题可能是代理在每次新请求时都被重置。请尝试以下内容:

    //servicePoint.BindIPEndPointDelegate = null; // Clears all delegates first, for testing
    servicePoint.BindIPEndPointDelegate += delegate
        {
            var address = IPAddress.Parse(this.IP);
            return new IPEndPoint(address, 0);
        };
    

    此外,据我所知,端点是缓存的,因此在某些情况下,即使清除委托也可能不起作用,而且它们可能会被重置。最坏情况下,您可以卸载/重新加载应用程序域。

    我喜欢这个新类UseIP

    关于保护自己免受IPv4/IPv6差异的影响,有一点值得商榷

    唯一需要更改的是Bind方法如下:

    private IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
    {
        if ((null != IP) && (IP.AddressFamily == remoteEndPoint.AddressFamily))
            return new IPEndPoint(this.IP, 0);
        if (AddressFamily.InterNetworkV6 == remoteEndPoint.AddressFamily)
            return new IPEndPoint(IPAddress.IPv6Any, 0);
        return new IPEndPoint(IPAddress.Any, 0);
    }
    
    re:正在多次调用的绑定方法

    对我来说,有效的方法是在添加代理链接之前删除它

    ServicePoint servicePoint = ServicePointManager.FindServicePoint(uri);
    servicePoint.BindIPEndPointDelegate -= this.Bind;   // avoid duplicate calls to Bind
    servicePoint.BindIPEndPointDelegate += this.Bind;
    
    我还喜欢缓存UseIP对象的想法。因此,我将这个静态方法添加到UseIP类中

    private static Dictionary<IPAddress, UseIP> _eachNIC = new Dictionary<IPAddress, UseIP>();
    public static UseIP ForNIC(IPAddress nic)
    {
        lock (_eachNIC)
        {
            UseIP useIP = null;
            if (!_eachNIC.TryGetValue(nic, out useIP))
            {
                useIP = new UseIP(nic);
                _eachNIC.Add(nic, useIP);
            }
            return useIP;
        }
    }
    
    private static Dictionary\u eachNIC=new Dictionary();
    公共静态使用IP ForNIC(IP地址nic)
    {
    锁(_eachNIC)
    {
    UseIP-UseIP=null;
    如果(!\u每个技术测试值(nic,out useIP))
    {
    useIP=新的useIP(nic);
    _添加(nic、useIP);
    }
    返回useIP;
    }
    }
    
    是,我想快速更改我的IP地址。我应该采取什么方法?你有什么异常吗?你可以尝试只在第一次绑定时将其保存在静态或实例变量中?@alexD:没有。只有第一个绑定的IP可以工作。
    UseIP
    类的后续实例将使用相同的IP地址。奖金将授予“频繁更改web请求的IP地址”的解决方案。类似于
    UseIP
    class的东西很有效。我以前也试过。如果您快速更改IP地址,您将发现
    private static Dictionary<IPAddress, UseIP> _eachNIC = new Dictionary<IPAddress, UseIP>();
    public static UseIP ForNIC(IPAddress nic)
    {
        lock (_eachNIC)
        {
            UseIP useIP = null;
            if (!_eachNIC.TryGetValue(nic, out useIP))
            {
                useIP = new UseIP(nic);
                _eachNIC.Add(nic, useIP);
            }
            return useIP;
        }
    }