初學Java NIO框架Netty(一) 之 Hello

聽朋友介紹這個框架於是學習之

首先非常感謝下面這兩篇博文,很大部分代碼和註釋都參考了他們

http://deep-fish.iteye.com/blog/1819647

http://blog.163.com/linfenliang@126/blog/static/127857195201210744448686/?suggestedreading


沒有廢話了,直接代碼加註釋


HelloServer 服務端

package com.wei.study;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

/**
 * 
 * @author wangwei
 * Jun 7, 2013
 */
public class HelloServer {

	public static void main(String[] args) {
		/*
		 * 基於NIO的用於創建服務器端channel的channelFactory
		 * 這個類利用非阻塞I/O模式引入NIO,這樣可以有效地向多個併發連接提供Channel 
		 * 
		 * 在實現類中傳入了兩個cached線程池
		 * 前面一個代表boss線程,後面一個代表worker線程
		 * 
		 * Boss線程: 
		 * 每個server服務器都會有一個boss線程,每綁定一個InetSocketAddress都會產生一個boss線程,比如:我們開啓了兩個服務器端口80和443,則我們會有兩個boss線程。
		 * 一個boss線程在端口綁定後,會接收傳進來的連接,一旦連接接收成功,boss線程會指派一個worker線程處理連接。 
		 *
		 * Worker線程: 
		 * 一個NioServerSocketChannelFactory會有一個或者多個worker線程。一個worker線程在非阻塞模式下爲一個或多個Channels提供非阻塞 讀或寫 
		 * 
		 * 簡單來說,Boss線程用來接收客戶端請求;worker用來執行非阻塞的IO操作,主要是read,write。
		 */
		ChannelFactory tcpChannelFactory =  new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),Executors.newCachedThreadPool());
		
		//server服務啓動器
		ServerBootstrap bootstrap = new ServerBootstrap(tcpChannelFactory);
		
		//管道工廠 設置處理客戶端消息和消息事件的Handler類
		bootstrap.setPipelineFactory(new ChannelPipelineFactory(){
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				return Channels.pipeline(new HelloServerHandler());
			}
		});
		//開放監聽8000端口代客戶端通信
		bootstrap.bind(new InetSocketAddress(8000));
	}
}


HelloServerHandler 服務處理類

package com.wei.study;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

/**
 * 
 * @author wangwei
 * Jun 7, 2013
 */
public class HelloServerHandler extends SimpleChannelHandler {

	@Override
	//設置連接時觸發事件
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("Hello,I'm server.");
	}
	
	
}


HelloClient 客戶端類

package com.wei.study;

import java.net.InetSocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;

/**
 * 
 * @author wangwei
 * Jun 7, 2013
 */
public class HelloClient {
	
	public static void main(String[] args) {
		//與服務端相同
		ChannelFactory clientSocketChannel = new NioClientSocketChannelFactory(Executors.newCachedThreadPool(),Executors.newCachedThreadPool());
		//與服務端相同
		ClientBootstrap bootstrap = new ClientBootstrap(clientSocketChannel);
		
		//與服務端相同
		bootstrap.setPipelineFactory(new ChannelPipelineFactory(){

			@Override
			public ChannelPipeline getPipeline() throws Exception {
				return Channels.pipeline(new HelloClientHandler());
			}
		});
		
		//連接到IP地址及端口
		bootstrap.connect(new InetSocketAddress("127.0.0.1",8000));
	}
	
}

HelloClient 客戶端處理類

package com.wei.study;

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

/**
 * 
 * @author wangwei
 * Jun 7, 2013
 */
public class HelloClientHandler extends SimpleChannelHandler {
	
	@Override
	//與服務端相同
	public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
			throws Exception {
		System.out.println("Hello World,I'm client");
	}
	
}


-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

最後符上前面提到的一篇博文的原文

Netty多線程處理機制  

技術點描述

本文主要研究NioServerSocketChannelFactory類和NioDatagramChannelFactory類,

以及這兩個類的各自作用。

    由於基於pipelineFactory的線程池的配置已在前一節(Netty中execution包功能詳解)中做了詳細說明,而channelFactory中的線程池的配置並未做說明,本文檔重點闡述channelFactory的線程池配置。NioServerSocketChannelFactory主要應用於TCP協議的數據處理,NioDatagramChannelFactory主要應用於UDP協議的數據處理。

實現方案

簡單介紹一下實現該功能,使用了哪些技術或知識點

參考源碼包

以下是對這兩個類的具體說明以及相關類,重要的常用的方法的說明

  1. NioServerSocketChannelFactory

此類常用於創建TCP的ServerBootstrap時作爲構造方法的參數使用。

調用方式及設置如下:

ChannelFactory TCPCHANNEL_FACTORY = new NioServerSocketChannelFactory(

Executors.newCachedThreadPool(), Executors.newCachedThreadPool());

ServerBootstrap TCPSERVER_BOOTSTRAP = new ServerBootstrap(TCPCHANNEL_FACTORY);

 

基於NIO的用於創建服務器端channelchannelFactory,這個類利用非阻塞I/O模式引入NIO,這樣可以有效地向多個併發連接提供Channel

線程的工作模式:

在此類中,有兩種類型的線程,一種是boss線程,另一種是worker線程

 

Boss線程:

每個server服務器都會有一個boss線程,每綁定一個InetSocketAddress都會產生一個boss線程,比如:我們開啓了兩個服務器端口80和443,則我們會有兩個boss線程。一個boss線程在端口綁定後,會接收傳進來的連接,一旦連接接收成功,boss線程會指派一個worker線程處理連接。

Worker線程:

一個NioServerSocketChannelFactory會有一個或者多個worker線程。一個worker線程在非阻塞模式下爲一個或多個Channels提供非阻塞 讀或寫

線程的生命週期和優雅的關閉

NioServerSocketChannelFactory被創建的時候,所有的線程都會從指定的Executors中獲取。Boss線程從bossExecutor中獲取,worker線程從workerExecutor中獲取。因此,我們應該準確的指定Executors可以提供足夠數量的線程,最好的選擇就是指定一個cached線程池It is the best bet to specify a cached thread pool)。

此處發現所有源碼中的例子(example)中均設置爲Executors.newCachedThreadPool()

Boss線程和worker線程都是懶加載,沒有程序使用的時候要釋放掉。當boss線程和worker線程釋放掉的時候,所有的相關資源如Selector也要釋放掉。因此,如果想要優雅的關閉一個服務,需要做一下事情:

  1. factory創建的channels執行解綁(unbind)操作
  2. 關閉所有的由解綁的channels處理的子channels(這兩步目前通常通過ChannelGroup.close()來操作)
  3. 調用releaseExternalResources()方法

請確保在所有的channels都關閉前不要關閉executor,否則,會報RejectedExecutionException異常而且相關資源可能不會被釋放掉。

 

在此類中,最關鍵的構造方法:

publicNioServerSocketChannelFactory(

Executor bossExecutor, WorkerPool<NioWorker> workerPool) {

if (bossExecutor ==null) {

thrownew NullPointerException("bossExecutor");

}

if (workerPool ==null) {

thrownew NullPointerException("workerPool");

}

 

this.bossExecutor = bossExecutor;

this.workerPool = workerPool;

sink =new NioServerSocketPipelineSink(workerPool);

}

 

參數說明:

bossExecutor:默認推薦設置爲Executors.newCachedThreadPool()

workerPool:可通過new NioWorkerPool(workerExecutor, workerCount, true)創建,workerExecutor默認推薦設置爲Executors.newCachedThreadPool()workerCount可設置爲Runtime.getRuntime().availableProcessors() * 2(默認)

關於其中的NioWorkerPool類和NioServerSocketPipelineSink類,將在下文給予說明。

  1. NioWorkerPool

此類的關鍵方法是super的AbstractNioWorkerPool,

構造方法:

AbstractNioWorkerPool(Executor workerExecutor, int workerCount, boolean allowShutDownOnIdle) {

}

參數說明:

workerExecutor:worker線程的Executor

workerCount:最多創建的worker線程數

allowShutDownOnldle:空閒時是否關閉線程

  1. NioServerSocketPipelineSink

內部組件,傳輸服務的一個實現類,大多數情況下,不對用戶開放。

  1. NioDatagramChannelFactory

此類常用於創建UDP的ServerBootstrap時作爲構造方法的參數使用。

調用方式及設置如下:

ChannelFactory UDPCHANNEL_FACTORY = new NioDatagramChannelFactory()

ConnectionlessBootstrap UDPSERVER_BOOTSTRAP = new ConnectionlessBootstrap(UDPCHANNEL_FACTORY);

這個類利用非阻塞I/O模式引入NIO,這樣可以有效地向多個併發連接提供Channel

此類僅有一種線程類型:worker線程。一個NioDatagramChannelFactory可創建一個或多個worker線程,一個worker線程在非阻塞模式下爲一個或多個Channels提供非阻塞 讀或寫

在NioDatagramChannelFactory被創建的時候,所有的線程都會從指定的Executors中獲取

因此,我們應該準確的指定Executors可以提供足夠數量的線程,最好的選擇就是指定一個cached線程池(It is the best bet to specify a cached thread pool)。

所有的worker線程都是懶加載,沒有程序使用的時候要釋放掉。當worker線程釋放掉的時候,所有的相關資源如Selector也要釋放掉。因此,如果想要優雅的關閉一個服務,需要做一下事情:

通常通過ChannelGroup.close()來關閉所有的由此factory創建的channels

調用releaseExternalResources()方法

請確保在所有的channels都關閉前不要關閉executor,否則,會報RejectedExecutionException異常而且相關資源可能不會被釋放掉。

 

不支持多播模式,如果需要多播模式支持,採用OioDatagramChannelFactory替代

以下是此類中的最重要的構造方法:

public NioDatagramChannelFactory(WorkerPool<NioDatagramWorker> workerPool, InternetProtocolFamily family) {

}

參數說明:

workerPool:參考NioServerSocketChannelFactory

family:網絡協議系列,這個參數是爲了UDP的多播模式的,此參數只在java7+版本纔有效。默認爲null,可通過StandardProtocolFamily.INET(Ipv4)或者StandardProtocolFamily.INET6(Ipv6)設置

 

  1. NioClientSocketChannelFactory

與NioServerSocketChannelFactory類似,不做具體解說

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章