所有的Netty服务器都需要以下两个部分
- 至少一个ChannelHandler——该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑
- 引导——这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上
ChannelHandler和业务逻辑
由于Echo服务器会响应传入的消息,因此只需要实现ChannelnboundHandler接口,用来定义响应入站的事件方法。这个简单的应用程序只需要用到少量的这些方法,因而只需要继承ChannelInboundHandlerAdapter类就足够了,它提供了ChannelInboundHandler的默认实现
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
@Sharable //标示一个ChannelHandler可以被多个Channel安全的共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf) msg;
//将消息记录到控制台
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in); //将接收到的消息写给发送者,而不冲刷出站消息
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将为决消息冲刷到远程节点,并且关闭该Channel
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace(); //打印异常栈跟踪
ctx.close(); //关闭该Channel
}
}
引导服务器
上面已经写了EchoServerHandler实现的核心业务逻辑之后,接下来开始引导服务器的编写了,它主要涉及一下内容
- 绑定到服务器将在其上监听并接受传入连接请求的端口
- 配置Channel,以将有关的入站消息通知给EchoServerChannel实例
import java.net.InetSocketAddress;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception{
if(args.length != 1) {
System.err.println("Usage: " + EchoServer.class.getSimpleName() + " <port>");
}
//设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
new EchoServer(port).start(); //调用服务器的start方法
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup(); //1、创建EventLoopGroup
try {
ServerBootstrap b = new ServerBootstrap(); //2、常见ServerBootstrap
b.group(group)
.channel(NioServerSocketChannel.class) //3、指定所使用的NIO传输Channel
.localAddress(new InetSocketAddress(port)) //4、使用指定的端口设置套接字地址
.childHandler(new ChannelInitializer<SocketChannel>() { //5、添加一个EchoServerHandler到子Channel的ChannelPipeline
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(serverHandler); //EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
}
});
ChannelFuture f= b.bind().sync(); //6、异步绑定服务器,调用sync()方法阻塞等待直到绑定完成
f.channel().closeFuture().sync(); //7、获取Channel的CloseFuture,并且阻塞当前线程直到它完成
} catch (Exception e) {
group.shutdownGracefully().sync(); //8、关闭EventLoopGroup释放所有的资源
}
}
}
在2处,创建了一个ServerBootstrap实例,因为目前正在使用NIO传输,所以指定了NioEventLoopGroup(1)来接收和处理新的连接,并且将Channel的类型指定为NioServerSocketChannel(3),在此之后,将本地地址设置为一个具有选定端口的InetSocketAddress(4),服务器将绑定到这个地址以监听新的连接请求
在5处,使用了一个特殊的类——ChannelInitializer。这是关键,当一个新的连接被接受是,一个新的子Channel将会被创建,而ChannelInitialize将会把一个你的EchoServerHandler的实例添加到该Channel的ChannelPipeline中。之前所声明的ChannelHandler将会收到有关入站的消息通知
接下来绑定了服务器6,并等待绑定完成。(对sync()方法的调用将导致当前Thread阻塞,一直到绑定操作完成为止)。在7处,该应用程序将会阻塞等待直到服务器的Channel关闭(因为在Channel的CloseFuture上调用了sync()方法)。然后,你将可以关闭EventLoopGroup,并释放所有的资源,包括所有被创建的线程8
创建服务器的步骤的主要代码组件如下:
- EchoServerHandler实现了业务逻辑;
- main()方法引导了服务器
引导过程如下:
- 创建一个ServerBootstrap的实例以引导和绑定服务器;
- 创建并分配一个NioEventLoopGroup实例以进行实践的处理,如接受新的连接以及读/写数据;
- 指定服务器绑定的本地的InetSocketAddress;
- 使用一个EchoServerHandler的实例初始化每一个新的Channel;
- 调用ServerBootstrap.bind()方法以绑定服务器