简介
UDT 是一个非常优秀的协议,可以提供在 UDP 协议基础上进行高速数据传输。但是可惜的是在 netty 4.1.7 中,UDT 传输协议已经被标记为 Deprecated 了!
意味着在后面的 netty 版本中,你可能再也看不到 UDT 协议了.
优秀的协议怎么能够被埋没,让我们揭开 UDT 的面纱,展示其优秀的特性,让 netty 再爱 UDT 一次吧。
netty 对 UDT 的支持
netty 对 UDT 的支持体现在有一个专门的 UDT 包来处理 UDT 相关事情:package io.netty.channel.udt。
这个包里面主要定义了 UDT 的各种 channel、channel 配置、UDT 消息和提供 ChannelFactory 和 SelectorProvider 的工具类 NioUdtProvider.
搭建一个支持 UDT 的 netty 服务
按照 netty 的标准流程,现在是需要创建一个 netty 服务的时候了。
netty 创建 server 服务无非就是创建 EventLoop、创建 ServerBootstrap、绑定 EventLoop、指定 channel 类型就完了,非常的简单。
唯一不同的就是具体的 childHandler,可能根据具体协议的不同使用不同的处理方式。
当然,如果不是 NioSocketChannel,那么对应的 ChannelFactory 和 SelectorProvider 也会有所变化。
没关系,我们先看下如何创建支持 UDT 的 netty 服务:
final ThreadFactory acceptFactory = new DefaultThreadFactory("accept");
final ThreadFactory connectFactory = new DefaultThreadFactory("connect");
final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory, NioUdtProvider.BYTE_PROVIDER);
final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, connectFactory, NioUdtProvider.BYTE_PROVIDER);
final ServerBootstrap boot = new ServerBootstrap();
boot.group(acceptGroup, connectGroup)
.channelFactory(NioUdtProvider.BYTE_ACCEPTOR)
.option(ChannelOption.SO_BACKLOG, 10)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<UdtChannel>() {
@Override
public void initChannel(final UdtChannel ch) {
ch.pipeline().addLast(
new LoggingHandler(LogLevel.INFO),
new UDTEchoServerHandler());
}
});
// 开启服务
final ChannelFuture future = boot.bind(PORT).sync();
复制代码
可以看到,UDT 和普通 netty socket 服务不同的地方在于它的 selector 和 channelFactory 都是由 NioUdtProvider 来提供了。
NioUdtProvider 是 netty 核心包中的内容,他提供了对 UDT 的有用封装,我们不需要要懂太多 UDT 内部的实现,就可以使用 UDT 协议,是不是很美妙。
异常来袭
如果有小伙伴兴冲冲的拿上面这段代码去尝试运行,那么很可惜你会得到异常,异常大概类似下面的情况:
什么?直接使用 netty 包中的类居然会报错?是可忍孰不可忍!
我们来仔细分析一下,这里只有一个新的类就是 NioUdtProvider,打开 NioUdtProvider 的源码,在 import 一栏,我们赫然发现居然引用了不属于 netty 的包,就是这些包报错了:
import com.barchart.udt.SocketUDT;
import com.barchart.udt.TypeUDT;
import com.barchart.udt.nio.ChannelUDT;
import com.barchart.udt.nio.KindUDT;
import com.barchart.udt.nio.RendezvousChannelUDT;
import com.barchart.udt.nio.SelectorProviderUDT;
复制代码
虽然很诡异,但是我们要想程序跑起来还是需要找出这些依赖包,经过本人的跋山涉水、翻山越岭终于功夫不负苦心人,下面的依赖包找到了:
<dependency>
<groupId>com.barchart.udt</groupId>
<artifactId>barchart-udt-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.barchart.udt</groupId>
<artifactId>barchart-udt-bundle</artifactId>
<version>2.3.0</version>
</dependency>
复制代码
netty 核心包居然要依赖与第三方库,这可能就是 netty 准备删除对 UDT 支持的原因吧。
TypeUDT 和 KindUDT
如果你去查看 barchart 中类的具体信息,就会发现这个包的作者有个癖好,喜欢把类后面带上一个 UDT。
当你看到满屏的类都是以 UDT 结尾的时候,没错,它就是 netty UDT 依赖的包 barchart 本包了。
大牛们开发的包我们不能说他不好,只能说看起来有点累....
barchart 包中有两个比较核心的用来区分 UDT type 和 kind 的两个类,分别叫做 TypeUDT 和 KindUDT.
Type 和 kind 翻译成中文好像没太大区别。但是两者在 UDT 中还是有很大不同的。
TypeUDT 表示的是 UDT socket 的模式。它有两个值,分别是 stream 和 datagram:
表示数据传输是以字节流的形式还是以数据报文消息的格式来进行传输。
KindUDT 则用来区分是服务器端还是客户端,它有三种模式:
ACCEPTOR,
CONNECTOR,
RENDEZVOUS
复制代码
server 模式对应的是 ACCEPTOR,用来监听和接收连接.它的代表是 ServerSocketChannelUDT,通过调用 accept()方法返回一个 CONNECTOR.
CONNECTOR 模式可以同时在客户端和服务器端使用,它的代表类是 SocketChannelUDT.
如果是在 server 端,则是通过调用 server 端的 accept 方法生成的。如果是在客户端,则表示的是客户端和服务器端之间的连接。
还有一种模式是 RENDEZVOUS 模式。这种模式表示的是连接的每一侧都有对称对等的 channel。也就是一个双向的模式,它的代表类是 RendezvousChannelUDT。
构建 ChannelFactory
上面提到的两种 Type 和三种 Kind 都是用来定义 channel 的,所以如果将其混合,会生成六种不同的 channelFactory,分别是:
public static final ChannelFactory<UdtServerChannel> BYTE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
TypeUDT.STREAM, KindUDT.ACCEPTOR);
public static final ChannelFactory<UdtChannel> BYTE_CONNECTOR = new NioUdtProvider<UdtChannel>(
TypeUDT.STREAM, KindUDT.CONNECTOR);
public static final ChannelFactory<UdtChannel> BYTE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
TypeUDT.STREAM, KindUDT.RENDEZVOUS);
public static final ChannelFactory<UdtServerChannel> MESSAGE_ACCEPTOR = new NioUdtProvider<UdtServerChannel>(
TypeUDT.DATAGRAM, KindUDT.ACCEPTOR);
public static final ChannelFactory<UdtChannel> MESSAGE_CONNECTOR = new NioUdtProvider<UdtChannel>(
TypeUDT.DATAGRAM, KindUDT.CONNECTOR);
public static final ChannelFactory<UdtChannel> MESSAGE_RENDEZVOUS = new NioUdtProvider<UdtChannel>(
TypeUDT.DATAGRAM, KindUDT.RENDEZVOUS);
复制代码
这些 channelFactory 通过调用 newChannel()方法来生成新的 channel。
但是归根节点,这些 channel 最后是调用 SelectorProviderUDT 的 from 方法来生成 channel 的。
SelectorProviderUDT
SelectorProviderUDT 根据 TypeUDT 的不同有两种,分别是:
public static final SelectorProviderUDT DATAGRAM =
new SelectorProviderUDT(TypeUDT.DATAGRAM);
public static final SelectorProviderUDT STREAM =
new SelectorProviderUDT(TypeUDT.STREAM);
复制代码
可以通过调用他的三个方法来生成对应的 channel:
public RendezvousChannelUDT openRendezvousChannel() throws IOException {
final SocketUDT socketUDT = new SocketUDT(type);
return new RendezvousChannelUDT(this, socketUDT);
}
public ServerSocketChannelUDT openServerSocketChannel() throws IOException {
final SocketUDT serverSocketUDT = new SocketUDT(type);
return new ServerSocketChannelUDT(this, serverSocketUDT);
}
public SocketChannelUDT openSocketChannel() throws IOException {
final SocketUDT socketUDT = new SocketUDT(type);
return new SocketChannelUDT(this, socketUDT);
}
复制代码
使用 UDT
搭建好了 netty 服务器,剩下就是编写 Handler 对数据进行处理了。
这里我们简单的将客户端写入的数据再回写。客户端先创建一个 message:
message = Unpooled.buffer(UDTEchoClient.SIZE);
message.writeBytes("www.flydean.com".getBytes(StandardCharsets.UTF_8));
复制代码
再写入到 server 端:
public void channelActive(final ChannelHandlerContext ctx) {
log.info("channel active " + NioUdtProvider.socketUDT(ctx.channel()).toStringOptions());
ctx.writeAndFlush(message);
}
复制代码
服务器端通过 channelRead 方法来接收:
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
复制代码
总结
以上就是 netty 中使用 UDT 的原理和一个简单的例子。
本文的例子可以参考:learn-netty4
本文已收录于 http://www.flydean.com/40-netty-udt-support/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
评论