Netty开发使用相当的简单,且相当的灵活,开发者不用关心NIO底层的知识,只需要关心业务内容即可。Netty是通过ChannelHandler 进行扩展,开发者只需要实现 ChannelHandler,并把实现类注册到 ChannelPipline中即可。
ChannelHandler是一个接口,Netty实现了ChanneHandlerAdapter适配器,开发者可以直接继承ChannelHandlerAdapter,只需要重写所关心的方法即可
下面是一个简单但例子,实现客户端请求服务端,获取服务端的当前时间。
1、实现服务端的ChannelHandler。
服务端创建NettyTimeServerHandler类,此类继承了 ChannelHandlerAdapter,并且重写了channelRead等方法。
服务端具体实现步骤如下:
1、channelRead 读取客户端发送的信息。
2、判断读完的客户端信息是否为“QUERY_TIME_ORDER”,如果是则获取服务端当前时间,否则返回 “BAD_ORDER”
3、调用ChannelHandlerContext 的write 方法发送数据。write只是把数据写入到缓冲区中,并没有真正的输出到网络中。
4、在channelReadComplete方法中调用ChannelHandlerContext 的flush 方法刷新缓冲区,此方法是把数据真正输出到网络通道中。
package org.java.io.nio.netty;
import java.util.Date;
import org.java.io.utils.Utils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.CharsetUtil;
public class NettyTimeServerHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("服务端出现异常:" + cause.getMessage());
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("我是开始第一个读取的handler---");
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String body = new String(bytes, CharsetUtil.UTF_8);
System.out.println("接收到的消息:" + body);
String currentTime = "QUERY_TIME_ORDER".equalsIgnoreCase(body) ? new Date(System.nanoTime()).toString()
: "BAD_ORDER";
ByteBuf buffer = Unpooled.copiedBuffer(currentTime.getBytes(CharsetUtil.UTF_8));
ctx.write(buffer); //把数据发暂时写入到缓冲中
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush(); //把缓冲区中的数据刷入到通道中
}
}
二、实现客户端ChannelHandler
客户端创建NettyTimeClientHandler类,此类同样也是继承了 ChannelHandlerAdapter,并且重写了channelRead、channelActive等方法。
客户端端具体实现步骤如下:
1、当socket连接创建成功后,Netty就会调用channelActive方法,再次方法中调用ChannelHandlerContext的writeAndFlush方法把请求命令发送到网络通道中。
2、当服务端发送完数据,客户端读取到服务端发送的数据后就会调用channelRead方法,在此方法中获取数据,并且打印到控制台。
package org.java.io.nio.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
public class NettyTimeClientHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.err.println("客户端出现异常:" + cause.getMessage());
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
byte[] bytes = "QUERY_TIME_ORDER".getBytes(CharsetUtil.UTF_8);
System.out.println("开始发送信息......");
ByteBuf buffer = Unpooled.copiedBuffer(bytes);
ctx.writeAndFlush(buffer); //向缓冲区中写入数据,同时刷新缓冲区数据到I/O通道
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buffer = (ByteBuf) msg;
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
System.out.println("返回的信息为:" + new String(bytes, CharsetUtil.UTF_8));
}
}
三、实现服务端
服务端启动类,
1、此类首先是创建了两个EventLoopGroup类对象,分别为boss 和workers,其中boss是主线程组用来接收客户端的请求,workers是工作线程组用来处理I/O操作。当boss接收到客户端的请求后,即把请求发送给workers线程组。
2、创建服务端的引导对象ServerBootstrap,此类是辅助启动服务端。
3、把boss和workers注册到ServerBootstrap中,同时调用ServerBootstrap的其他方法设置参数。
4、调用ServerBoostrap的bind方法绑定端口,启动服务单。
package org.java.io.nio.netty;
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;
public class NettyServerHandler {
public void bind(int port) {
EventLoopGroup boss = new NioEventLoopGroup(); //创建主线程组
EventLoopGroup workers = new NioEventLoopGroup(); //创建工作线程主
ServerBootstrap sb = new ServerBootstrap(); //创建服务端引导
sb.group(boss, workers).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyTimeServerHandler()); //把自定义的ChannelHandler注册到ChannelPipeline中
}
});
try {
ChannelFuture future = sb.bind(port).sync(); //绑定端口
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
workers.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyServerHandler().bind(8080);
}
}
四、实现客户端
客户端启动类,说明如下:
1、此类首先创建了一个EventLoopGroup对象,由于客户端只是连接读写数据,所以值创建了一个EventLoopGroup对象。
2、创建客户端的引导类Bootstrap对象,同时调用此类的相应方法设置参数。
3、调用Bootstrap的connect方法连接服务单。
package org.java.io.nio.netty;
import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;
public class NettyClientHandler {
public void connect(String host, int port) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bs = new Bootstrap();
bs.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyTimeClientHandler()); //把自定义的ChannelHandler注册到ChannelPipeline中
}
});
try {
ChannelFuture future = bs.connect(host, port).sync(); //创建连接
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new NettyClientHandler().connect("localhost", 8080);
}
}
五、运行结果
1、服务端输出日志:
2、客户端输出日志
说明:在学习过程中主要参考了 《Netty实践》、《Netty权威指南第二版》和网络博客。写此博客主要是作为学习笔记,增强记忆。