Netty

事件管道模型



事件管道模型

很多人知道事件驱动模式,那么事件管道(Event Pipeline)模式是什么?下面我们一起来探讨

event 的出现是设计为一个对象的状态的改变即就通知对该对象感兴趣的其他对象。
一般产生一个event总是有个发源地,我们可以把这个发源地叫做Event Generators而在java的EventObject interface中其实应该就是指的
source参数。

通常我们事件驱动模式是在一个本机上做,其实这个模式在分布式环境中也可以采用事件驱动模式。从整个模式上看,本地的事件驱动模型(local event)
和远程的事件驱动模型(remote event)没有什么本质区别,但有几点需要注意

1.local event:所有的对象都在本地包括需要通知的对象。
  remote event:Event Generators在一段,remote event 有Event Generators产生后通知另外远端的entity。

2.因为有远端那么就涉及到网络通信,因此通常不能保证网络通信一直通畅,所有remote event的模式可能或出现event丢失不能达到对方的情况。


3.local event 在本地,所以往往event很快,相应的处理的反馈也快,因此你会发现本地的event模式通常都类似于http的握手模式,也就是一个event触发
通常就直接通知给所有监听器。
而remote event 是远程event的传送,那么在网络通信其实消耗大量的时间,那么我认为client端多event的发送,server端统一处理一批event可能是节省资源
比较好的方式。

因此我们这里引入Event Pipeline,他是对server端接收到的event进行管理,将event 放入管道进行各自策略的处理。
另外我们把远端的Event Listener叫做Remote Event Listener,其实本质是一样的,只是为了区分。

当许多remote events 在同一个管道的时候,那么我们需要根据不同的需要来定制event的策略,下面列举以下一些策略
a.In-order delivery
events按照某种顺序传递

b. Efficient delivery
多个event 合并为一个event

c. Store and forward
event 暂停在管道中,等待某个条件出现再继续传递

d. Filtering of events
根据条件过滤相应event

e. Grouping of events
多个event 被一个event代替


PipeLine 流程:

Event Generator  ---> Event pipeLine -->Event Consumer

其中pipeLine中可以动态地设置一个或者多个策略。



posted @ 2010-12-01 16:46 linugb118 阅读(1323) | 评论 (0) | 编辑 收藏
Netty Channel 接口名词理解



1.Channel
channel 是负责数据读,写的对象,有点类似于老的io里面的stream,他和stream的区别,channel是双向的
既可以write 也可以read,而stream要分outstream和inputstream。而且在NIO中用户不应该直接从channel中读写数据,
而是应该通过buffer,通过buffer再将数据读写到channel中。
一个channel 可以提供给用户下面几个信息
(1)channel的当前状态,比如open 还是closed
(2)ChannelConfig对象,表示channel的一些参数,比如bufferSize
(3)channel支持的所有i/o操作(比如read,write,connect.bind)以及ChannelPipeLine(下面解释)

2.ChannelConfig
channel的参数,以Map 数据结构来存储

3.ChannelEvent
ChannelEvent广义的认为Channel相关的事件,他是否分Upstream events和downstream events两大块,这里需要注意的,让是server为
主体的话,从client的数据到server的过程是Upstream;而server到client的数据传输过程叫downstream;而如果以client为主体
的话,从server到client的过程对client来说是Upstream,而client到server的过程对client来说就是downstream。
Upstream events包括:
messageReceived:信息被接受时 ---MessageEvent
exceptionCaught:产生异常时 ---ExceptionEvent
channelOpen:channel被开启时 ---ChannelStateEvent
channelClosed:channel被关闭时 ---ChannelStateEvent
channelBound:channel被开启并准备去连接但还未连接上的时候 ---ChannelStateEvent
channelUnbound:channel被开启不准备去连接时候 ---ChannelStateEvent
channelConnected:channel被连接上的时候 ---ChannelStateEvent
channelDisconnected:channel连接断开的时候 ---ChannelStateEvent
channelInterestChanged:Channel的interestOps被改变的时候 ------ChannelStateEvent
writeComplete:写到远程端完成的时候 --WriteCompletionEvent

Downstream events包括:
write:发送信息给channel的时候 --MessageEvent
bind:绑定一个channel到指定的本地地址 --ChannelStateEvent
unbind:解除当前本地端口的绑定--ChannelStateEvent
connect:将channel连接到远程的机 --ChannelStateEvent
disconnect:将channel与远程的机连接断开 --ChannelStateEvent
close:关闭channel --ChannelStateEvent

需要注意的是,这里没有open event,这是因为当一个channel被channelFactory创建的话,channel总是已经被打开了。

此外还有两个事件类型是当父channel存在子channel的情况
childChannelOpen:子channel被打开 ---ChannelStateEvent
childChannelClosed:子channel被关闭 ---ChannelStateEvent

4.ChannelHandler
channel是负责传送数据的载体,那么数据肯定需要根据要求进行加工处理,那么这个时候就用到ChannelHandler
不同的加工可以构建不同的ChannelHandler,然后放入ChannelPipeline中
此外需要有ChannelEvent触发后才能到达ChannelHandler,因此根据event不同有下面两种的sub接口ChannelUpstreamHandler
和ChannelDownstreamHandler。
一个ChannelHandler通常需要存储一些状态信息作为判断信息,常用做法定义一个变量
比如
public class DataServerHandler extends {@link SimpleChannelHandler} {
*
*     <b>private boolean loggedIn;</b>
*
*     {@code @Override}
*     public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*         {@link Channel} ch = e.getChannel();
*         Object o = e.getMessage();
*         if (o instanceof LoginMessage) {
*             authenticate((LoginMessage) o);
*             <b>loggedIn = true;</b>
*         } else (o instanceof GetDataMessage) {
*             if (<b>loggedIn</b>) {
*                 ch.write(fetchSecret((GetDataMessage) o));
*             } else {
*                 fail();
*             }
*         }
*     }
*     ...
* }

// Create a new handler instance per channel.
* // See {@link Bootstrap#setPipelineFactory(ChannelPipelineFactory)}.
* public class DataServerPipelineFactory implements {@link ChannelPipelineFactory} {
*     public {@link ChannelPipeline} getPipeline() {
*         return {@link Channels}.pipeline(<b>new DataServerHandler()</b>);
*     }
* }

除了这种,每个ChannelHandler都可以从ChannelHandlerContext中获取或设置数据,那么下面的做法就是利用ChannelHandlerContext
设置变量
* {@code @Sharable}
* public class DataServerHandler extends {@link SimpleChannelHandler} {
*
*     {@code @Override}
*     public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*         {@link Channel} ch = e.getChannel();
*         Object o = e.getMessage();
*         if (o instanceof LoginMessage) {
*             authenticate((LoginMessage) o);
*             <b>ctx.setAttachment(true)</b>;
*         } else (o instanceof GetDataMessage) {
*             if (<b>Boolean.TRUE.equals(ctx.getAttachment())</b>) {
*                 ch.write(fetchSecret((GetDataMessage) o));
*             } else {
*                 fail();
*             }
*         }
*     }
*     ...
* }

* public class DataServerPipelineFactory implements {@link ChannelPipelineFactory} {
*
*     private static final DataServerHandler <b>SHARED</b> = new DataServerHandler();
*
*     public {@link ChannelPipeline} getPipeline() {
*         return {@link Channels}.pipeline(<b>SHARED</b>);
*     }
* }
这两种做法还是有区别的,上面的变量做法,每个new的handler 对象,变量是不共享的,而下面的ChannelHandlerContext是共享的

如果需要不同的handler之间共享数据,那怎么办,那就用ChannelLocal
例子:
  public final class DataServerState {
*
*     <b>public static final {@link ChannelLocal}&lt;Boolean&gt; loggedIn = new {@link ChannelLocal}&lt;Boolean&gt;() {
*         protected Boolean initialValue(Channel channel) {
*             return false;
*         }
*     }</b>
*     ...
* }
*
* {@code @Sharable}
* public class DataServerHandler extends {@link SimpleChannelHandler} {
*
*     {@code @Override}
*     public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*         Channel ch = e.getChannel();
*         Object o = e.getMessage();
*         if (o instanceof LoginMessage) {
*             authenticate((LoginMessage) o);
*             <b>DataServerState.loggedIn.set(ch, true);</b>
*         } else (o instanceof GetDataMessage) {
*             if (<b>DataServerState.loggedIn.get(ch)</b>) {
*                 ctx.getChannel().write(fetchSecret((GetDataMessage) o));
*             } else {
*                 fail();
*             }
*         }
*     }
*     ...
* }
*
* // Print the remote addresses of the authenticated clients:
* {@link ChannelGroup} allClientChannels = ...;
* for ({@link Channel} ch: allClientChannels) {
*     if (<b>DataServerState.loggedIn.get(ch)</b>) {
*         System.out.println(ch.getRemoteAddress());
*     }
* }
* </pre>

5.ChannelPipeline
channelPipeline是一系列channelHandler的集合,他参照J2ee中的Intercepting Filter模式来实现的,
让用户完全掌握如果在一个handler中处理事件,同时让pipeline里面的多个handler可以相互交互。
Intercepting Filter:http://java.sun.com/blueprints/corej2eepatterns/Patterns/InterceptingFilter.html
对于每一个channel都需要有相应的channelPipeline,当为channel设置了channelPipeline后就不能再为channel重新设置
channelPipeline。此外建议的做法的通过Channels 这个帮助类来生成ChannelPipeline 而不是自己去构建ChannelPipeline

通常pipeLine 添加多个handler,是基于业务逻辑的
比如下面
{@link ChannelPipeline} p = {@link Channels}.pipeline();
* p.addLast("1", new UpstreamHandlerA());
* p.addLast("2", new UpstreamHandlerB());
* p.addLast("3", new DownstreamHandlerA());
* p.addLast("4", new DownstreamHandlerB());
* p.addLast("5", new SimpleChannelHandler());
upstream event 执行的handler按顺序应该是 125
downstream event 执行的handler按顺序应该是 543
SimpleChannelHandler 是同时实现了 ChannelUpstreamHandler和ChannelDownstreamHandler的类
上面只是具有逻辑,如果数据需要通过格式来进行编码的话,那需要这些写
* {@link ChannelPipeline} pipeline = {@link Channels#pipeline() Channels.pipeline()};
* pipeline.addLast("decoder", new MyProtocolDecoder());
* pipeline.addLast("encoder", new MyProtocolEncoder());
* pipeline.addLast("executor", new {@link ExecutionHandler}(new {@link OrderedMemoryAwareThreadPoolExecutor}(16, 1048576, 1048576)));
* pipeline.addLast("handler", new MyBusinessLogicHandler());
其中:
Protocol Decoder - 将binary转换为java对象
Protocol Encoder - 将java对象转换为binary
ExecutionHandler - applies a thread model.
Business Logic Handler - performs the actual business logic(e.g. database access)
虽然不能为channel重新设置channelPipeline,但是channelPipeline本身是thread-safe,因此你可以在任何时候为channelPipeline添加删除channelHandler

需要注意的是,下面的代码写法不能达到预期的效果
* public class FirstHandler extends {@link SimpleChannelUpstreamHandler} {
*
*     {@code @Override}
*     public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*         // Remove this handler from the pipeline,
*         ctx.getPipeline().remove(this);
*         // And let SecondHandler handle the current event.
*         ctx.getPipeline().addLast("2nd", new SecondHandler());
*         ctx.sendUpstream(e);
*     }
* }
前提现在Pipeline只有最后一个FirstHandler,
上面明显是想把FirstHandler从Pipeline中移除,然后添加SecondHandler。而pipeline需要只要有一个Handler,因此如果想到到达这个效果,那么可以
先添加SecondHandler,然后在移除FirstHandler。

6.ChannelFactory
channel的工厂类,也就是用来生成channel的类,ChannelFactory根据指定的通信和网络来生成相应的channel,比如
NioServerSocketChannelFactory生成的channel是基于NIO server socket的。
当一个channel创建后,ChannelPipeline将作为参数附属给该channel。
对于channelFactory的关闭,需要做两步操作
第一,关闭所有该factory产生的channel包括子channel。通常调用ChannelGroup#close()。
第二,释放channelFactory的资源,调用releaseExternalResources()

7.ChannelGroup
channel的组集合,他包含一个或多个open的channel,closed channel会自动从group中移除,一个channel可以在一个或者多个channelGroup
如果想将一个消息广播给多个channel,可以利用group来实现
比如:
{@link ChannelGroup} recipients = new {@link DefaultChannelGroup}()
recipients.add(channelA);
recipients.add(channelB);
recipients.write(ChannelBuffers.copiedBuffer("Service will shut down for maintenance in 5 minutes.",CharsetUtil.UTF_8));

当ServerChannel和非ServerChannel同时都在channelGroup中的时候,任何io请求的操作都是先在ServerChannel中执行再在其他Channel中执行。
这个规则对关闭一个server非常适用。

8.ChannelFuture
在netty中,所有的io传输都是异步,所有那么在传送的时候需要数据+状态来确定是否全部传送成功,而这个载体就是ChannelFuture。

9.ChannelGroupFuture
针对一次ChannelGroup异步操作的结果,他和ChannelFuture一样,包括数据和状态。不同的是他由channelGroup里面channel的所有channelFuture
组成。

10.ChannelGroupFutureListener
针对ChannelGroupFuture的监听器,同样建议使用ChannelGroupFutureListener而不是await();

11.ChannelFutureListener
ChannelFuture监听器,监听channelFuture的结果。

12.ChannelFutureProgressListener
监听ChannelFuture处理过程,比如一个大文件的传送。而ChannelFutureListener只监听ChannelFuture完成未完成

13.ChannelHandlerContext
如何让handler和他的pipeLine以及pipeLine中的其他handler交换,那么就要用到ChannelHandlerContext,
ChannelHandler可以通过ChannelHandlerContext的sendXXXstream(ChannelEvent)将event传给最近的handler
可以通过ChannelHandlerContext的getPipeline来得到Pipeline,并修改他,ChannelHandlerContext还可以存放一下状态信息attments。
一个ChannelHandler实例可以有一个或者多个ChannelHandlerContext

14.ChannelPipelineFactory
产生ChannelPipe的工厂类

15.ChannelState
记载channel状态常量



posted @ 2010-11-12 15:36 linugb118 阅读(1120) | 评论 (0) | 编辑 收藏
Netty 简单样例分析



Netty 是JBoss旗下的io传输的框架,他利用java里面的nio来实现高效,稳定的io传输。

作为io传输,就会有client和server,下面我们看看用netty怎样写client和server

Client:
需要做的事情:
1.配置client启动类
  ClientBootstrap bootstrap = new ClientBootstrap(..)

2.根据不同的协议或者模式为client启动类设置pipelineFactory。
这里telnet pipline Factory 在netty中已经存在,所有直接用
  bootstrap.setPipelineFactory(new TelnetClientPipelineFactory());
也可以自己定义
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(
                        new DiscardClientHandler(firstMessageSize));
            }
        });      
这里DiscardClientHandler 就是自己定义的handler,他需要
public class DiscardServerHandler extends SimpleChannelUpstreamHandler
继承SimpleChannelUpstreamHandler  来实现自己的handler。这里DiscardClientHandler
是处理自己的client端的channel,他的
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
        // Server is supposed to send nothing.  Therefore, do nothing.
    }
  可以看到Discard client不需要接受任何信息

3.连接server
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));

这里解释一下channelFuture:

在Netty中所有的io操作都是异步的,这也就是意味任何io访问,那么就立即返回处理,并且不能确保
返回的数据全部完成。因此就出现了channelFuture,channelFuture在传输数据时候包括数据和状态两个
部分。他只有Uncompleted和Completed





既然netty io是异步的,那么如何知道channel传送完成有两种方式,一种添加监听器
addListener(ChannelFutureListener) 还有一种直接调用await()方法,这两种方式
有下面的区别
监听器:是以事件模式的,因此代码就需要用事件模式的样式去写,相当复杂,但他是non-blocking模式的
性能方面要比await方法好,而且不会产生死锁情况

await(): 直接方法调用,使用简单,但是他是blocking模式,性能方面要弱而且会产生死锁情况

不要在ChannelHandler 里面调用await(),这是因为通常在channelHandler里的event method是被i/o线程调用的
(除非ChannelPipeline里面有个ExecutionHandler),那么如果这个时候用await就容易产生死锁。

错误样例:
// BAD - NEVER DO THIS
* {@code @Override}
* public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*     if (e.getMessage() instanceof GoodByeMessage) {
*         {@link ChannelFuture} future = e.getChannel().close();
*         future.awaitUninterruptibly();
*         // Perform post-closure operation
*         // ...
*     }
* }
*
正确样例:
* // GOOD
* {@code @Override}
* public void messageReceived({@link ChannelHandlerContext} ctx, {@link MessageEvent} e) {
*     if (e.getMessage() instanceof GoodByeMessage) {
*         {@link ChannelFuture} future = e.getChannel().close();
*         future.addListener(new {@link ChannelFutureListener}() {
*             public void operationComplete({@link ChannelFuture} future) {
*                 // Perform post-closure operation
*                 // ...
*             }
*         });
*     }
* }

虽然await调用比较危险,但是你确保不是在一个i/o 线程中调用该方法,毕竟await方法还是很简洁方便的,如果
调用该方法是在一个i/o 线程,那么就会抛出 IllegalStateException

await的timeout和i/o timeout区别
需要注意的是这两个timeout是不一样的, #await(long),#await(long, TimeUnit), #awaitUninterruptibly(long),
#awaitUninterruptibly(long, TimeUnit) 这里面的timeout也i/o timeout 没有任何关系,如果io timeout,那么
channelFuture 将被标记为completed with failure,而await的timeout 与future完全没有关系,只是await动作的
timeout。
错误代码
  * // BAD - NEVER DO THIS
* {@link ClientBootstrap} b = ...;
* {@link ChannelFuture} f = b.connect(...);
* f.awaitUninterruptibly(10, TimeUnit.SECONDS);
* if (f.isCancelled()) {
*     // Connection attempt cancelled by user
* } else if (!f.isSuccess()) {
*     // You might get a NullPointerException here because the future
*     // might not be completed yet.
*     f.getCause().printStackTrace();
* } else {
*     // Connection established successfully
* }
*
正确代码
* // GOOD
* {@link ClientBootstrap} b = ...;
* // Configure the connect timeout option.
* <b>b.setOption("connectTimeoutMillis", 10000);</b>
* {@link ChannelFuture} f = b.connect(...);
* f.awaitUninterruptibly();
*
* // Now we are sure the future is completed.
* assert f.isDone();
*
* if (f.isCancelled()) {
*     // Connection attempt cancelled by user
* } else if (!f.isSuccess()) {
*     f.getCause().printStackTrace();
* } else {
*     // Connection established successfully
* }

4.等待或监听数据全部完成
如: future.getChannel().getCloseFuture().awaitUninterruptibly();

5.释放连接等资源
bootstrap.releaseExternalResources();

Server:
1.配置server

ServerBootstrap bootstrap = new ServerBootstrap(
                new NioServerSocketChannelFactory(
                        Executors.newCachedThreadPool(),
                        Executors.newCachedThreadPool()));
                      
2.设置pipeFactory
        bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
            public ChannelPipeline getPipeline() throws Exception {
                return Channels.pipeline(new EchoServerHandler());
            }
        });
    或者
   bootstrap.setPipelineFactory(new HttpServerPipelineFactory());
 
3.绑定sever端端口
bootstrap.bind(new InetSocketAddress(8080));



posted @ 2010-11-11 14:03 linugb118 阅读(902) | 评论 (1) | 编辑 收藏
Netty 源码研究



org.jboss.netty.bootstrap
本身 Netty 可以作为一个server存在的,因此他存在启动入口,他具有client启动,server启动以及connectionless 启动(比如UDP)
1.基类bootstrap:他包含ChannelFactory,ChannelPipeline,ChannelPipelineFactory。
ClientBootstrap: 有connect()方法
ConnectionlessBootstrap:有connect(),bind()方法
ServerBootstrap:有bind()方法

2.org.jboss.netty.buffer
netty自己提供了buffer 来取代nio 中的java.nio.ByteBuffer,他与nio中的byteBuffer相比,有下面的几个特点
1>可以根据自己需要自己定义buffer type
2>只是buffer type改变而不拷贝buffer的内容,这样可以减少开销
3>动态大小的buffer type 类似于stringbuffer
4>不需要调用flip()方法
5>更快的性能


3.org.jboss.netty.channel
channel的核心api,包括异步和事件驱动等各种传送接口
org.jboss.netty.channel.group
channel group,里面包含一系列open的channel

org.jboss.netty.channel.local
一种虚拟的运输方式,能允许同一个虚拟机上的两个部分可以互相通信
org.jboss.netty.channel.socket
TCP,UDP端口接口,主要继承channel

org.jboss.netty.channel.socket.nio
基于nio端口channel的具体实现

org.jboss.netty.channel.socket.oio
基于老的io端口的channel的具体实现

org.jboss.netty.channel.socket.http
基于http的客户端和相应的server端的实现,可以在有防火墙的情况下进行工作

需要做的事情
a. 将http tunnel 作为servlet进行配置
web.xml
<servlet>
*     <servlet-name>NettyTunnelingServlet</servlet-name>
*      <servlet-class>org.jboss.netty.channel.socket.http.HttpTunnelingServlet</servlet-class>
*     <!--
*       The name of the channel, this should be a registered local channel.
*       See LocalTransportRegister.
*     -->
*     <init-param>
*       <param-name>endpoint</param-name>
*       <param-value>local:myLocalServer</param-value>
*     </init-param>
*     <load-on-startup>1</load-on-startup>
*   </servlet>
*
*   <servlet-mapping>
*     <servlet-name>NettyTunnelingServlet</servlet-name>
*     <url-pattern>/netty-tunnel</url-pattern>
*   </servlet-mapping>
接下来需要将你的基于netty的server app绑定到上面的http servlet
你可以这样写
*
* public class LocalEchoServerRegistration {
*
*     private final ChannelFactory factory = new DefaultLocalServerChannelFactory();
*     private volatile Channel serverChannel;
*
*     public void start() {
*         ServerBootstrap serverBootstrap = new ServerBootstrap(factory);
*         EchoHandler handler = new EchoHandler();
*         serverBootstrap.getPipeline().addLast("handler", handler);
*
*         // Note that "myLocalServer" is the endpoint which was specified in web.xml.
*         serverChannel = serverBootstrap.bind(new LocalAddress("myLocalServer"));
*     }
*
*     public void stop() {
*         serverChannel.close();
*     }
* }

然后在Ioc framework(JBoss Microcontainer,Guice,Spring)中定义bean
<bean name="my-local-echo-server"
     class="org.jboss.netty.example.http.tunnel.LocalEchoServerRegistration" />

这样http servlet 就可以了

b. 连接http tunnel
构造client
* ClientBootstrap b = new ClientBootstrap(
*         new HttpTunnelingClientSocketChannelFactory(
*                 new NioClientSocketChannelFactory(...)));
*
* // Configure the pipeline (or pipeline factory) here.
* ...
*
* // The host name of the HTTP server
* b.setOption("serverName", "example.com");
* // The path to the HTTP tunneling Servlet, which was specified in in web.xml
* b.setOption("serverPath", "contextPath/netty-tunnel");
* b.connect(new InetSocketAddress("example.com", 80);

4.org.jboss.netty.container
各种容器的兼容
org.jboss.netty.container.microcontainer
JBoss Microcontainer 整合接口

org.jboss.netty.container.osgi
OSGi framework 整合接口

org.jboss.netty.container.spring
Spring framework 整合接口

5.org.jboss.netty.handler
处理器
org.jboss.netty.handler.codec.base64
Base64 编码

org.jboss.netty.handler.codec.compression
压缩格式

org.jboss.netty.handler.codec.embedder
嵌入模式下编码和解码,即使没有真正的io环境也能使用
org.jboss.netty.handler.codec.frame
可扩展的接口,重新评估基于流的数据的排列和内容
org.jboss.netty.handler.codec.http.websocket
websocket相关的编码和解码,
参考
http://en.wikipedia.org/wiki/Web_Sockets

org.jboss.netty.handler.codec.http
http的编码解码以及类型信息

org.jboss.netty.handler.codec.oneone
一个对象到另一对象的自定义抽象接口,如果有自己编码需要继承该抽象类

org.jboss.netty.handler.codec.protobuf
Google Protocol Buffers的编码解码
Google Protocol Buffers参考下面
http://code.google.com/p/protobuf/

org.jboss.netty.handler.codec.replay

org.jboss.netty.handler.codec.rtsp
Real_Time_Streaming_Protocol的编码解码
Real_Time_Streaming_Protocol 参考下面wiki
http://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol

org.jboss.netty.handler.codec.serialization
将序列化的对象转换到byte buffer的相关实现

org.jboss.netty.handler.codec.string
字符串编码解码,比如utf8编码等,继承oneone包的接口

org.jboss.netty.handler.execution
基于java.util.concurrent.Executor的实现

org.jboss.netty.handler.queue
将event存入内部队列的处理

org.jboss.netty.handler.ssl
基于javax.net.ssl.SSLEngine的SSL以及TLS实现
参考
http://en.wikipedia.org/wiki/Transport_Layer_Security

org.jboss.netty.handler.stream
异步写入大数据,不会产生outOfMemory 也不会花费很多内存

org.jboss.netty.handler.timeout
通过jboss.netty.util.Timer来对读写超时或者闲置链接的通知

6.org.jboss.netty.logging
根据不同的log framework 实现的类

7.org.jboss.netty.util
nettyutil类

org.jboss.netty.util.internal
netty内部util类,不被外部使用

posted @ 2010-11-09 15:08 linugb118 阅读(1281) | 评论 (0) | 编辑 收藏
JSF 探索 (1)

JSF 探索 (1)

虽然JSF已经出来好久了,而且好像感觉不是很潮流的框架了,然后当我看见他的时候,我觉得他的思想很好,而且随着手机等client多样化的出现,我相信人们
必将重新拾起JSF的思想。

JSF说白了是事件驱动,web以前的时代我估计大部分都是事件驱动的开发模式,而web的出现,使人们更多关注浏览器上面的渲染,以及servlet和http的操作上。
web2.0的出现,使人们感觉在浏览器上的操作更像以前c/s结构的操作,而手机client的热捧更能体现未来多种客户端访问server的模式即将到来,也许大家开始
在想如何做新的框架,其实这种想法已经out了,JSF里面早就有这个思想了,你只需要去关注JSF不就ok了?

任何《* in action》的书总是让programmer 兴奋,同样JSF in Action也是一本不错的书,让我们一起去看看它把。

1.JSF 关键词
UI component :静态对象,它保存在server端,其实本身UI Component是javabean对象,他具有javabean具备的属性,methods和events。通常多个component被组织为一个页面的视图

Renderer:渲染器,renderer被用作渲染component,以及将用户的输入转换为component的值。特别当不同类型的client端,比如手机,那么对应的就有手机相应格式的renderer。

Validator:负责验证用户输入值的可靠性,单个UI component可以对应一个或多个Validator.

Backing beans:后台的javabean,做一些逻辑处理什么的,存储component的值,实现事件监听器,他可以引用component。

Converter:显示转换器,比如时间格式的显示,一个component可以对应一个converter

Events and listeners:事件和监听器

Messages:显示给用户的信息

Navigation:一个页面到另外一个页面的向导。


2.JSF请求的六个阶段
Phase 1: Restore View
构建视图页面供用户输入或查看

Phase 2: Apply Request Values
获取请求值

Phase 3: Process Validations
可以对请求值进行验证

Phase 4: Update Model Values
更新back bean 的值

Phase 5: Invoke Application
调用应用,执行相应的监听器

Phase 6: Render Response
将response根据要求进行包装后返回给用户,这里的包装是指知道的页面技术,比如html,比如手机展现技术

3.client and server component id
server component id可以在component 的id属性中显性填写id,以便在服务端能唯一标识该组件
client id 是继承server component id,他最后体现的也是html 中的标签,而id也为对应标签的id,与server component id不同的是,他有继承关系,比如

<form id="myForm" method="post"
action="/jia-standard-components/client_ids.jsf"
enctype="application/x-www-form-urlencoded">
<p>
<input type="text" name="myForm:_id1" />
</p>
<p>
<input id="myForm:inputText" type="text" name="myForm:inputText" />
</p>
...
</form>
而对应的服务端
<p>
<h:outputText id="outputText" value="What are you looking at?"/>
</p>
<h:form id="myForm">
<p>
<h:inputText/>
</p>
<p>
<h:inputText id="inputText"/>
</p>
...
</h:form>

4.JSF EL 表达语言: 他是基于JSTL的描述
此外Jsp中application,session,page 四个scoped变量,只有page jsf中不支持

除了上面三个scoped变量,jsf还有下面一些隐性的变量
applicationScope:从application中获取对象,比如#{applicationScope.myVariable} myVariable对象存放在application中
cookie:从cookie中获取数据
facesContext:这个jsp2.0中没有,当前请求的faceContext实例
header:http head头信息,比如#{header['User-Agent']}
headerValues:多个head头信息的载体 比如#{headerValues['Accept-Encoding'][3]}
initParam:初始化的一些参数比如servlet context initialization parameters 可以这样使用#{initParam.adminEmail}
param:等同jsp中的request.getParameter() 用法#{param.address}
paramValues:等同jsp中的request.getParameterValues 用法#{param.address[2]}
requestScope等同于jsp中reqeust 用法#{requestScope.user-Preferences}
sessionScope等同于jsp中session  用法#{sessionScope['user']}
view 这个变量jsp2.0中没有,它表示当前视图,它有三个属性viewId,renderKitId, and locale 用法#{view.locale}

5.需要的jar包和文件
构建jsf环境需要jsf-api.jar,jsf-impl.jar,jstl.jar,standard.jar 以及配置文件faces-config.xml
需要在web.xml 中配置faceservlet
<web-app>
...
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
...
</web-app>

web.xml 中jsf定义的上下文的参数有
javax.faces.CONFIG_FILES
javax.faces.DEFAULT_SUFFIX
javax.faces.LIFECYCLE_ID
javax.faces.STATE_SAVING_METHOD

RI-specific configuration parameters:
com.sun.faces.NUMBER_OF_VIEWS_IN_SESSION
com.sun.faces.validateXml
com.sun.faces.verifyObjects

比如
<web-app>
...
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>/WEB-INF/navigation.xml,/WEB-INF/RegistrationWizard.xml</
param-value>
</context-param>
...
<web-app>
除了web.xml jsf相关的参数,本身jsf具有faces-config.xml 具体里面的配置属性可以查看配置文件,可以找可视化工具进行配置。



posted @ 2010-11-04 16:24 linugb118 阅读(1162) | 评论 (1) | 编辑 收藏
[转]Java 理论与实践: 正确使用 Volatile 变量


Java 理论与实践: 正确使用 Volatile 变量

volatile 变量使用指南
Brian Goetz ([email protected]), 高级工程师, Sun Microsystems

简介: Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低),而且其使用也更容易出错。在这期的 Java 理论与实践 中,Brian Goetz 将介绍几种正确使用 volatile 变量的模式,并针对其适用性限制提出一些建议。

查看本系列更多内容

标记本文!

发布日期: 2007 年 7 月 05 日
级别: 中级
访问情况 237 次浏览
建议: 0 (添加评论)
1 star2 stars3 stars4 stars5 stars 平均分 (共 1 个评分 )



Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形。

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

Volatile 变量

Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile 变量的最新值。Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。因此,单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类(例如 “start <=end”)。

出于简易性或可伸缩性的考虑,您可能倾向于使用 volatile 变量而不是锁。当使用 volatile 变量而非锁时,某些习惯用法(idiom)更加易于编码和阅读。此外,volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。

正确使用 volatile 变量的条件

您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

    * 对变量的写操作不依赖于当前值。
    * 该变量没有包含在具有其他变量的不变式中。

实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。

第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)

大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。清单 1 显示了一个非线程安全的数值范围类。它包含了一个不变式 —— 下界总是小于或等于上界。

清单 1. 非线程安全的数值范围类

            @NotThreadSafe
            public class NumberRange {
            private int lower, upper;
            public int getLower() { return lower; }
            public int getUpper() { return upper; }
            public void setLower(int value) {
            if (value > upper)
            throw new IllegalArgumentException(...);
            lower = value;
            }
            public void setUpper(int value) {
            if (value < lower)
            throw new IllegalArgumentException(...);
            upper = value;
            }
            }
           


这种方式限制了范围的状态变量,因此将 lower 和 upper 字段定义为 volatile 类型不能够充分实现类的线程安全;从而仍然需要使用同步。否则,如果凑巧两个线程在同一时间使用不一致的值执行 setLower 和 setUpper 的话,则会使范围处于不一致的状态。例如,如果初始状态是 (0, 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用 setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值。至于针对范围的其他操作,我们需要使 setLower() 和 setUpper() 操作原子化 —— 而将字段定义为 volatile 类型是无法实现这一目的的。

性能考虑

使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。

很难做出准确、全面的评价,例如 “X 总是比 Y 快”,尤其是对 JVM 内在的操作而言。(例如,某些情况下 VM 也许能够完全删除锁机制,这使得我们难以抽象地比较 volatile 和 synchronized 的开销。)就是说,在目前大多数的处理器架构上,volatile 读操作开销非常低 —— 几乎和非 volatile 读操作一样。而 volatile 写操作的开销要比非 volatile 写操作多很多,因为要保证可见性需要实现内存界定(Memory Fence),即便如此,volatile 的总开销仍然要比锁获取低。

volatile 操作不会像锁一样造成阻塞,因此,在能够安全使用 volatile 的情况下,volatile 可以提供一些优于锁的可伸缩特性。如果读操作的次数要远远超过写操作,与锁相比,volatile 变量通常能够减少同步的性能开销。

正确使用 volatile 的模式

很多并发性专家事实上往往引导用户远离 volatile 变量,因为使用它们要比使用锁更加容易出错。然而,如果谨慎地遵循一些良好定义的模式,就能够在很多场合内安全地使用 volatile 变量。要始终牢记使用 volatile 的限制 —— 只有在状态真正独立于程序内其他内容时才能使用 volatile —— 这条规则能够避免将这些模式扩展到不安全的用例。

模式 #1:状态标志

也许实现 volatile 变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。

很多应用程序包含了一种控制结构,形式为 “在还没有准备好停止程序时再执行一些工作”,如清单 2 所示:

清单 2. 将 volatile 变量作为状态标志使用

            volatile boolean shutdownRequested;
            ...
            public void shutdown() { shutdownRequested = true; }
            public void doWork() {
            while (!shutdownRequested) {
            // do stuff
            }
            }
           


很可能会从循环外部调用 shutdown() 方法 —— 即在另一个线程中 —— 因此,需要执行某种同步来确保正确实现 shutdownRequested变量的可见性。(可能会从 JMX 侦听程序、GUI 事件线程中的操作侦听程序、通过 RMI 、通过一个 Web 服务等调用)。然而,使用synchronized 块编写循环要比使用清单 2 所示的 volatile 状态标志编写麻烦很多。由于 volatile 简化了编码,并且状态标志并不依赖于程序内任何其他状态,因此此处非常适合使用 volatile。

这种类型的状态标记的一个公共特性是:通常只有一种状态转换;shutdownRequested 标志从 false 转换为 true,然后程序停止。这种模式可以扩展到来回转换的状态标志,但是只有在转换周期不被察觉的情况下才能扩展(从 false 到 true,再转换到 false)。此外,还需要某些原子状态转换机制,例如原子变量。

模式 #2:一次性安全发布(one-time safe publication)

缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原语值变得更加困难。在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一个线程写入)和该对象状态的旧值同时存在。(这就是造成著名的双重检查锁定(double-checked-locking)问题的根源,其中对象引用在没有同步的情况下进行读操作,产生的问题是您可能会看到一个更新的引用,但是仍然会通过该引用看到不完全构造的对象)。

实现安全发布对象的一种技术就是将对象引用定义为 volatile 类型。清单 3 展示了一个示例,其中后台线程在启动阶段从数据库加载一些数据。其他代码在能够利用这些数据时,在使用之前将检查这些数据是否曾经发布过。

清单 3. 将 volatile 变量用于一次性安全发布

            public class BackgroundFloobleLoader {
            public volatile Flooble theFlooble;
            public void initInBackground() {
            // do lots of stuff
            theFlooble = new Flooble();  // this is the only write to theFlooble
            }
            }
            public class SomeOtherClass {
            public void doWork() {
            while (true) {
            // do some stuff...
            // use the Flooble, but only if it is ready
            if (floobleLoader.theFlooble != null)
            doSomething(floobleLoader.theFlooble);
            }
            }
            }
           


如果 theFlooble 引用不是 volatile 类型,doWork() 中的代码在解除对 theFlooble 的引用时,将会得到一个不完全构造的 Flooble。

该模式的一个必要条件是:被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)。volatile 类型的引用可以确保对象的发布形式的可见性,但是如果对象的状态在发布后将发生更改,那么就需要额外的同步。

模式 #3:独立观察(independent observation)

安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。

使用该模式的另一种应用程序就是收集程序的统计信息。清单 4 展示了身份验证机制如何记忆最近一次登录的用户的名字。将反复使用 lastUser 引用来发布值,以供程序的其他部分使用。

清单 4. 将 volatile 变量用于多个独立观察结果的发布

            public class UserManager {
            public volatile String lastUser;
            public boolean authenticate(String user, String password) {
            boolean valid = passwordIsValid(user, password);
            if (valid) {
            User u = new User();
            activeUsers.add(u);
            lastUser = user;
            }
            return valid;
            }
            }
           


该模式是前面模式的扩展;将某个值发布以在程序内的其他地方使用,但是与一次性事件的发布不同,这是一系列独立事件。这个模式要求被发布的值是有效不可变的 —— 即值的状态在发布后不会更改。使用该值的代码需要清楚该值可能随时发生变化。

模式 #4:“volatile bean” 模式

volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性。清单 5 中的示例展示了遵守 volatile bean 模式的 JavaBean:

清单 5. 遵守 volatile bean 模式的 Person 对象

            @ThreadSafe
            public class Person {
            private volatile String firstName;
            private volatile String lastName;
            private volatile int age;
            public String getFirstName() { return firstName; }
            public String getLastName() { return lastName; }
            public int getAge() { return age; }
            public void setFirstName(String firstName) {
            this.firstName = firstName;
            }
            public void setLastName(String lastName) {
            this.lastName = lastName;
            }
            public void setAge(int age) {
            this.age = age;
            }
            }
           


volatile 的高级模式

前面几节介绍的模式涵盖了大部分的基本用例,在这些模式中使用 volatile 非常有用并且简单。这一节将介绍一种更加高级的模式,在该模式中,volatile 将提供性能或可伸缩性优势。

volatile 应用的的高级模式非常脆弱。因此,必须对假设的条件仔细证明,并且这些模式被严格地封装了起来,因为即使非常小的更改也会损坏您的代码!同样,使用更高级的 volatile 用例的原因是它能够提升性能,确保在开始应用高级模式之前,真正确定需要实现这种性能获益。需要对这些模式进行权衡,放弃可读性或可维护性来换取可能的性能收益 —— 如果您不需要提升性能(或者不能够通过一个严格的测试程序证明您需要它),那么这很可能是一次糟糕的交易,因为您很可能会得不偿失,换来的东西要比放弃的东西价值更低。

模式 #5:开销较低的读-写锁策略

目前为止,您应该了解了 volatile 的功能还不足以实现计数器。因为 ++x 实际上是三种操作(读、添加、存储)的简单组合,如果多个线程凑巧试图同时对 volatile 计数器执行增量操作,那么它的更新值有可能会丢失。

然而,如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。清单 6 中显示的线程安全的计数器使用 synchronized 确保增量操作是原子的,并使用 volatile 保证当前结果的可见性。如果更新不频繁的话,该方法可实现更好的性能,因为读路径的开销仅仅涉及 volatile 读操作,这通常要优于一个无竞争的锁获取的开销。

清单 6. 结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”

            @ThreadSafe
            public class CheesyCounter {
            // Employs the cheap read-write lock trick
            // All mutative operations MUST be done with the 'this' lock held
            @GuardedBy("this") private volatile int value;
            public int getValue() { return value; }
            public synchronized int increment() {
            return value++;
            }
            }
           


之所以将这种技术称之为 “开销较低的读-写锁” 是因为您使用了不同的同步机制进行读写操作。因为本例中的写操作违反了使用 volatile 的第一个条件,因此不能使用 volatile 安全地实现计数器 —— 您必须使用锁。然而,您可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。然而,要随时牢记这种模式的弱点:如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难。

结束语

与锁相比,Volatile 变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile 的使用条件 —— 即变量真正独立于其他变量和自己以前的值 —— 在某些情况下可以使用 volatile 代替 synchronized 来简化代码。然而,使用 volatile 的代码往往比使用锁的代码更加容易出错。本文介绍的模式涵盖了可以使用 volatile 代替synchronized 的最常见的一些用例。遵循这些模式(注意使用时不要超过各自的限制)可以帮助您安全地实现大多数用例,使用 volatile 变量获得更佳性能。

参考资料

学习

    * 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。

    * Java Concurrency in Practice :使用 Java 代码开发并发程序的 how-to 手册,内容包括构建并编写线程安全的类和程序、避免性能影响、管理性能和测试并发应用程序。

    * 流行的原子:介绍了 Java 5.0 中新增的原子变量类,该特性对 volatile 变量进行了扩展,从而支持原子状态转换。

    * 非阻塞算法简介:介绍如何使用原子变量而不是锁实现并发算法。

    * Volatiles:从 Wikipedia 获得关于 volatile 变量的更多信息。

    * Java 技术专区:提供了数百篇有关 Java 编程各个方面的文章。

讨论

    * 参与论坛讨论。

    * developerWorks blogs:加入 developerWorks 社区。

关于作者

Brian Goetz 作为一名专业软件开发人员已经 20 年了。他是 Sun Microsystems 的高级资深工程师 ,为多个 JCP Expert Group 服务。Brian 的著作 Java Concurrency In Practice 在 2006 年 5 月由 Addison-Wesley 出版。请参阅 Brian 在业界主流刊物上 已经发表和即将发表的文章。

posted @ 2010-10-29 17:05 linugb118 阅读(42) | 评论 (0) | 编辑 收藏
Java NIO---Channel部分

Java NIO---Channel部分

Channel 是连接buffer和Io设备之间的管道,当然channel也可以一头连接channel

Basic Channel Interface:
public interface Channel
{
public boolean isOpen( );
public void close( ) throws IOException;
}

1.open channel
channel作为i/o 设备的管道,分为两种,一种文件(file),另一种是端口(Socket)
file相关的只有fileChannel
socket相关的有SocketChannel, ServerSocketChannel和DatagramChannel.

socket channel可以通过factory创建新的socket channel
比如
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress ("somehost", someport));

ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (somelocalport));

而fileChannel 则不能这样,他是在一个打开的andomAccessFile, FileInputStream或者FileOutputStream
对象上getChannel()
比如
RandomAccessFile raf = new RandomAccessFile ("somefile", "r");
FileChannel fc = raf.getChannel( );

2.Using Channels
三个接口:
public interface ReadableByteChannel
extends Channel
{
public int read (ByteBuffer dst) throws IOException;
}
public interface WritableByteChannel
extends Channel
{
public int write (ByteBuffer src) throws IOException;
}
public interface ByteChannel
extends ReadableByteChannel, WritableByteChannel
{
}

需要注意的是 fileChannel 虽然也有write,read方法,但是他们能否调用write read方法是由file相关的访问权限决定的。

channel 有blocking和nonblocking两种模式,nonblocking是指永远不会将一个正在调用的线程sleep,他们被请求的结果要么
立刻完成,要么返回一个nothing的结果。
只有stream-oriented的channels(比如sockets and pipes)可以用nonblocking模式

3.Closing Channels
和buffer不一样,channel不能被重用,用完之后一定要close,此外当一个线程被设置为interrupt状态,当该线程试图访问某个channel
的话,该channel将立刻关闭。


4.Scatter/Gather
当我们在多个buffer上执行一个i/O操作的时候,我们需要将多个buffer放在一个buffer数组一并让channel来处理
Scatter/Gather interface:

public interface ScatteringByteChannel
extends ReadableByteChannel
{
public long read (ByteBuffer [] dsts)
throws IOException;
public long read (ByteBuffer [] dsts, int offset, int length)
throws IOException;
}
public interface GatheringByteChannel
extends WritableByteChannel
{
public long write(ByteBuffer[] srcs)
throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
}

例子:
ByteBuffer header = ByteBuffer.allocateDirect (10);
ByteBuffer body = ByteBuffer.allocateDirect (80);
ByteBuffer [] buffers = { header, body };
int bytesRead = channel.read (buffers);

5.File Channels
filechannel 只能是blocking模式也就是被锁住模式,不能是nonblocking模式
public abstract class FileChannel
extends AbstractChannel
implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// This is a partial API listing
public abstract void truncate (long size)
public abstract void force (boolean metaData)
}
fileChannel 中有truncate和force 两个方法
truncate (long size)是以给定的size来切断file
force (boolean metaData) file属性的修改立刻影响到disk上

6.File Locking
file 锁虽然在fileChannel中提出,但是需要注意本身lock是和file相关,而且不同的操作系统提供的file也有不同,和channel无关的

public abstract class FileChannel
extends AbstractChannel
implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// This is a partial API listing
public final FileLock lock( )
public abstract FileLock lock (long position, long size,
boolean shared)
public final FileLock tryLock( )
public abstract FileLock tryLock (long position, long size,
boolean shared)
}

lock 有三个参数,第一个是开始锁住的file的postion, 第二个是锁住的file大小,前面这两个参数就能定义了一块lock范围,第三个是
显示lock是shared(true)类型还是exclusive(false)类型,
没有参数的lock()其实等同于fileChannel.lock (0L, Long.MAX_VALUE, false);
FileLock 本身是线程安全的,可以多个线程同时访问

tryLock() 和Lock()相似,当某个lock因为其他原因不能被获取,那么就要用tryLock() 这个时候就返回null
FileLock总关联一个特定的channel,可以通过channel()获取相关联的channel,当FileLock release(),那么对于的channel也就相应的close();

7.Memory-Mapped Files
Memory-mapped files 一般效率更高,而且因为他是和操作系统相关的,所有他不会消费jvm分配的内存
通过map()来建立MappedByteBuffer,
mapped buffers和lock有点不同,lock和产生它的channel绑定,如果channel closed那么lock也就消失
而mapped buffers本身也没有ummap方法,他是通过自身不再被引用然后被系统垃圾回收的。

8.Channel-to-Channel Transfers
channel直接的传输可以提供更好的效率

public abstract class FileChannel
extends AbstractChannel
implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
{
// This is a partial API listing
public abstract long transferTo (long position, long count,
WritableByteChannel target)
Java NIO
90
public abstract long transferFrom (ReadableByteChannel src,
long position, long count)
}

这个是能在filechannel之间使用,两个socketchannel之间不能直接使用transferTo和transferFrom,但是因为socketchannel继承WritableByteChannel and ReadableByteChannel,所有可以将一个文件的内容transferTo socketChannel或直接通过transferFrom从socketChannel中读取数据

9.Socket Channels
对应以前一般一个线程对应一个socket,而在NIO中可以一个线程对应成百上千的socket,同时没有性能降低的问题
三种socket channel(DatagramChannel,SocketChannel, and ServerSocketChannel)
DatagramChannel和SocketChannel 有read和write的方法
而ServerSocketChannel 则是监听connection和创建SocketChannel,他本身不参与数据的传输
这三个channel都可以通过socket()方法获取到对应的Socket,ServerSocket,DatagramSocket
需要说明的是,socket不一样要绑定channel,通过传统的new 创建socket,那么getChannel()就会返回null

SocketChannel 有Nonblocking和blocking 两种模式
SocketChannel sc = SocketChannel.open( );
sc.configureBlocking (false); // nonblocking
...
if ( ! sc.isBlocking( )) {
doSomething (cs);
}
可以通过configureBlocking 来设置,true表示blocking mode 而false 表示nonblocking mode
对应大型应用并发处理的 建议使用nonblocking mode
看看下面的例子:
Socket socket = null;
Object lockObj = serverChannel.blockingLock( );
// have a handle to the lock object, but haven't locked it yet
// may block here until lock is acquired
synchronize (lockObj)
{
// This thread now owns the lock; mode can't be changed
boolean prevState = serverChannel.isBlocking( );
serverChannel.configureBlocking (false);
socket = serverChannel.accept( );
serverChannel.configureBlocking (prevState);
}
// lock is now released, mode is allowed to change
if (socket != null) {
doSomethingWithTheSocket (socket);
}
体验一下 blockingLock()和lock()的区别

SocketChannel 可以通过finishConnect( ),isConnectPending( ), or isConnected( )获取当前connection的状态

socket是面向流的,datagram是面向packet的
因此DatagramChannel 就是面向packet协议的channel,比如UDP/IP

10.pipe 方式 可以研究一下












posted @ 2010-09-10 15:54 linugb118 阅读(1730) | 评论 (0) | 编辑 收藏
Java NIO---buffer部分

Java NIO---buffer部分

最近想建一个网络传输的平台,查看到了Jboss 的Netty,而他们核心的传输是用了JDK 1.4以后的
NIO特性,因此借机会学习一下NIO
NIO主要有下面几大部分

Buffer:Io的操作少不了缓存,通过Buffer能大大提高传输的效率,同样NIO中也有Buffer的这部分
Buffer针对数据类型有相应的子类,他们都是继承Buffer class
比如CharBuffer,IntBuffer等等
需要说明的子类MappedByteBuffer 通过命名可以看出这个MappedByteBuffer有mapped+byte+Buffer组成
据我理解 这么mapped 是指memory-mapped
这里解释一下memory-mapped, 以为我们说的buffer 可能就是指物理上的内存,包括jdk 以前的io
一般情况下,buffer指物理上的内存没什么问题,可以物理内存毕竟比较有限,当需要很大buffer存放数据的时候
物理内存就不够,那么就引出一个虚拟内存的概念,就像现在的os 一样,也有虚拟内存的概念,虚拟内存本质上
是存在硬盘上的,只是物理内存存放的不是具体的数据而是虚拟内存上的地址,而具体的数据则是在虚拟内存上,这样
物理内存只是存放很多地址,这样就大大增加了buffer的size。当然如果你buffer的数据很小,也可以知道放入物理内存
因此我认为 这个memory-mapped 是指的用虚拟内存的哪种缓存模式

A.Buffer的 attribute
1.Capacity:buffer的大小,一个buffer按照某个Capacity创建后就不能修改
2.Limit:用了多少buffer
3.Position:指针,获取下一个元素的位置,操作get(),put(),这个position会自动更新(和iterator相似)
4.Mark:被标记的指针位置 通过mark()操作可以使mark = position 通过reset() 可以使position = mark

上面这几个属性应该符合下面条件
0 <= mark <= position <= limit <= capacity


B. fill:插入数据
用put() 方法可以插入一段数据 但是这个时候postion 在插入这段数据的尾部

C. flip:弹出数据
因为上面写入数据后,指针在尾部不是从0开始,那么我们需要
buffer.limit(buffer.position( )).position(0);
将limit 设置为当前的postion,limit 可以告诉方法这段数据什么时候结束,而具体可以调用方法
hasRemaining( ) 来判断是否数据结束。

同时将当前指针设置为0
NIO 提供了方法buffer.flip()完成上面的动作

另外方法rewind( )和flip相似 只是区别在于flip要修改limit,而rewind不修改limit
rewind 用处可以在已经flip后,还能重读这段数据

Buffer不是线程安全的,如果你想要多线程同时访问,那么需要用synchronization

D. Mark:让buffer记住某个指针,已备后面之用。
调用mark()就能将当前指针mark住
而调用reset()就能将当前指针回到mark了的指针位置,如果mark未定义,那么调用reset就会报InvalidMarkException
此外rewind( ), clear( ), and flip( )这些操作将会抛弃mark,这样clear()和reset()区别
clear表示清空buffer 而reset表示能将当前指针回到mark了的指针位置

E:Comparing:buffer 也是java的object sub class,所有buffer也能对象进行比较
public abstract class ByteBuffer
extends Buffer implements Comparable
{
// This is a partial API listing
public boolean equals (Object ob)
public int compareTo (Object ob)
}

两个buffer 认为相等,需要具备下面条件
1.buffer 类型相同
2.buffer里面保留的元素数量相同,buffer capacities不需要相同,这里需要注意保留的元素是指有效的元素,不是指buffer里面有的元素。
其实就从position到limit这段的元素,当然当前的postion和limit值都可以不相同,但这段数据要相同
3.remaining data 的数据顺序要相同,可以通过get()来循环获取出来后判断每个element都相同

如果同时符合上面三个条件,那么就表示buffer object 相等

F:Bulk Moves
如何在buffer中大块的数据移动,对性能起到关键作用
他的做法是get的时候,将一块数据取出放入数组里面,这样比起循环get()一个byte要效率高多了,那么对于块状数据总是有个指定的长度
这个长度就是指定数组的长度
public CharBuffer get (char [] dst, int offset

你可能感兴趣的:(设计模式,多线程,socket,jboss,JSF)