2.3 写一个回声服务端
编写Netty服务端主要包含两个方面:
引导配置服务端功能--像线程和端口
实现服务器处理程序--构建包含业务逻辑的组件,它决定了当连接和数据被接收时,应该发生什么
2.3.1 引导服务端
通过创建ServerBootstrap类的实例来引导服务端。这个实例接下来会被配置,如下所示:设置参数,像端口、线程模型/事件循环,和服务处理器处理业务逻辑(例如,它回显数据,也可以更复杂)。
public class NettyEchoServer {
private final int port;
EchoServerHandler echoServerHandler =new EchoServerHandler();
public NettyEchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
//1 服务引导
ServerBootstrap b = new ServerBootstrap();
//2. 指定nio传输,本地套接字地址
b.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
//3. 在管道中添加处理
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//4 Binds server, waits for server to close, and releases resources
socketChannel.pipeline().addLast(echoServerHandler);
}
});
//5. bind the server and then wait until the bind completes,
// the call to the "sync()" method will cause this to block until the server is bound.
ChannelFuture f = b.bind().sync();
System.out.println(NettyEchoServer.class.getName() + " started and listen on " + f.channel().localAddress());
// 6. 等待通道关闭
f.channel().closeFuture().sync();
} finally {
// 7. 关闭EventLoopGroup和释放资源
group.shutdownGracefully().sync();
}
public static void main(String[] args) throws InterruptedException {
if (args.length != 1) {
System.err.println(
"Usage: " + NettyEchoServer.class.getSimpleName() +
" <port>");
}
int port = Integer.parseInt(args[0]);
new NettyEchoServer(port).start();
}
}这个例子看似微不足道,但它完成了第一章和以后的Java示例所有的工作。为了引导服务端,首先创建一个ServerBootstrap实例。因为你在用NIO传输,需要指明NioEventLoopGroup去接收新连接和处理接收的连接,指明NioServerSocketChannel作为管道的类型,设置InetSocketAddress去绑定到服务端,用来接收新连接。
接着,指明当连接接收后要调用的ChannelHandler,它创建了一个子处理器,这里用的是一个叫ChannelInitializer处理器。
尽管第一章的NIO示例是可扩展的,但它们会引起其他问题。例如,线程不容易正确处理。但Netty的设计和抽象封装了大多数你需要做的线程工作。使用到的EventLoopGroup、SocketChannel和ChannelInitializer,每一个都会在以后的章节讨论细节。
ChannelPipeline拥有一个管道的所有不同的ChannelHandler。所以你添加以前写的EchoServerHandler到管道的ChannelPipeline。
您绑定服务器,然后等待直到绑定完成,对sync()方法的调用将导致此操作阻塞,直到绑定服务器为止。At #7 the application will wait until the servers channel closes (because we call sync() on the channels close future). You can now shutdown the EventLoopGroup and release all resources, including all created threads(#10).
NIO使用这个作为例子,因为它是当前最常用的传输,并且你可能也会用它,当然你可以选择不同的传输实现。例如,如果这个例子使用OIO传输,你需要指明OioServerSocketChannel。Netty的架构,包括用什么传输,后面将会讲到。
让我们重点关注以下几点:
创建ServerBootstrap实例引导服务端并绑定它
创建和分配NioEvenLoopGroup实例去处理事件,像接收新连接,收发数据等等
指明本地的InetSocketAddress到绑定的服务端
为每个接收的连接设置执行的childHandler
以上设置完成后,调用ServerBootstrap.bind()方法去绑定服务端
2.3.2 实现服务端业务逻辑
Netty使用的是之前讲过的futures和callbacks的概念,另外结合了允许你挂载和响应不用的事件类型。后面再讨论细节,现在先关注我们收到的数据。为此,你的channelHandler必须继承ChannelInboundHandlerAdapter类并且重写messageReceived方法。每当收到消息的时候这个方法都被调用,它是字节的。这里写逻辑让你回显数据到客户端,如下所示:
Netty使用channel Handlers来实现最大程度的关注点分离,随着业务的发展,使得容易新增、修改、移除逻辑变得很容易。处理器的每一个方法都能够被重写,以挂载到数据生命周期的每一部分。但只有channelRead是必须被重写的。
2.3.3 拦截异常
为了重写channelRead方法,你或许注意到了exceptionCaught方法也被重写了。这样做是为了对异常和任何Throwable子类型。在例子中,记录了它并关闭了客户端连接,因为连接可能处于未知的状态。这是通常需要做的,但有些情况,错误可能恢复,所有你需要自己决定一个好的实现。需要注意的是,你至少需要一个channelHandler实现这个方法并处理各种错误。
Netty拦截异常的方式使得处理发生在不同线程里的异常变得更加容易。不能从各个线程处理的异常,都通过简单集中的API处理。
ChannelHandler有很多子类和实现,如果你计划实现一个真实的应用或者用Netty写框架,你需要注意它。记住,ChannelHandler的实现会被各种类型的事件调用,你可以实现或者继承他们去钩入事件的生命周期。
最后更新于
这有帮助吗?