netty是JBOSS针对网络开发的一套应用框架,它也是在NIO的基础上发展起来的。netty基于异步的事件驱动,具有高性能、高扩展性等特性,它提供了统一的底层协议接口,使得开发者从底层的网络协议(比如 TCP/IP、UDP)中解脱出来。在互联网的应用中有一个特点,高并发,但处理逻辑相对简单。针对这个特别有时间我们可以相对的简化下平时所有的WEB服务器,来满足这一的特点,而且更方便控制。Netty就是一个不错的选择,Netty 提供异步的、事件驱动的网络应用程 另外与Spring一起使用,以快速开发高性能、高可靠性的网络服务器和客户端程序。
一,开始(Getting Started)
1.2 写一个不接收数据的服务器(writing a discard server)
服务端处理器:
package org.jboss.netty.example.discard;
@ChannelPipelineCoverage("all") //此注解用于说明处理器的类型,告诉这种类型的处理器是否能够被多余1 //个channel共享
public class DiscardServerHandler extends SimpleChannelHandler//SimpleChannelHandler提供了各种各样//的处理器方法,你继承它后可以不用去实现ChannelHandler的所有事件处理器方法。
{
@Override
//此方法中得MessageEvent参数包含了接收到的信息
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
{
}
@Override
//此方法带有一个ExceptionEvent参数,在netty由于I/O错误发起异常或者由一个处理器实现程序抛出异常的时//候被调用。
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
Channel ch = e.getChannel();
ch.close();
}
}
启动服务端处理器的主方法:
package org.jboss.netty.example.discard;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class DiscardServer {
public static void main(String[] args) throws Exception {
ChannelFactory factory = //ChannelFactory是一个工厂用于创建和管理Channels和它的相关资源。它会处理所有的I/O请求并生成ChannelEvents.NioServerSocketChannelFacroty是ChannelFacroty的一个实现,它不会自主创建I/O线程,因此它需要从你通过构造器传递给它的线程池中获得线程。
new NioServerSocketChannelFactory (
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ServerBootstrap bootstrap = new ServerBootstrap (factory);//ServerBootStrap是一个帮助类,用来设置服务器。
DiscardServerHandler handler = new DiscardServerHandler();
ChannelPipeline pipeline = bootstrap.getPipeline();
pipeline.addLast("handler", handler);//我们增加DiscardServerHandler到默认的ChannelPipeline,无论什么时候,一个新的连接被服务器接受,一个新的ChannelPipeline将会被创建给新接受的Channel,在这里添加的所有ChannelHandler(即服务端处理器)将会被添加给新创建的ChannelPipeline.所有的Channel和他们的ChannelPipelines将会共享同一个DiscardServerHandler实例。
bootstrap.setOption("child.tcpNoDelay", true);//这里设置tcpNoDelay和keepAlive参数,前面的child前缀必须要加上,用来指明这个参数将被应用到接收到的Channels,而不是设置的ServerSocketChannel.ServerSocketChannel的设置是bootstrap.setOption("reuserAddress",true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.bind(new InetSocketAddress(8080));//绑定端口
}
}
1.3 研究接收到的数据(Looking into the Received Data)
1.4 写一个echo 服务器(writing an echo server)
1.5 写一个时间服务器(writing a time server)
1.6 写一个时间客户端(writing a time client)
用Netty写一个服务器和客户端最大的不同点就是在于不同的Bootstrap和ChannelFactory被需要。
package org.jboss.netty.example.time;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = args[0];
int port = Integer.parseInt(args[1]);
ChannelFactory factory =
new NioClientSocketChannelFactory (
Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ClientBootstrap bootstrap = new ClientBootstrap (factory);
TimeClientHandler handler = new TimeClientHandler();
bootstrap.getPipeline().addLast("handler", handler);
bootstrap.setOption("tcpNoDelay" , true);
bootstrap.setOption("keepAlive", true);
bootstrap.connect (new InetSocketAddress(host, port));
}
}
1,NioClientSocketChannelFactory 在写客户端程序的时候 代替了NioServerSocketChannelFactory ,被使用来创建客户端的Channel.
2,注意在客户端边设置Channel时,参数名不用加上"child."前缀。因为在客户端SocketChannel没有父对象。
package org.jboss.netty.example.time;
import java.util.Date;
@ChannelPipelineCoverage("all")
public class TimeClientHandler extends SimpleChannelHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
ChannelBuffer buf = (ChannelBuffer) e.getMessage();
long currentTimeMillis = buf.readInt() * 1000L;
System.out.println(new Date(currentTimeMillis));
e.getChannel().close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
e.getCause().printStackTrace();
e.getChannel().close();
}
}
这个客户端处理器看上去很简单,不过有些时候会抛出IndexOutOfBoundsException异常而拒绝工作。其原因会在过后讨论。
1.7 处理基于流的传输(dealing with a stream-based transport)
1.7.1 套接字缓存的一个小的警告(one small caveat of socket buffer)
1.7.2 第一个解决方案
1.7.3 第二个解决方案
1.8 在pojo中输出而不是用ChannelBuffer(speaking in pojo instead of ChannelBuffer)
1.9 关闭你的应用程序(shutting down your application)
1.10 小结(summary)
第二章 netty结构概略(Architectural Overview)
2.1 充足的缓存数据结构(rich buffer data structure)
---ChannelBuffer
2.2 Universal Asynchronous I/O API
Netty有一个普遍的异步I/O接口---Channel,它抽象出所有点对点交互需要的操作。
That is, once you wrote your application on one Netty transport,
your application can run on other Netty transports. Netty provides a number of essential transports via one
universal API:
• NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),
• OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),
• OIO-based UDP/IP transport, and
• Local transport (See org.jboss.netty.channel.local).
2.3 基于拦截链模式的事件模型(event model based on the interceptor chain pattern)
2.4 advanced components for more rapid development
2.4.1 codec framework
2.4.2 ssl/tls 支持
2.4.3 HTTP 实现
2.4.4 google 协议缓冲整合(google protocol buffer integration)
2.5 总结
Netty 由三个组件构成:buffer , channel , event model。其他高级功能是建立在这三个核心组件之上。
Netty研究心得:
网络应用系统,相互之间通信的过程概括出来也就如下所述:
客户端:
编辑数据
编码数据
发送数据
接到回复数据并解析数据
处理数据
服务端:
监听端口
接收到数据并解析数据
处理数据
编码数据
发送数据
对于用netty开发网络应用程序概述:
1,开发网络应用的服务端:
(1)首先需要针对网络应用所基于的协议创建BootStrap(它主要是用来创建Channel,并提供必备的数据结构给它的子类使用。其与子类结构如下:
)。
如果网络应用基于的是TCP/IP类具有连接性的协议,则应该使用ServerBootstrap,如果是基于UDP/IP这类无连接的协议,则使用ConnectionlessBootstrap。
此处我们介绍ServerBootstrap类,这个类是一个帮助类,主要用来创建一个服务端的Channel(通道),用来接受连接进入的连接。这个Channel被称作parent channel,它是被bootstrap的ChannelFactory通过调用bind()和bind(SocketAddress)来创建的。
一旦成功绑定,parent channel就会开始监听接口,接受访问的连接,被接受的访问连接将会变成parent channel的child channel。
2,配置channels
Options 被使用来配置parent channel和它的child channels。为了能够配置child channels,需要在option name(配置属性名)前面加上“child.”前缀。如下:
ServerBootstrap b = ...;
// Options for a parent channel
b.setOption("localAddress", new InetSocketAddress(8080));
b.setOption("reuseAddress", true);
// Options for its children
b.setOption("child.tcpNoDelay", true);
b.setOption("child.receiveBufferSize", 1048576);
配置parent channel pipeline(流水线)
定制一个parent channel的pipeline基本上是很少的情况,因为它需要做的都是很典型。然而, 你可能想要增加一个处理器来处理一些特别的需要,例如将进程的UID从一个超级使用者降到一个普通使用者或者改变当前VM的安全管理器到更好的安全。为了支持这种情况,我们可以通过bootstrap的setOption方法配置"parentHandler"属性。
配置child channel pipeline(流水线)
每个child channel都由它自己的ChannelPipeline(即Channel流水线),你能够通过两种方法配置它。第一个方法就是使用默认的pipeline(流水线),让bootstrap将默认的pipeline浅拷贝给每个新的child channel。配置如下:
ServerBootstrap b = ...;
ChannelPipeline p = b.getPipeline();
// Add handlers to the pipeline.
p.addLast("encoder", new EncodingHandler());
p.addLast("decoder", new DecodingHandler());
p.addLast("logic", new LogicHandler());
注意:这里的是“潜复制”,意味着被增加到默认的pipeline中的ChannelHandlers不是被克隆而只是将他们的引用增加到新的child channel的pipeline中。因此,如果你准备接受多个child channel,他们的ChannelPipeline包含了一些它们的ChannelPipelineCoverage为"one"的ChannelHandler时,你不得不选择第二方法。
(注释:ChannelPipelineCoverage annotation用来定义被注释的ChannelHandler的同一个实例是否能够被添加到多个ChannelPipeline中,供多个Channel使用)。
第二种方法就是定义一个ChannelPipelineFactory(此接口中只定义了一个方法getPipeline()),完全控制去从新创建一个新的pipeline。这个方法比第一个方法复杂,但是更加灵活,如下:
ServerBootstrap b = ...;
b.setPipelineFactory(new MyPipelineFactory());
public class MyPipelineFactory implements ChannelPipelineFactory {
// Create a new pipeline for a new child channel and configure it here ...
}
应用不同的设置到不同的Channels上(applying different settings for different Channels)
ServerBootstrap仅仅是一个帮助类。它既不能分配资源,也不能管理资源。管理资源的是在ServerBootstrap构造器(即ServerBootstrap(ChannelFactory channelFactory))中定义的ChannelFactory实现类实例。因此,你可以创建多个ServerBootstrap实例,并且可以将他们配置成不同的设置,将不同的设置应用到不同的Channels上。
(2)创建ChannelHandler
ChannelHandler用来处理和拦截一个ChannelEvent并发送一个ChannelEvent给在ChannelPipeline里的下一个处理器。
子类型(Sub-types)
ChannelHandler它自己不会提供任何方法,它是一个接口。为了处理ChannelEvent你需要实现它的子接口。有两个子接口用来处理接收到的事件,一个是用于upstream 事件,另一个是用于downstream 事件。
ChannelUpstreamHandler :用来处理和拦截一个upstream ChannelEvent.
ChannelDownStreamHandler:用来处理和拦截一个downstream ChannelEvent.
(这里的上行,下行方向是依据消息在服务器的位置决定的,消息从服务器外网服务器内传就为上行,消息从服务器内往服务器外传播就为下行。)
上下文对象(the context object)
一个ChannelHandler会被提供一个ChannelHandlerContext对象,ChannelHandler可以通过这个上下文对象与它所属的ChannelPipeline进行通信,如它能将事件向上或者向下传递,修改那个pipeline的动作或者存储ChannelHandler的某些具体信息或者附件。
ChannelPipeline接口:
创建一个pipeline(creation of a pipeline)
建议使用帮助类Channels来创建一个新的pipeline,而不是调用自己实现的构造器来创建。
(Channels是一个帮助类,用于提供与Channel,ChannelHandler,ChannelPipeline有关的各种方便的方法。)
在一个pipeline中得一个事件流:(How an event flows in a pipeline)
如上图描述了ChannelEvents是如何在ChannelPipeline中被ChannelHandlers处理的。一个ChannelEvent能够被或者ChannelUpstreamHandler或者ChannelDownstreamHandler处理,也能靠调用ChannelHandlerContext.sendUpstream(ChannelEvent)或ChannelHandlerContext.sendDownstream(ChannelEvent)来将ChannelEvent传递给邻近的handler处理。
一个上行事件(即接收到的事件)从下往上进行处理,如果超出了最顶层的upstream handler,事件将会默默的被丢弃。
一个下行事件(即往外发送的事件)从上往下处理,在下行事件超出最顶层的downstream handler时,它将会被与Channel有联系的I/O线程进行处理。
如下创建一个pipeline:
ChannelPipeline p = 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 UpstreamHandlerX());
在如上的例子中,标有Upstream的类为上行handler,标有downstream的handler类为下行handler。
在被给的例子中,当一个事件上行时,handlers的处理顺序是12345(即addLast()方法是越往内部添加处理器)。当一个事件下行时,handlers的处理顺寻则是54321。不过在上面定义的流水线中,某些handlers在某些事件处理流中会被跳过:
1, 3和4没有实现ChannelUpstreamHandler接口,因此它们不能处理上行事件,在上行事件流中,handlers的处理顺序则是125。
2, 125没有实现ChannelDownstreamHandler,因此它们不能处理下行事件,所以在下行事件流中,handlers的处理顺序是43。
3, 假如5继承了SimpleChannelHandler类(此类既继承了ChannelUpstreamHandler还继承了ChannelDownstreamHandler),因此5既能处理上行事件也能处理下行事件,所以handlers处理上行事件流的顺序是125,处理下行事件流的顺序是543。
建立一个pipeline(building a pipeline)
一个使用者在一个pipeline中应该需要多个ChannelHandlers来接收I/O事件和请求I/O处理。例如,一个典型的服务端将会再每个Channel的pipeline中设置如下的handlers,其他的handler可以根据自身业务需要进行添加。
1 Protocol Decoder - translates binary data (e.g. ChannelBuffer) into a Java object.
2 Protocol Encoder - translates a Java object into binary data.
3 ExecutionHandler- applies a thread model.
4 Business Logic Handler - performs the actual business logic (e.g. database access).
如上例子能够被如下代码展示出来:
ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
pipeline.addLast("executor", new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576)));
pipeline.addLast("handler", new MyBusinessLogicHandler());
ChannelPipeline是线程安全的。
Channel类:
它是一个与一个网络socket或者一个有I/O操作能力如读,写,连接和绑定等的组件的枢纽。
一个Channel能够提供给一个用户:
(1)channel的当前状态(如open? Or connected?)
(2)channel的配置参数(如:接收数据的缓冲大小)
(3)channel支持的I/O操作(如read,write,connect,bing);
(4)用于处理与channel有关的所有I/O事件和请求的ChannelPipeline。
提供的所有I/O操作都是异步的:
在netty中所有的I/O操作都是异步的。也就意味着,任何一个I/O操作将会立刻返回,并不会保证在方法调用结束后就能保证请求的I/O操作就会完成,代替的是,你将会获得一个ChannelFuture实例,它将会通知你那个请求的I/O操作将在什么时候成功或者失败或者被取消。
Channels是分等级的(Channels are hierarchical):
一个Channel能够拥有一个父亲,这依据它是如何被创建的。例如,一个由ServerSocketChannel创建的SocketChannel,在SocketChannel调用getParent()方法时,将会返回ServerSocketChannel。
那个分等级结构语义上依赖于Channel所属的传输实现。例如,你能够写一个用于创建sub-channels的Channel实现,这些被创建的sub-channels会共享同一个socket connetion,就如同BEEP和SSH。
InterestOps:
一个Channel会有一个属性,叫做interestOps,它类似于NIO SelectionKey。它由两个标识构成的二进制域组成。
OP_READ:假如属性设置成这个值,那么一条由远端节点发送过来的消息就会被立刻读取。假如没有设置此值,则来自远端的消息将不会被读取,会直到OP_READ被再次设置。
OP_WRITE:如果是这个值,那么一个写请求将不会立刻被送到远端,而是会直到OP_WRITE标识被清除,在这之前写请求将会被阻塞在一个队列中。如果不是此值,写请求将会立马被flush出去。
OP_READ_WRITE:这是一个OP_READ和OP_WRITE的结合,意味着只有写请求会被暂停。
OP_NONE:这个一个NOT OP_READ和NOT OP_WRITE的结合,意味着只有读操作会被阻塞。
ExecutionHandler类:
这个类的作用是用来将一个上行ChannelEvent传递给一个Executor(执行器)。
靠增加这个处理器到ChannelPipeline中,你能够实现各种各样的线程模型。最经常的用法就是增加用OrderedMemoryAwareThreadPoolExecutor构造的ExecutionHandler。如下:
ChannelPipeline pipeline = ...;
pipeline.addLast("decoder", new MyProtocolDecoder());
pipeline.addLast("encoder", new MyProtocolEncoder());
// HERE
pipeline.addLast("executor", new ExecutionHandler(new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576)));
pipeline.addLast("handler", new MyBusinessLogicHandler());
这样做主要是为了利用更多的处理器(processor)来处理ChannelEvents。你也可以使用别的Executor实现来构造ExecutionHandler。
ExternalResourceReleasable接口:
实现了它的类代表,该类需要依赖于外部资源,并且这些资源是需要被明确的释放和关闭的。
它只声明了一个方法:releaseExternalResources(),此方法用来释放该对象依赖的外部资源。
如下是示例代码:
discardServer.java
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.socket.nio.NioServerSocketChannelFactory;
public class DiscardServer {
public static void main(String[] args)
{
ChannelFactory factory =new NioServerSocketChannelFactory (Executors.newCachedThreadPool(),Executors.newCachedThreadPool());
ServerBootstrap bootstrap = new ServerBootstrap (factory);
DiscardServerHandler handler = new DiscardServerHandler();
ChannelPipeline pipeline = bootstrap.getPipeline();
pipeline.addLast("handler", handler);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.bind(new InetSocketAddress(8080));
}
}
discardServerHandler.java
import java.io.IOException;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
@ChannelPipelineCoverage("all")
public class DiscardServerHandler extends SimpleChannelHandler {
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
Channel ch=e.getChannel();
ch.write(e.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws IOException {
e.getCause().printStackTrace();
Channel ch = (Channel) e.getChannel();
ch.close();
}
}