Netty入门应用
一、开发环境搭建
前提:电脑有Java环境,并且jdk版本1.7+。熟悉Java基础,maven。
1.1创建一个基础maven项目
1.file -> new -> project
2.创建maven项目,添加相关信息,然后点击create
3.等待idea下载依赖,如果失败,需要配置maven仓库
1.2补充pom文件
1.2.1 前往选择Netty版本
网址为:
https://mvnrepository.com/artifact/io.netty/netty-all
实际上,Netty4.1.97.Final的使用者更多,但是笔者跟着《Netty权威指南 第二版》学习的,所以选择的是5.0.0.Alpha1
点击进入,然后复制下来:
maven:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
复制到pom.xml文件中,点击如图m重构maven,下载依赖:
二、Netty服务端开发
以一个基础的时间服务器为例进行开发
2.1编写服务端TimeServer类
代码如下:
public class TimeServer {
public void bind(int port){
//配置服务端NIO线程组
//这里创建两个NioEventLoopGroup实例,一个用户服务端接受客户端连接,一个用于SocketChannel的读写
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try{
//ServerBootstrap是用于启动NIO服务端的辅助启动类
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) //将上面两个group当成参数传入ServerBootstarp
.channel(NioServerSocketChannel.class)//设置通道为NioServerSocketChannel
.option(ChannelOption.SO_BACKLOG,1024)//配置NioServerSocketChannel的tcp参数
.childHandler(new ChildChannelHandler());//绑定实际的事件处理类
//绑定端口,等待连接
ChannelFuture channelFuture = b.bind(port).syncUninterruptibly();
//等待服务器监听关闭
//使用这个方法进行阻塞,等待服务端链路关闭之后main函数才退出
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
//优雅退出,该函数会关闭跟shutdownGracefully相关联的资源
bossGroup.shutdownGracefully();//关闭线程
workerGroup.shutdownGracefully();
}
}
//IO事件处理类
private static class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) {
int port = 8080;
if (args!=null && args.length > 0) {
try{
port = Integer.parseInt(args[0]);
}catch (NumberFormatException e){
System.out.println(e.getMessage());
}
}
new TimeServer().bind(port);
}
}
2.2编写服务端TimeServerHandler类
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//这里进行转换,因为ByteBuf提供了更强大灵活的功能
ByteBuf buf = (ByteBuf) msg;
//readableBytes()方法可以获取缓冲区可读的字节数,以此创建一个合适大小的byte数组
byte[] bytes = new byte[buf.readableBytes()];
//readBytes方法将buf中缓存的字节复制到新建的bytes数组中
buf.readBytes(bytes);
//将数组转为字符
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println("The time is " + s);
//QUERY TIME ORDER这串字符是客户端发送过来的,这里判断请求消息来源,如果是客户端的消息,则将正确时间赋值给currentTime
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(s) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
//转为ByteBuf对象
ByteBuf byteBuf = Unpooled.copiedBuffer(currentTime.getBytes());
//异步发送应答消息给客户端
ctx.write(byteBuf);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
相关的代码解释已经注释在代码里面,如果理解不了也没关系。本次仅仅是一个入门应用,仅做一次基本的解释。
三、客户端开发
3.1编写客户端TimeClient
public class TimeClient {
public void connect(int port, String host) {
//客户端处理IO事件的线程组
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
//客户端辅助启动类
Bootstrap bootstrap = new Bootstrap();
try {
Bootstrap channel = bootstrap.group(nioEventLoopGroup).channel(NioSocketChannel.class)//设置channel为NioSocketChannel
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() { //匿名内部类的方式,实现一个init方法
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new TimeClientHandler());
}
});
//发起异步连接操作,等待连接
ChannelFuture sync = bootstrap.connect(host, port).sync();
//等待客户端链路关闭,异步阻塞
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//优雅退出,关闭NIO线程
nioEventLoopGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
if(args!=null && args.length>0){
try{
port = Integer.parseInt(args[0]);
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
new TimeClient().connect(port, "127.0.0.1");
}
}
3.2编写客户端TimeClientHandler
public class TimeClientHandler extends ChannelHandlerAdapter {
private static final Logger logger = Logger
.getLogger(TimeClientHandler.class.getName());
private final ByteBuf firstMessage;
public TimeClientHandler(){
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println("Now is : "+s);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//释放资源
logger.warning("Unexpected exception from downstream : "+cause.getMessage());
ctx.close();
}
}
这里重点关注三个方法:channelActive、channelRead和exceptionCaught。当客户端和服务端TCP链路建立成功之后,Netty的Nio线程会调用channelActive方法,发送查询时间的指令给服务端,调用ChannelHandlerContext的writeAndFlush方法将请求发送给服务端。
当服务端返回应答消息时,channelRead方法被调用。
四、运行
在idea中,右键TimeServer,点击启动按钮如图:
同样的方法启动客户端TimeClient:
我们来查看控制台: