1. 程式人生 > >[瘋狂Java]代理伺服器:Proxy(代理連線)、ProxySelector(自動代理選擇器)、預設代理選擇器

[瘋狂Java]代理伺服器:Proxy(代理連線)、ProxySelector(自動代理選擇器)、預設代理選擇器

1. 代理伺服器:

    1) 英文就叫Proxy,即代替使用者去連線想要的網站並獲取資訊;

    2) 一般主流的商業瀏覽器都提供代理的功能,即你可以先設定一個代理伺服器,那麼接下來所有的上網都是通過該指定的代理伺服器完成;

    3) 一旦設定好了代理伺服器,那麼你之後所有的上網行為都是通過代理伺服器完成的:

        i. 你輸入一個網址並回車後,會先連線代理伺服器;

        ii. 然後代理伺服器幫你開啟那個網址並獲取你想要的資訊,然後將資訊返回給你;

        iii. 即代理伺服器幫你上網,幫你下載,最後將結果返回給你;

    4) 為什麼需要代理伺服器:

        i. 處於特殊安全原因需要對外隱藏自己的IP,代理伺服器對外只能暴露代理伺服器本身的IP,但是可以隱藏你的IP;

        ii. 訪問國內特定單位、內部團體的資料(也是i.的一種特殊情況);

        iii. 代理伺服器可以提供快取,大大提高訪問速度(即你訪問一個資源的時候,如果之前有其他人通過Proxy訪問過了,那麼Porxy就會把該資源直接放入自己的快取中,下次再有使用者想要獲取就直接從快取中拿而不是去連線這個遠端資源了;

!!這樣的例子很多,比如迅雷下載裡面的離線下載/取回本地,之所以速度那麼快是因為迅雷伺服器已經將該資源儲存在伺服器的快取中了,你下載的時候並不是直接連線遠端資源,而是直接從迅雷伺服器取回本地,而迅雷可以提供最大頻寬接入的服務;

!!搜尋引擎提供的網頁,你直接開啟詞條進入網頁其實進入的並不是遠端連線的網頁,搜尋引擎將這些蒐集到的網頁暫時儲存在自己的伺服器裡,你其實是直接從搜尋引擎伺服器的資料庫中下載到這些網頁,因此開啟以及搜尋特別快速,就是這個原因;

        !!只不過搜尋引擎提供一種機制,它可以保證真正的遠端網頁一旦被更新它資料庫中儲存的網頁也可以及時地同步更新;

2. Java對代理伺服器的支援:

    1) Java中就用Proxy類來代表代理伺服器;

    2) 在URL的openConnection就有一個版本,其引數就是Proxy物件,包括Socket等的構造器也都有傳入Proxy物件的過載版本,一旦呼叫了以上方法指定了代理伺服器,那麼接下來的所有對外訪問行為都是通過指定的代理伺服器進行的;

    3) Proxy構造器:Proxy(Proxy.Type type, SocketAddress sa);

         i. Proxy.Type是Proxy類中定義的列舉型別,共有三個值:DIRECT(直連,不使用代理)、HTTP(高階協議代理,HTTP、FTP等的代理)、SOCKS(SOCKS V4、V5的代理);

         ii. 之前的openConnection以及不帶Proxy引數的Socket構造器等都是預設DIRECT直連的,即不使用任何代理的直連;

         iii. sa即代理伺服器的IP地址/埠號;

    4) openConnection時指定代理:URLConnection URL.openConnection(Proxy proxy); // 用指定的代理伺服器代開連線

    5) 指定socket使用代理伺服器:Socket(Proxy proxy);

    6) 示例:openConnection使用代理

public class Test {
	// 代理伺服器的IP和埠
	private final String PROXY_ADDR = "129.82.12.188";
	private final int PROXY_PORT = 3124;
	
	public void init() throws IOException {
		URL url = new URL("http://www.baidu.com"); // 想訪問的網址
		Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT));
		URLConnection conn = url.openConnection(); // 連線時設定代理
		conn.setConnectTimeout(3000);
		
		try(
			Scanner scan = new Scanner(conn.getInputStream());
			PrintStream ps = new PrintStream("index.htm")) {
			while (scan.hasNextLine()) { // 直接將返回的網頁程式碼下載到本地的index.htm檔案中
				String line = scan.nextLine();
				System.out.println(line);
				ps.println(line);
			}
		}
	}
	
	public static void main(String[] args) throws IOException {
		new Test().init();
	}
}

3. ProxySelector——自動代理選擇器:

    1) 上面直接顯示傳入Porxy物件的方法未免有點太繁瑣,主要問題是有時候需要根據不同的網址選擇不同的代理伺服器,比如訪問歐洲的網頁時需要用一種代理伺服器加速,而訪問美洲的網頁時可能用另一個代理更快更好,那麼每次都要這樣顯式地指定代理豈不是很繁瑣嗎?

    2) Java提供了一個抽象類ProxySelector,該類的物件可以根據你要連線的URL自動選擇最合適的代理,但是該類是抽象類,需要自己繼承該類實現個性化、先進化的代理自動選擇;

    3) 選擇器實現完畢後就可以建立選擇器的物件例項,並呼叫ProxySelector類的靜態方法setDefault將該選擇器設定為預設選擇器;那麼之後,所有的訪問網路的行為(openConnection、Socket構造器)等都不需要指定代理,直接自動交給代理選擇器自動根據你要訪問的URL選擇一個代理幫你訪問網路;

    4) ProxySelector:其中有兩個重要的方法需要實現

         i. List<Proxy> select(URI uri); // 給定一個URI(URL的父類,但實際中只用URL)返回一個最適合訪問該URI的代理伺服器列表,其中會首選列表的第一個代理,如果不行則用第二個,如果全部不行就會呼叫connectFailed方法處理連線失敗問題

         ii. void connectFailed(URI uri, SocketAddress sa, IOException ioe);

             a. URI是目標網址;

             b. sa是連線失敗的那個Proxy的IP地址;

             c. ioe是連線失敗時所丟擲的異常物件;

    5) 設定預設的代理選擇器:

         i. 當選擇器設計好後就可以創建出選擇器物件;

         ii. 但是建立完物件後該物件並不會起作用,必須要將其設為本程式的預設選擇器才會使其生效(運作);

         iii. 直接呼叫ProxySelector的靜態方法(本質是一個框架函式)setDefault將該選擇器註冊給框架(即設定為預設選擇器);

         iv. 之後選擇器就生效了,並開始運作;

         v. 方法原型:static void ProxySelector.setDefault(ProxySelector ps);

    6) 示例:

public class Test {
	
	private final String PROXY_ADDR = "139.82.12.188";
	private final int PROXY_PORT = 3124;
	
	public void init() {
		ProxySelector.setDefault(new ProxySelector() {

			@Override
			public List<Proxy> select(URI uri) { // 預設任何URI都返回一個代理
				// TODO Auto-generated method stub
				// return null;
				List<Proxy> list = new ArrayList<>();
				list.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT)));
				return list;
			}

			@Override
			public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { // 簡單地列印資訊
				// TODO Auto-generated method stub
				System.out.println("無法連線到代理!");
			}
			
		});
		
		// ... 正常的訪問網路
	}
	
	public static void main(String[] args) throws IOException {
		new Test().init();
	}
}


4. 系統預設的代理選擇器:

    1) 每個Java程式啟動時都會得到一個跟該程式有關的系統屬性表(可以看做是該程式獨有的環境變數);

    2) 該屬性表中記錄有該Java程式訪問網路預設使用的代理(http代理、ftp代理、socks代理等),只不過初始時全部都是空的(即預設所有協議都採用直連,即無代理);

    3) 如果自己設計並設定代理選擇器,其實Java也提供了一個預設實現的代理選擇器,即sun.net.spi.DefaultProxySelector,自己未設定之前使用的就是這個Sun公司提供的預設代理選擇器,而該選擇器的select方法的實現策略就是就是根據給定的URI返回系統屬性表中指定的代理,只不過初始時屬性表中的代理都是為空,因此預設採取的是直連而已;

    4) sun.net.spi.DefaultProxySelector雖然存在並且執行著,但不過該API沒有公開,目的就是防止使用者顯示使用它造成不安全因素,因此如果你想改變預設選擇器對代理的選擇只能通過修改系統屬性表的方式得到想要的代理;

    5) 系統屬性表中的代理屬性:

         i. 代理的屬性名格式為“協議名.XXX”,協議支援http、https、ftp、socks等;

         ii. XXX是代理的資訊,主要有3種:

             a. proxyHost:代理的IP地址

             b. proxyPort:代理伺服器的埠

             c. nonProxyHosts:不需要使用代理就可以訪問的網址(主機),可以指定多個,多個網址之間用|隔開,允許使用萬用字元*表示

          iii. 一條典型的代理屬性(鍵值對):

http.proxyHost: 192.168.10.96 // 該http代理伺服器的IP地址是192.168.10.96

ftp.nonProxyHosts: localhost | 10.10.10.* // localhost(表示本機)以及所有以10.10.10開頭的ftp地址訪問是無需代理,直連就行

    6) 設定系統屬性:

         i. 必須要先獲得系統屬性表物件:static Properties System.getProperties();

         ii. 設定屬性值:synchronized Object Properties.setProperty(String key, String value); // 返回的是修改前的value,如果修改前沒有value則返回null

!!可以看到鍵值都是字串型別的;

    7) 示例:Properties props = System.getProperties(); props.setProperty("socks.proxyHost", "192.168.10.6");

    8) 使用ProxySelector的靜態方法getDefault獲得當前正在使用的代理選擇器:static ProxySelector ProxySelector.getDefault();

!!如果設定過自己設計的選擇器則返回的就是自己設計的那個,如果什麼都沒動就能返回Sun公司沒有公開的sun.net.spi.DefaultProxySelector咯!

    9) 另外一個sun.net.spi.DefaultProxySelector預設選擇器的實現方法connectFailed,其實現策略就是直連,即如果用代理連線失敗的話(比如等了很長時間還是連不上)就不用代理則直連目標網址;