作为通用库使用
Netty 是用于构建网络应用程序的框架,但它还提供了一些基础类,即使不执行套接字 I/O 的程序也可以使用,这些类非常方便。
io.netty.buffer
提供了一种名为 ByteBuf
的通用缓冲区类型。它类似于 java.nio.ByteBuffer
,但速度更快、更易于使用且可扩展。
您是否曾经忘记调用 java.nio.ByteBuffer.flip()
,并想知道为什么缓冲区中不包含任何内容?在 ByteBuf
中永远不会发生这种情况,因为它有两个索引,一个用于读取,另一个用于写入
ByteBuf buf = ...;
buf.writeUnsignedInt(42);
assertThat(buf.readUnsignedInt(), is(42));
它有一组更丰富的访问方法,可以更轻松地访问缓冲区的内容。例如,它具有用于有符号和无符号整数、搜索和字符串的访问器方法。
您无法对 java.nio.ByteBuffer
进行子类化,但您可以对 ByteBuf
进行子类化。还为您提供了抽象骨架实现,以方便您使用。因此,您可以编写自己的缓冲区实现,例如文件支持的、复合的,甚至混合的。
分配新的 java.nio.ByteBuffer
时,其内容将填充为零。此“清零”消耗 CPU 周期和内存带宽。通常,缓冲区会立即从某个数据源填充,因此清零没有任何好处。
要回收,java.nio.ByteBuffer
依赖于 JVM 垃圾收集器。它适用于堆缓冲区,但不适用于直接缓冲区。根据设计,直接缓冲区预计会存在很长时间。因此,分配许多短寿命的直接 NIO 缓冲区通常会导致 OutOfMemoryError
。此外,使用(隐藏的专有)API 显式释放直接缓冲区并不是很快。
ByteBuf
的生命周期与其引用计数绑定。当其计数变为零时,其底层内存区域(byte[]
或直接缓冲区)将被显式取消引用、释放或返回到池中。
Netty 还提供了一个可靠的缓冲池实现,它不会浪费 CPU 周期或内存带宽来清零其缓冲区
ByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
ByteBuf buf = alloc.directBuffer(1024);
...
buf.release(); // The direct buffer is returned to the pool.
但是,引用计数并不是万能的。如果 JVM 在其底层内存区域返回池之前对池缓冲区进行垃圾回收,则泄漏最终会耗尽池。
为了帮助您解决泄漏问题,Netty 提供了一种泄漏检测机制,它足够灵活,可以权衡应用程序的性能和泄漏报告的详细信息。有关更多信息,请参阅 引用计数对象。
异步执行任务(计划任务并在完成时得到通知)很常见,而且应该很容易。当java.util.concurrent.Future
首次出现时,我们的兴奋并没有持续多久。我们不得不阻塞才能在完成时得到通知。在异步编程中,你指定在完成时做什么,而不是等待结果。
io.netty.concurrent.Future
是 JDK Future
的子类型。它允许你添加一个侦听器,当 future 完成时,该侦听器将被事件循环调用。
io.netty.util.concurrent.EventExecutor
是一个单线程事件循环,它扩展了java.util.concurrent.ScheduledExecutorService
。你可以构建自己的事件循环或将其用作功能丰富的任务执行器。通常,你创建多个EventExecutor
来利用并行性
EventExecutorGroup group = new DefaultEventExecutorGroup(4); // 4 threads
Future<?> f = group.submit(new Runnable() { ... });
f.addListener(new FutureListener<?> {
public void operationComplete(Future<?> f) {
..
}
});
...
有时你想要一个始终可用的唯一执行器,并且不需要生命周期管理。GlobalEventExecutor
是一个单线程单例EventExecutor
,它会延迟启动其线程,并在一段时间内没有待处理的任务时停止。
GlobalEventExecutor.INSTANCE.execute(new Runnable() { ... });
在内部,Netty 使用它来通知其他EventExecutor
的终止。
请注意,此功能仅供内部使用。如果需求足够,我们正在考虑将其移出内部包。
io.netty.util.internal.PlatformDependent
提供与平台相关的和潜在不安全的操作。你可以将其视为sun.misc.Unsafe
和其他与平台相关的专有 API 的一层薄薄的层。
为了构建一个高性能的网络应用程序框架,我们引入了实用程序。你可能会发现一些有用的东西。
如果你的线程是长时间运行的,并且你分配了许多同类型的短生存期对象,则可以使用称为Recycler
的线程局部对象池。它减少了你产生的垃圾数量,从而节省了内存带宽消耗和垃圾收集器的负载。
public class MyObject {
private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
protected MyObject newObject(Recycler.Handle<MyObject> handle) {
return new MyObject(handle);
}
}
public static MyObject newInstance(int a, String b) {
MyObject obj = RECYCLER.get();
obj.myFieldA = a;
obj.myFieldB = b;
return obj;
}
private final Recycler.Handle<MyObject> handle;
private int myFieldA;
private String myFieldB;
private MyObject(Handle<MyObject> handle) {
this.handle = handle;
}
public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}
}
MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();
enum
非常适合静态常量集,但你无法扩展它。当需要在运行时添加更多常量或允许第三方定义其他常量时,请改用可扩展的io.netty.util.ConstantPool
public final class Foo extends AbstractConstant<Foo> {
Foo(int id, String name) {
super(id, name);
}
}
public final class MyConstants {
private static final ConstantPool<Foo> pool = new ConstantPool<Foo>() {
@Override
protected Foo newConstant(int id, String name) {
return new Foo(id, name);
}
};
public static Foo valueOf(String name) {
return pool.valueOf(name);
}
public static final Foo A = valueOf("A");
public static final Foo B = valueOf("B");
}
private final class YourConstants {
public static final Foo C = MyConstants.valueOf("C");
public static final Foo D = MyConstants.valueOf("D");
}
Netty 使用ConstantPool
来定义ChannelOption
,以便非核心传输可以以类型安全的方式定义特定于传输的选项。
使用io.netty.util.AttributeMap
接口来快速、类型安全、线程安全地收集键值对
public class Foo extends DefaultAttributeMap {
...
}
public static final AttributeKey<String> ATTR_A = AttributeKey.valueOf("A");
public static final AttributeKey<Integer> ATTR_B = AttributeKey.valueOf("B");
Foo o = ...;
o.attr(ATTR_A).set("foo");
o.attr(ATTR_B).set(42);
正如你可能已经注意到的,AttributeKey
是一个Constant
。
哈希轮计时器是java.util.Timer
和java.util.concurrent.ScheduledThreadPoolExecutor
的可扩展替代方案。它可以高效地处理许多计划任务及其取消,如下表所示
计划新任务 | 取消任务 | |
---|---|---|
HashedWheelTimer |
O(1) | O(1) |
java.util.Timer 和 ScheduledThreadPoolExecutor |
O(logN) | O(logN),其中 N = 待处理的任务数 |
在内部,它使用哈希表,其键是任务的计时,从而为大多数计时器操作产生恒定时间。(java.util.Timer
使用二进制堆。)
有关哈希轮计时器的更多信息,请参阅 这些幻灯片(“哈希和分层计时轮”,Dharmapurikar) 和 此论文(“哈希和分层计时轮:用于高效实现计时器工具的数据结构”,Varghese 和 Lauck)。
以下类很有用,但您会在其他库(如 Guava)中找到不错的替代品
-
io.netty.util.CharsetUtil
提供常用的java.nio.charset.Charset
。 -
io.netty.util.NetUtil
提供常用的与网络相关的常量,如 IPv4 和 IPv6 环回地址的InetAddress
。 -
io.netty.util.DefaultThreadFactory
是一个通用的ThreadFactory
实现,可让您轻松配置执行器线程。
由于 Netty 尝试最大程度地减少其依赖项集,因此其一些实用程序类与其他流行库(如 Guava)中的类类似。
此类库提供各种实用程序类和备用数据类型,以减轻使用 JDK API 的痛苦,并且它们通常做得很好。
Netty 专注于提供以下所需的构造:
- 异步编程
- 低级操作(又称“机械同情”),如
- 堆外访问
- 访问专有固有操作
- 与平台相关的行为
Java 有时会通过采用包含 Netty 提供的构造的思想来进步。例如,JDK 8 添加了 CompletableFuture
,它在某种程度上与 io.netty.util.concurrent.Future
重叠。在这种情况下,Netty 的构造为您提供了良好的迁移路径;我们会勤勉地更新 API,以考虑未来的迁移。