1. 程式人生 > >Java NIO框架Netty教程(十二)

Java NIO框架Netty教程(十二)

寫在前面:對Netty併發問題的測試和解決完全超出了我的預期,想說的東西越來越多。所以才出現這個中篇,也就是說,一定會有下篇。至於問題點的發現,OneCoder也在努力驗證中。

繼續併發的問題。在《Java NIO框架Netty教程(十一)-併發訪問測試(上)》中,我們測試的其實是一種偽併發的情景。底層實際只有一個Channel在運作,不會出現什麼無響應的問題。而實際的併發不是這個意思的,獨立的客戶端,自然就會有獨立的channel。也就是一個服務端很可能和很多的客戶端建立關係,就有很多的Channel,進行了多次的繫結。下面我們來模擬一下這種場景。

/**
 * Netty併發,多connection,socket併發測試
 * 
 * @author lihzh(OneCoder)
 * @alia OneCoder
 * @Blog http://www.coderli.com
 * @date 2012-8-13 下午10:47:28
 */
public class ConcurrencyNettyMultiConnection { public static void main(String args[]) { final ClientBootstrap bootstrap = new ClientBootstrap( new NioClientSocketChannelFactory( Executors.newCachedThreadPool(), Executors.newCachedThreadPool())); // 設定一個處理服務端訊息和各種訊息事件的類(Handler)
bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline(new ObjectEncoder(), new ObjectClientHandler() ); } }); for (int i = 0; i < 3; i++) { bootstrap.connect
(new InetSocketAddress("127.0.0.1", 8000)); } } }

看到這段程式碼,你可能會疑問,這是在做什麼。這個驗證是基於酷殼的文章《Java NIO類庫Selector機制解析(上)》。他是基於Java Selector直接進行的驗證,Netty是在此之上封裝了一層。其實OneCoder也做了Selector層的驗證。

/**
 * Selector開啟埠數量測試
 * 
 * @author lihzh
 * @alia OneCoder
 * @blog http://www.coderli.com
 */
public class ConcurrencySelectorOpen {

	/**
	 * @author lihzh
	 * @alia OneCoder
	 * @throws IOException 
	 * @throws InterruptedException 
	 * @date 2012-8-15 下午1:57:56
	 */
	public static void main(String[] args) throws IOException, InterruptedException {
		for (int i = 0; i < 3; i++) {
			Selector selector = Selector.open();
			SocketChannel channel = SocketChannel.open();
			channel.configureBlocking(false);
			channel.connect(new InetSocketAddress("127.0.0.1", 8000));
			channel.register(selector, SelectionKey.OP_READ);
		}
		Thread.sleep(300000);
	}
}

同樣,通過Process Explorer去觀察端口占用情況,開始跟酷殼大哥的觀察到的效果一樣。當不啟動Server端,也就是不實際跟Server端建立連結的時候,3次selector open,佔用了6個埠。

當啟動Server端,進行繫結的時候,佔用了9個埠

其實,多的三個,就是實際繫結的8000埠。其餘就是酷殼大哥說的內部Loop連結。也就是說,對於我們實際場景,一次連結需要的埠數是3個。作業系統的埠數和資源當然有有限的,也就是說當我們增大這個連結的時候,錯誤必然會發生了。OneCoder嘗試一下3000次的場景,並沒有出現錯誤,不過這麼下去出錯肯定是必然的。

再看看服務端的情況

這個還是直接通過Selector連線的時候的服務端的情況,除了兩個內部迴環埠以外,都是通過監聽的8000的埠與外界通訊,所以,服務端不會有埠限制的問題。不過,也可以看到,如果對連結不控制,服務端也維持大量的連線耗費系統資源!所以,良好的編碼是多麼的重要。

回到我們的主題,Netty的場景。先使用跟Selector測試一樣的場景,單執行緒,一個bootstrap,連續多次connect看看錯誤和端口占用的情況。程式碼也就是開頭提供的那段程式碼。

看看測試結果,

同樣連線後還是佔用9個埠。如果手動呼叫了channle.close()方法,則會釋放與8000連結的埠,也就是變成6個端口占用。 增大連續連線數到10000。

首先沒有報錯,在每次close channel情況下,客戶端端口占用情況如圖(服務端情況類似)。

可見,並沒有像selector那樣無限的增加下去。這正是Nettyworker的巨大作用,幫我們管理這些瑣碎的連結。具體分析,我們留待下章,您也可以自己研究一下。(OneCoder注:如果沒關閉channel,會發現對8000埠的佔用,會迅速增加。系統會很卡。)

調整測試程式碼,用一個執行緒來模擬一個客戶端的請求。

/**
 * Netty併發,多connection,socket併發測試
 * 
 * @author lihzh(OneCoder)
 * @alia OneCoder
 * @Blog http://www.coderli.com
 * @date 2012-8-13 下午10:47:28
 */
public class ConcurrencyNettyMultiConnection {
	
	public static void main(String args[]) {
		final ClientBootstrap bootstrap = new ClientBootstrap(
				new NioClientSocketChannelFactory(
						Executors.newCachedThreadPool(),
						Executors.newCachedThreadPool()));
		// 設定一個處理服務端訊息和各種訊息事件的類(Handler)
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				return Channels.pipeline(new ObjectEncoder(),
						new ObjectClientHandler()
				);
			}
		});
		for (int i = 0; i < 1000; i++) {
			// 連線到本地的8000埠的服務端
			Thread t = new Thread(new Runnable() {
				@Override
				public void run() {
					bootstrap.connect(new InetSocketAddress("127.0.0.1", 8000));
					try {
						Thread.sleep(300000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			t.start();
		}
	}
}

不出所料,跟微博上問我問題的朋友出現的問題應該是一樣的:

		Write: 973
		Write: 974
		八月 17, 2012 9:57:28 下午 org.jboss.netty.channel.SimpleChannelHandler
		警告: EXCEPTION, please implement one.coder.netty.chapter.eight.ObjectClientHandler.exceptionCaught() for proper handling.
		java.net.ConnectException: Connection refused: no further information

這個問題,筆者確實尚未分析出原因,僅從端口占用情況來看,跟前面的測試用例跑出的效果基本類似。應該說明不是埠不足導致的,不過OneCoder尚不敢妄下結論。待研究後,我們下回分解吧:)您有任何想法,也可以提供給我,我來進行驗證。