https://blog.williamgates.net/2006/07/aspdotnet-through-multi-proxy/
首先,通过普通的匿名透明代理的方法,是直接使用Socket发送GET命令,只不过与GET普通网站稍有不同罢了
直接访问:
1 2 3 4 5 6 7 8 9 10 11 | GET / HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, */* Accept-Language: zh-cn UA-CPU: x86 Accept-Encoding: gzip, deflate If-Modified-Since: Thu, 06 Jul 2006 15:39:53 GMT If-None-Match: "1172d9-381c-44ad2ec9" User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: www.redhat.com Connection: Keep-Alive Cookie: s_vi=[CS]v1|44AAA05400004577-A170C060000008A[CE]; Apache=61.147.159.196.23241152032846747 |
通过本机CCproxy:
1 2 3 4 5 6 7 8 | GET / HTTP/1.0 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, */* Accept-Language: zh-cn UA-CPU: x86 Proxy-Connection: Keep-Alive User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727) Host: www.redhat.com Cookie: s_vi=[CS]v1|44AAA05400004577-A170C060000008A[CE]; Apache=61.147.159.196.23241152032846747 |
可以看出来,只要给代理服务器发送正确的请求地址即可,不需要程序上特殊的变化。
事实上,对于不使用代理服务器的场合,你可以直接向某HTTP服务器发送GET /,而这在使用代理时不行,需要说明详细地址或者标明Host: www.redhat.com对于两层代理,就有所不同了
首先要给sproxy发送一个CONNECT指令,令其去连接某公众网代理(这里称为proxy2),然后通过这个Socket连接发送GET指令给proxy2。这里对sproxy(以后称proxy1)有一个要求,即必须支持CONNECT,也就是说必须是一个HTTP Tunnel,或者说支持HTTPS。 由于一开始设置上的问题,我本以为返回HTTP 1.0的CCProxy不支持这样的方式,因为据说HTTP 1.0就不能支持HTTPS,连接一次,发送了数据一定会断开(一开始的测试中的确如此)。后来查阅资料才知道,HTTP 1.0只是默认不使用Connect: Keep-Alive的参数,事实上也是支持的,只要置上这个参数即可。 但实际使用中,发现不加上这个参数也是可以的(我对proxy1没有使用这个参数,可能是.net下的Socket类自动加上了)。经过连续数天的测试,一直不能通过,问题集中在,给proxy2发送一次CONNECT命令,Socket就会断开,无法收到数据。这是典型的不支持HTTP Tunnel的表现,但是我一直怀疑是CCproxy和学校代理服务器的问题,没有发现根源所在。
因为我们访问sproxy需要用户名密码验证,我不得已才使用了CCProxy。后来,我又安装了一个代理服务器软件,发现其中只要使用“代理嵌套”(CCproxy叫作二级代理)的时候都需要HTTPS,我突然想到,会不会需要在CCproxy中设置? 于是,在CCproxy的设置-高级-二级代理中把代理类型设为HTTPS,使用proxy1做上级代理,果然成功。事实上,直接连接学校代理应该也是可以的,只是我还不知道如何在代理请求中发送用户验证信息(需要将验证信息截断为多个数据包,不能直接发送)。代码如下,调试通过并成功运行了十多天了:
后台一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | public string Get_Socket_Request_uip(string ip) { Encoding ASCII = Encoding.Default; //给Proxy2发送命令 string Con = "CONNECT " + ip + " HTTP/1.1 Connection: Keep-Alive "; string Get = "HEAD http://www.RedHat.com/ HTTP/1.1 Host: www.RedHat.com Pragma: no-cache Connection: Close "; Byte[] ByteCon = ASCII.GetBytes(Con); Byte[] ByteGet = ASCII.GetBytes(Get); Byte[] RecvBytes = new Byte[256]; Byte[] RecvBytes2 = new Byte[256]; string strRetPage = null; string strRetCon = null; //建立Socket Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //连接本地CCProxy IPAddress hostip = IPAddress.Parse("127.0.0.1"); IPEndPoint ipend = new IPEndPoint(hostip, 808); //设置超时,超过了就证明这个代理无效 s.SendTimeout = 20000; s.ReceiveTimeout = 20000; //这句话就是socket的Keep-Alive,我没有使用 //s.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true); s.Connect(ipend); try { //发送CONNECT命令 s.Send(ByteCon, ByteCon.Length, 0); Int32 bytes = s.Receive(RecvBytes, RecvBytes.Length, 0); strRetCon = strRetCon + ASCII.GetString(RecvBytes, 0, bytes); if (strRetCon.Contains("200")) { s.Send(ByteGet, ByteGet.Length, 0); Int32 bytes2 = s.Receive(RecvBytes2, RecvBytes2.Length, 0); strRetPage = strRetPage + ASCII.GetString(RecvBytes2, 0, bytes2); //本来这里会循环接收数据(本来发送的不是HEAD而是GET,取整个页面),后来发现有时候这会导致断线,就去掉了,只用HEAD命令取返回的HTTP Head /*while (bytes2 > 0) { bytes2 = s.Receive(RecvBytes2, RecvBytes2.Length, 0); strRetPage = strRetPage + ASCII.GetString(RecvBytes2, 0, bytes2); }*/ } } catch { break; } finally { s.Shutdown(SocketShutdown.Both); s.Close(); } return strRetPage; } |
前台的调用方法(已经使用证则表达式在别处的网页中抓取到待检测的代理列表,在matches数组中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public void TestProxy() { int r = 0; try { foreach (Match i in matches) { string ip = i.Result("${ip}"); string port = i.Result("${port}"); try { //这里的hc是后台那个类,myhttpclient string result = hc.Get_Socket_Request_uip(ip + ":" + port); if (result.Contains("200 OK")) { checkedproxy[r] = ip + ":" + port; r = r + 1; } } catch (TimeoutException) { continue; } catch { continue; } } } catch { break; } } |
WebService代码就不给出了,就是调用这个方法而已。
大家应该可以看出,这里实际上通过了三层代理(CCproxy,proxy1,proxy2),所以,如果要通过n层代理,方法也是一样的,只要一层层的CONNECT下去就行,但要求前n-1层代理都要支持HTTP tunnel。 另外,在实际部署中使用这个程序,需要在Web.config中的system.web项下添加一行 1 | <httpruntime executionTimeout="600"/> |
否则超过10项的代理列表,几乎一定会超时的