Netty是基于Java NIO的网络应用框架,,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来开发网络应用程序,这种方式使得他很容易使用和很强的扩展性,Netty的内部实现很复杂,但是他提供了简单的API从网络代码中解耦业务逻辑,Netty是完全基于NIO实现的,所以整个Netty都是异步的。
什么是Netty?
Netty提供了高层次抽象来简化TCP和UDP服务器的编程,但仍然可以使用底层的API
Netty的“quick and easy(高性能和简单易用)”并不意味着编写的程序的性能和可维护性会受到影响。从Netty中实现的协议如FTP,SMTP,HTTP,WebSocket,SPDY以及各种二进制和基于文本的传统协议中获得的经验导致Netty的创始人要非常小心它的设计。Netty成功的提供了易于开发,高性能和高稳定性,以及较强的扩展性。
异步设计
整个Netty的API都是异步的,异步不是一个新的机制,这个机制出来已经有一段时间了。
下面就讨论一下最常用的方法
Callbacks(回调)
回调是一般异步处理的一种技术。一个回调是被传递到并且执行完该方法。
1. package netty.in.action;
2.
3. public class Worker {
4.
5. public void doWork() {
6. Fetcher fetcher = new MyFetcher(new Data(1, 0));
7. fetcher.fetchData(new FetcherCallback() {
8. @Override
9. public void onError(Throwable cause) {
10. System.out.println("An error accour: " + cause.getMessage());
11. }
12.
13. @Override
14. public void onData(Data data) {
15. System.out.println("Data received: " + data);
16. }
17. });
18. }
19.
20. public static void main(String[] args) {
21. Worker w = new Worker();
22. w.doWork();
23. }
24.
25. }
1. package netty.in.action;
2.
3. public interface Fetcher {
4. void fetchData(FetcherCallback callback);
5. }
1. package netty.in.action;
2.
3. public class MyFetcher implements Fetcher {
4.
5. final Data data;
6.
7. public MyFetcher(Data data){
8. this.data = data;
9. }
10.
11. @Override
12. public void fetchData(FetcherCallback callback) {
13. try {
14. callback.onData(data);
15. } catch (Exception e) {
16. callback.onError(e);
17. }
18. }
19.
20. }
1. package netty.in.action;
2.
3. public interface FetcherCallback {
4. void onData(Data data) throws Exception;
5. void onError(Throwable cause);
6. }
1. package netty.in.action;
2.
3. public class Data {
4.
5. private int n;
6. private int m;
7.
8. public Data(int n,int m){
9. this.n = n;
10. this.m = m;
11. }
12.
13. @Override
14. public String toString() {
15. int r = n/m;
16. return n + "/" + m +" = " + r;
17. }
18. }
上面只是一个简单的模拟回调,Fetcher,fetchData()方法只需要传递一个FetcherCallBack类型的参数,
当获得数据或发生错误是被回调。对于每种情况都提供了统一方法
- FetcherCallback.onData(),接受数据时被调用
- FetcherCallback.onError(),发生错误时候被调用
因为可以将这些方法执行从“caller”线程移动到其他线程执行,但不会保证FetcherCallback的每个方法都执行。回调过程有问题都是链式调用,很多不同方法会导致线性代码,有人认为链式调用导致代码难以阅读,但这是一种风格和习惯问题。例如,基于Javascript的Node.js越来越受欢迎,它使用了大量的回调,许多人都认为它的这种方式利于阅读和编写。
Futures
第二种技术是Futures。Futures是一个抽象概念,他表示一个值,该值可能在某一点变得可用,一个Futures要么获得完整的计算结果,要不获得计算失败后的异常。Java在juc包中附带了一个Future接口,他使用Executor异步执行。例如下面的代码,每次传递一个Runnable对象到ExecutorService.submit()方法都会得到一个回调的Future,你能检测他是否执行完成
1. package netty.in.action;
2.
3. import java.util.concurrent.Callable;
4. import java.util.concurrent.ExecutorService;
5. import java.util.concurrent.Executors;
6. import java.util.concurrent.Future;
7.
8. public class FutureExample {
9.
10. public static void main(String[] args) throws Exception {
11. ExecutorService executor = Executors.newCachedThreadPool();
12. Runnable task1 = new Runnable() {
13. @Override
14. public void run() {
15. //do something
16. System.out.println("i am task1.....");
17. }
18. };
19. Callable<Integer> task2 = new Callable<Integer>() {
20. @Override
21. public Integer call() throws Exception {
22. //do something
23. return new Integer(100);
24. }
25. };
26. Future<?> f1 = executor.submit(task1);
27. Future<Integer> f2 = executor.submit(task2);
28. System.out.println("task1 is completed? " + f1.isDone());
29. System.out.println("task2 is completed? " + f2.isDone());
30. //waiting task1 completed
31. while(f1.isDone()){
32. System.out.println("task1 completed.");
33. break;
34. }
35. //waiting task2 completed
36. while(f2.isDone()){
37. System.out.println("return value by task2: " + f2.get());
38. break;
39. }
40. }
41.
42. }
Netty核心概念
一个Netty程序开始于Bootstrap类,bootstrap类是Netty提供的一个可以通过简单配置来设置或“引导”程序的一个重要的类。Netty中设计了Handler来处理特定的“event”和设置Netty中的事件,从而来处理多个协议和数据。事件可以描述成一个非常通用的方法,因为你可以自定义一个handler,
ChannelInboundHandler这个类是用来接收消息,当你有消息传送过来,你可以决定怎么处理。当程序需要返回消息时可以在ChannelInboundHandler里write/flush数据。可以任务程序的业务逻辑都是在ChannelInboundHandler中来处理的。
Netty连接客户端端或绑定服务器需要知道如何发送或接收消息,这是通过不同类型的handlers来做的,多个Handlers是怎么配置的?Netty提供了ChannelInitializer类用来配置Handlers。ChannelInitializer是通过ChannelPipeline来添加ChannelHandler的,如发送和接收消息,这些Handlers将确定发的是什么消息。ChannelInitializer自身也是一个ChannelHandler,在添加完其他的handlers之后会自动从ChannelPipeline中删除自己。
所有Netty程序都是基于ChannelPipeline。ChannelPipeline和EventLoop和EventGroup密切相关,因为他们三个都是和事件处理相关,所以这就是为什么他们处理IO的工作由EventLoop管理的原因。
Netty中所有的IO操作都是异步执行的,例如你连接一个主机默认是异步完成的;写入/发送消息也是同样是异步。也就是说操作不会直接执行,而是会等一会执行,因为你不知道返回的操作结果是成功还是失败,但是需要有检查是否成功的方法或者是注册监听来通知;Netty使用Futures和ChannelFutures来达到这种目的。Future注册一个监听,当操作成功或失败时会通知。ChannelFuture封装的是一个操作的相关信息,操作被执行时会立刻返回ChannelFuture。
什么是Bootstarp?
“引导”是Netty中配置程序的过程,当你需要连接客户端或服务器绑定指定端口时需要使用bootstrap。如前面所述,“引导”有两种类型,一种是用于客户端的Bootstrap(也适用于DatagramChannel),一种是用于服务端的ServerBootstrap。不管程序使用哪种协议,无论是创建一个客户端还是服务器都需要使用“引导”。
两种bootsstraps之间有一些相似之处,其实他们有很多相似之处,也有一些不同。Bootstrap和ServerBootstrap之间的差异:
• Bootstrap用来连接远程主机,有1个EventLoopGroup
• ServerBootstrap用来绑定本地端口,有2个EventLoopGroup
事件组(Groups),传输(transports)和处理程序(handlers)分别在本章后面讲述,我们在这里只讨论两种"引导"的差异(Bootstrap和ServerBootstrap)。第一个差异很明显,“ServerBootstrap”监听在服务器监听一个端口轮询客户端的“Bootstrap”或DatagramChannel是否连接服务器。通常需要调用“Bootstrap”类的connect()方法,但是也可以先调用bind()再调用connect()进行连接,之后使用的Channel包含在bind()返回的ChannelFuture中。
第二个差别也许是最重要的。客户端bootstraps/applications使用一个单例EventLoopGroup,而ServerBootstrap使用2个EventLoopGroup(实际上使用的是相同的实例),它可能不是显而易见的,但是它是个好的方案。一个ServerBootstrap可以认为有2个channels组,第一组包含一个单例ServerChannel,代表持有一个绑定了本地端口的socket;第二组包含所有的Channel,代表服务器已接受了的连接。
ChannelPipeline
ChannelPipeline是ChannelHandler实例的列表,用于处理或者截获通道的接受和发送数据。ChannelPipeline提供了一种高级的截取过滤器模式,让用户可以在ChannelPipeline中完全控制一个事件及如何处理ChannelHandler与ChannelPipeline的交互。