所有的Netty服务器都需要一下两部分:
①至少一个ChannelHandler——该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。
②引导——这是配置服务器的启动代码。至少,它会将服务器绑定到连接请求的端口上。
因为Echo服务器会影响传入的消息,所以它需要ChannelInboundHandler接口,用来定义响应入站事件的方法。这个简单的应用程序只需要用到少量的这些方法,所以继承CHannelInboundHandlerAdapter类也就足够了,它提供了ChannelInboundHandler的默认实现。
package netty.server;
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 //标示一个Channel-Handler可以被多个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();
}
}
ChannelInboundHandlerAdapter有一个直观的API,并且它的每个方法都可以被重写以挂钩到事件生命周期的恰当点上。因为需要处理所有接收到的数据,所以重写了channelRead()方法。在这个服务器应用程序中,你将数据简单地送给了远程节点。
重写exceptionCaught()允许你对Throwable的任何子类型做出反应。
如果不捕获异常,会发生什么呢?
每个channel都拥有一个与之相关联的ChannelPipeline,其持有一个ChannelHandler的实例链。在默认情况下,ChannelHandler会把对它的方法的调用转发给链接中的下一个ChannelHandler。因此,如果exceptionCaught()方法没有被该链中的某处实现,那么所接收的异常将会被传递到ChannelPipeline的尾端并被记录。为此,你的应用该程序应该提供至少有一个实现了exceptionCaught()方法的ChannelHandler。
引导服务器
①绑定到服务器将在其上监听并接受传入连接请求的端口;
②配置channel,并将有关的入站消息通知给EchoServerHandler实例。
package netty.server;
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>");
return;
}
//设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
//调用服务器的start()方法
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
//创建Event-LoopGroup
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建ServerBootstrap
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//指定所使用的NIO传输Channel
.channel(NioServerSocketChannel.class)
//使用制定的端口设置套接字地址
.localAddress(new InetSocketAddress(port))
//添加一个EchoServer-Handler到子Channel的ChannelPipeline
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
ch.pipeline().addLast(serverHandler);
}
});
//异步的绑定服务器;调用sync()方法阻塞等待直到绑定完成
ChannelFuture f = b.bind().sync();
//获取Channel的CloseFuture,并且阻塞当前线程直到它完成
f.channel().closeFuture().sync();
}finally {
//关闭EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
}