Netty简介
Netty框架
Netty是一个NIO(Non-blocking IO或New IO)的客户端服务器框架,它可以快速和简单开发高性能、高可靠性的网络应用,极大地简化和提高网络编程的过程和效率。快速和简单并不会导致网络应用出现性能和维护性问题,Netty吸收了多种协议的实现经验,比如FTP、SMTP、HTTP、各种二进制和文本协议;最终,Netty成功找到一种方式,在保证易于开发的同时还保证了其应用的性能、稳定性和扩展性。
使用Netty的好处
- 整个Netty都是异步的(Asynchronous),因此客户端不需要为线程被阻塞在等待服务器响应而浪费资源,因此系统的性能会提高
- Netty中是将Java网络编程API高度抽象化成简单易用的API,以致程序员可以从繁琐的网络处理代码中解耦业务逻辑
- Netty的功能丰富以及有完整的文档
Netty中所使用的关键技术
异步技术
Netty整个框架的API都是异步的,异步处理能够使得系统更加有效地使用资源,它允许你创建一个任务,当有事件发生时将获取通知并等待事件的完成,不管事件完成与否都会及时返回,系统就可以利用剩下的资源做其他事情,提高了资源的利用率。
1. 回调 Callback
回调是异步处理的其中一种实现技术。因为Java没有C/C++的函数指针或函数对象概念,所以不能够把函数当作对象传到别处进行调用。因此要在Java中实现回调,一般都用接口实现,即回调接口中有回调方法,真正使用的时候便是传递该回调接口的实现类。
下面是一个回调技术演示的简单例子
创建回调接口
1
2
3
4
5
6public interface FetcherCallback {
// 当操作或接收数据时被调用
void onDataAccess(Data data) throws Exception;
// 当有异常出现时被调用
void onError(Throwable cause);
}创建一个关于获取数据的业务逻辑接口, 获取数据方法接收回调对象
1
2
3public interface Fetcher {
void fetchData(FetcherCallback callback);
}实现获取数据业务逻辑接口, 根据不同的情况, 使用回调对象调用不同的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class MyFetcher implements Fetcher {
final Data data;
public MyFetcher(Data data) {
this.data = data;
}
public void fetchData(FetcherCallback callback) {
try {
callback.onDataAccess(data);
} catch (Exception e) {
callback.onError(e);
}
}
}定义Data类
1
2
3
4
5
6
7
8
9
10
11
12
13
14public class Data {
private int n;
private int m;
public Data(int n, int m) {
this.n = n;
this.m = m;
}
public String toString() {
return n + " / " + m + " = " + (n / m);
}
}创建业务逻辑类实例进行业务处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78public class Worker {
public void doWork() {
Fetcher fetcher = new MyFetcher(new Data(1, 0));
// 创建并传入回调对象到业务方法中, 并完成回调方法的具体实现
fetcher.fetchData(new FetcherCallback() {
public void onDataAccess(Data data) throws Exception {
System.out.println("Data received: " + data);
}
public void onError(Throwable cause) {
System.out.println("An error accour: " + cause.getMessage());
}
});
}
public static void main(String[] args) {
Worker worker = new Worker();
worker.doWork();
}
}
```
> 从上面的示例我们可以得出使用回调技术的步骤
1. 创建一个回调接口, 里面有若干个针对业务逻辑不同情况的回调方法
2. 创建一个类并实现回调接口,该类所创建的对象就称作回调对象或函数对象了
3. 创建业务类(业务接口不是必须的,还是要根据实际情况而定),通过业务类的构造方法或业务方法接收回调对象,在业务方法中,根据自己设定的不同情况下进而使用回调对象调用对应回调方法
**2. Future**
如果你学习过Java,并且使用过Java写过并发程序,或许你就用过java.util.concurrent.Future这一个接口。但是不只局限于Java,其实Future是一种并发设计模式。Future模式是"生产者-消费者"模型的扩展,经典的"生产者-消费者"模型中的消息生产者不关心消费者何时处理完这条消息,也不关心处理结果;但Future模式则可以让消息生产者等待直到消费者对消息处理结束,还能取得处理结果。
- **Java中Future的具体工作原理**
Future对象本身可以看作是一个对异步处理结果的引用;由于其异步性质,在创建之初,它所引用的对象可能并不可用(比如在网络传输或等待、一些耗时运算);这时,得到Future对象的程序流程如果并不急于使用Future所引用的对象,那么它可以处理其它任务,当程序流程运行到真正需要Future背后引用的对象时,<font color="red">可能出现两种情况</font>:
> - 程序流程希望可以使用该Future引用的对象,并以此继续完成后续的流程;若实在不可用,也可以进入其它分支流程
> - 程序流程一定要使用该Future引用的对象,所以会一直等待,或者会等到超时
对于第一种情况,是通过调用isDone()判断该对象是否准备就绪,并采取不同的处理;而第二种情况则只需调用get()或get(long timeout, TimeUnit unit)通过同步阻塞方式等待对象就绪。
以下是代码示例
```java
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newCachedThreadPool();
Runnable task1 = new Runnable() {
public void run() {
System.out.println("task1 -> run!");
}
};
Callable<Integer> task2 = new Callable<Integer>() {
public Integer call() throws Exception {
System.out.println("task2 -> run!");
return new Integer(100);
}
};
Future<?> f1 = executor.submit(task1);
Future<Integer> f2 = executor.submit(task2);
System.out.println("task1 is completed? " + f1.isDone());
System.out.println("task2 is completed? " + f2.isDone());
// 等待任务1的完成
while (f1.isDone()) {
System.out.println("task1 completed!");
break;
}
// 等待任务2的完成
while (f2.isDone()) {
System.out.println("return value by task2: " + f2.get());
break;
}
}
}
非阻塞IO技术
Java中,非阻塞IO即NIO(Non-blocking IO或New IO),在jdk1.4时提供的新API,它是基于通道(Channel)和缓冲区(Buffer),支持锁和内存映射文件访问,提供多路非阻塞式的高伸缩性网络IO
Java的Blocking IO和Non-blocking IO的差异
内部概念
Java的阻塞IO使用的是流(Stream)的概念,而Java的非阻塞IO使用的是通道和缓冲区的概念
性能差异
若在一个线程下使用read()或write()等阻塞IO的API,则该线程就会被阻塞,直到那么数据完全被读取或者写入,该线程才能继续往下执行其他操作;而使用非阻塞IO时,线程不需等待IO操作完全地完成,它可以去做别的事情。
最重要的就是这两个区别,其他区别可以查看NIO相关的文章。
Java NIO的问题以及在Netty中是如何解决的
跨平台和兼容性问题
NIO是一个比较底层的API,它依赖操作系统IO的API,因此有可能发现代码在Linux上正常运行,但在Windows上就会出现问题,因此这个问题需要自己亲自验证。
Java7提供了NIO2的API,但是有的程序本身就是基于Java1.6开发的,这就无法使用NIO2了,并且NIO2没有提供DatagramSocket的支持,所以NIO2不支持UDP程序;而Netty提供了统一的API,同一功能下,无论是Java6还是Java7都能正常运行,使得开发人员无需关心API兼容性的问题。
扩展ByteBuffer
Java NIO中的ByteBuffer构造函数是私有的,因此它是不可以被自定义扩展的,如果开发人员希望尽量减少内存拷贝,这是不可能的;但Netty提供了另一种ByteBuffer实现,Netty通过简单的API就能对ByteBuffer进行构造和使用,以此解决NIO的一些限制。
NIO对缓冲区的聚合和分散操作有可能造成内存泄露
很多NIO的通道都实现支持Gather和Scatter操作,但是这可能会导致内存泄露,Java7解决了这一个问题;但Netty统一了API,因此Netty无论在Java6或7环境中使用,都不会有问题。
著名的epoll缺陷
Linux-like OSs的选择器使用的是epoll-IO事件通知工具。这是一个在操作系统以异步方式工作的网络stack.Unfortunately,即使是现在,著名的epoll-bug也可能会导致无效的状态的选择和100%的CPU利用率。要解决epoll-bug的唯一方法是回收旧的选择器,将先前注册的通道实例转移到新创建的选择器上。而Netty解决了这以一问题,开发人员无需再担心。