设置Netty开发环境
Maven工程中POM加入如下依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.22.Final</version>
</dependency>
Echo服务器
- 一个服务器handler:这个组件实现了服务器的业务逻辑,决定了连接创建后和接收到信息后该如何处理。覆写其中一些方法,决定你自己的处理逻辑。
- Bootstrapping:这个是配置服务器的启动代码。最少需要设置服务器绑定的端口,用来监听连接请求。
继承ChannelHandler来实现业务逻辑
Echo Server是用来接收客户端的消息,并发送给客户端,就像Linux的Echo命令。
我们需要实现ChannelInboundHandler接口,用来处理接收的消息。由于Echo服务器逻辑很简单,我们继承ChannelInboundHandlerAdapter就行,它提供了一些默认的实现,我们覆写它们即可。
主要的几个方法如下:
3. channelRead() - 接收到消息时调用。
4. channelReadComplete() - 通知处理器最后的channelRead()是当前批处理中的最后一条消息时调用。
5. exceptionCaught() - 读操作时捕获到异常时调用。
示例代码
// 标识这类的实例之间可以在 channel 里面共享
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
// 日志消息输出到控制台
System.out.println("Server received :" + in.toString(CharsetUtil.UTF_8));
// 将所接收的消息返回给发送者。注意,这还没有冲刷数据
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
// 冲刷所有待审消息到远程节点。关闭通道后,操作完成
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 冲刷所有待审消息到远程节点。关闭通道后,操作完成
cause.printStackTrace();
// 关闭通道
ctx.close();
}
}
注
:exceptionCaught使我们能够应对Throwable的子类型,通常情况下,我们这时难以从连接错误中恢复状态,干脆在此时关闭连接。
如果异常没被捕获会发生什么?
每个 Channel 都有一个关联的 ChannelPipeline,它代表了 ChannelHandler 实例的链。适配器处理的实现只是将一个处理方法调用转发到链中的下一个处理器。因此,如果一个 Netty 应用程序不覆盖exceptionCaught ,那么这些错误将最终到达 ChannelPipeline,并且结束警告将被记录。出于这个原因,你应该提供至少一个 实现 exceptionCaught 的 ChannelHandler。
服务器启动代码
public class EchoServer {
private int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws Exception {
// 3.创建EventLoopGroup
NioEventLoopGroup group = new NioEventLoopGroup();
try {
// 4.创建ServerBootstrap,服务器配置助手
ServerBootstrap sb = new ServerBootstrap();
sb.group(group)
// 5.指定使用 NIO 的传输 Channel
.channel(NioServerSocketChannel.class)
// 6.设置 socket 地址使用所选的端口
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 7.添加 EchoServerHandler 到 Channel 的 ChannelPipeline
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 8.绑定的服务器;sync 等待服务器关闭
ChannelFuture cf = sb.bind().sync();
System.out.println(EchoServer.class.getName() + " started and listen on " + cf.channel().localAddress());
// 9.关闭 channel 和 块,直到它被关闭
cf.channel().closeFuture().sync();
} finally {
// 关掉EventLoopGroup,释放所有资源。
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage : " + EchoServer.class.getSimpleName() + " <port>");
return;
}
// 1.设置端口号
int port = Integer.parseInt(args[0]);
// 2.调用服务器的start方法
new EchoServer(port).start();
}
}
注:
在这个例子中,代码创建ServerBootstrap实例(步骤四)。由于我们使用在NIO传输,我们已指定NioEventLoopGroup(3)接收和处理新连接,指定NioServerSocketChannel(5)为信道类型。我么你设置本地地址是InetSocketAddress与所选择的端口(6),服务器将绑定的此地址来监听新的连接请求。
第七步是关键:我们使用一个特殊的类,ChannelInitializer,当一个新的连接被接受,一个新的子Channel将被创建,ChannelInitializer会添加我们EchoServerHandler的实例到Channel的ChannelPipeline。如果有入站信息,这个处理器将被通知。
总结
服务器的主要代码组件:
1. EchoServerHandler实现了业务逻辑(ChannelRead等)
2. EchoServer实现了服务器的配置,以及main()引导服务器启动
步骤二所需的步骤:
1. 创建ServerBootstrap实例来引导服务器并随后绑定
2. 创建并分配一个NioEventLootGroup实例来处理事件,如接受新的连接和读/写数据。
3. 指定本地InetSocketAddress给服务器绑定
4. 通过EchoServerHandler实例给每一个新的Channel初始化。
5. 最后调用ServerBootstrap.bind()绑定服务器。