7.3 KiB
netty
netty解决的问题
当前,http协议被广泛使用于client和server之间的通信。但是,部分场景下,http协议并不适用,例如传输大文件、e-mail消息、事实的经济或游戏数据。对于该类场景,http协议并无法很好的满足,此时需要一个高度优化的自定义协议实现,而通过netty可以及进行快速的协议实现。
netty提供了一个异步的事件驱动的网络应用框架,用于快速开发一个拥有高性能、高可拓展性的协议的server和client。
netty example
Discard Server Example
如下是一个Discard Protocol的样例,其会对所有接收到的数据进行丢弃,并且不返回任何结果。handler方法会处理由netty产生的io事件:
package io.netty.example.discard;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Handles a server-side channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
DiscardServerHandler继承了ChannelInboundHandlerAdapter,而ChannelInboundHandlerAdapter则是实现了ChannelInboundHandler。ChannelInboundHandler提供了各种不同的io事件处理方法,可以对这些方法进行覆盖。
在上述示例中,对chnnelRead方法进行了重写,channelRead方法将会在从客户端接收到数据时被调用,被调用时会把从客户端接收到的消息作为参数。在上述示例中,接收到的消息是ByteBuf类型。
ByteBuf是一个引用计数对象,必须要通过调用
release方法来显式释放。并且,handler method有责任对传入的引用计数对象进行释放操作。
通常,channelRead方法的重写按照如下形式:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
// Do something with msg
} finally {
// handler makes sure that msg passed has been released
ReferenceCountUtil.release(msg);
}
}
上述示例中除了重写channelRead方法外,还重写了exceptionCaught方法。exceptionCaught方法会在 netty由于io error抛出异常 或是 handler method实现在处理事件时抛出异常 的场景下被调用。
在通常情况下,被exceptionCaught方法捕获的异常,其异常信息应该被打印到日志中,且异常关联的channel应该被关闭。通过重写caughtException也可以自定义异常捕获后的实现,例如在关闭channel之前向client发送error code.
netty应用程序实现示例
如下是netty实现的Discard Server示例:
package io.netty.example.discard;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Discards any incoming data.
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
核心组件解析
NioEventLoopGroup
NioEventLoopGroup是一个多线程的event loop,用于处理io操作。netty提供了不同的EventLoopGroup实现用于不同种类的传输。
在上述示例中,使用了两个NioEventLoopGroup:
- boss:boss event loop用于接收incoming connections
- worker:worker event loop用于处理accepted connection的通信
每当boss event loop接收到connection之后,其会将新的连接注册到worker event loop。
event group loop中拥有的线程数,以及线程如何与channel相关联(可能是每个线程对应一个channel、或是一个线程管理多个channel),由EventLoopGroup的实现来决定,并且可以通过构造器来进行配置。
ServerBootstrap
ServerBoostrap是一个helper类,用于方便的构建一个netty server。
NioServerSocketChannel
在上述使用中,使用了NioServerSocketChannel来实例化一个channel,改channel用于接收incoming connection。
handler注册
在上述示例中,为accepted connection注册handler的示例如下所示:
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
在上述注册示例中,每个newly accepted channel都会调用DiscardServerHandler。
ChannelInitializer是一个特殊的handler,用于帮助用户为新channel配置ahndler。其initChannel为新channel的ChannelPipeline配置添加自定义的handler,从而实现自定义的消息处理逻辑。
为Channel实现设置参数
通过option和childOption方法,可以为channel设置参数。由于netty server基于TCP/IP,故而可以指定socket option例如tcpNoDelay和keepAlive参数。
- option:option设置的是NioServerSocketCahnel,该channel用于接收incoming connection
- childOption:childOption用于设置被server channel接收的channel,被接收的channel为SocketChannel
为Channel绑定port
在经历完上述配置之后,就可以为server绑定监听端口并启动了。