本文导读
- 在已经了解完《Netty 理论详解》之后,想必已经开始跃跃欲试了,毕竟这么好的东西呀!
- 本文将详细讲解 Netty 入门案例。
- Netty 官网地址:http://netty.io/
- GitHub 托管地址:https://github.com/netty/netty
- 用户指南官网地址:https://netty.io/wiki/user-guide.html
- Netty 4.0 官网开发文档地址:https://netty.io/4.0/api/index.html
- Netty 4.1 官网开发文档地址:https://netty.io/4.1/api/index.html
开发包获取
二进制 jar 包
- Netty 本身就是人家写好的一个 Java 的 Jar 包(库),所以开发的第一步便是要下载它,Maven 方式后面讲解。
- 如下所示,进入 Netty 官网,然后鼠标悬停在 "Downloads" 上,点击下载对应的版本,本文以最新的 4.1.30.Final 为例。
- 下载后压缩包大小为 20.1M,可以使用 "WinRar" 等压缩工具进行解压。
- jar 开发包目录中提供了各个模块单独的开发包,同时也提供了对应的源码,其中有一个 netty-example-4.1.30.Final.jar 、netty-example-4.1.30.Final-sources.jar 提供了大量的示例,非常适合学习。
- 以导入二进制类库的方式开发 Netty 时,则只需要导入如下所示的整合包即可,3.7M 包含了所有的模块,同时也提供了源码。
Maven 依赖
- 如果使用 Maven 进行项目开发管理,则 Netty 也提供了 Maven 依赖。
- Maven 依赖可以从 Netty 官网下载页中获取:https://netty.io/downloads.html,如下所示:
<dependencies>
...
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId> <!-- Use 'netty-all' for 4.0 or above -->
<version>X.Y.Z.Q</version>
<scope>compile</scope>
</dependency>
...
</dependencies><artifactId>netty</artifactId>:如果 Netty 是 4.0 以下版本,则 artifactId值写 netty,如果 Netty 是 4.0 及以上版本,则 写 netty-all。
<version>X.Y.Z.Q</version>:netty 版本号自己填写具体版本即可。
- 也可以直接从 Maven 官方仓库获取:https://mvnrepository.com/artifact/io.netty/netty-all
- 这里同样可以选择上面最新版的 4.1.30.Final ,点击进入即可获取依赖:
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.30.Final</version>
</dependency>
Hello World
- 出于入门阶段学习的目的,本文将新建 Java SE 项目,采用导入二进制开发包的方式,暂时不使用 Maven 管理。
- 环境:IDEA 14 + JDK 8 + Netty4.1.30。
- 项目建好之后,新建 lib 目录用于存放第三方开发包,注意要添加到类路径中,同时新建 classes 目录作为项目统一的编译输出目录。
- 以一个简单的例子来对 Netty 网络编程有一个初步的了解,其中的细节可以以后慢慢消化:先开启服务器等待客户端连接,然后开启客户端,同时给服务器发送一条消息,服务器接收到消息后,回发一条消息。
服务端
·TimeServerHandler·
package com.lct.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
/**
* Created by Administrator on 2017/5/16.
* 用于对网络事件进行读写操作
*/
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
/**获取缓冲区可读字节数,并创建数组*/
byte[] reg = new byte[buf.readableBytes()];
/**将缓冲区字节数组复制到新建的byte数组中*/
buf.readBytes(reg);
/**获取请求消息*/
String body = new String(reg, "UTF-8");
System.out.println("The server receive order : " + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
/**给客户端发送应答消息,实际是将消息放到发送缓冲数组中*/
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
/**将发送缓冲区中的消息全部写到SocketChannel中*/
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
/**当发生异常时,关闭ChannelHandlerContext,释放和它相关联的句柄等资源*/
ctx.close();
}
}
·TimeServer·
package com.lct.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.beans.beancontext.BeanContextChildComponentProxy;
/**
* Created by Administrator on 2017/5/16.
*/
public class TimeServer {
public void bind(int port){
/**配置服务端的NIO线程组,专门用于网络事件处理
* 一个用于服务端结束客户端连接
* 一个用于用于进行SocketChannel读写*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
/**ServerBootstrap是Netty用于启动NIO服务端的辅助启动类*/
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
.childHandler(new ChildChannelHandler());
/**绑定端口,同步等待成功*/
ChannelFuture f = b.bind(port).sync();
/**等待服务器监听端口关闭*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
/**优雅退出,释放线程池资源*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
public static void main(String[] args) {
int port = 8080;
if (args!=null && args.length>0){
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
/**采用默认值*/
}
}
new TimeServer().bind(port);
}
}
客户端
·TimeClientHandler·
package com.lct.client;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.logging.Logger;
/**
* Created by Administrator on 2017/5/17.
* 用于对网络事件进行读写操作
*/
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
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);
}
/**当客户端和服务端TCP链路建立成功之后,Netty的NIO线程会调用channelActive方法
* 同时发送查询时间的指令给服务端,调用ChannelHandlerContext的writeAndFlush方法
* 将 请求消息发送给服务端*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
/**当服务端返回应答消息时,channelRead方法被调用,从Netty的ByteBuf中读取并打印应答消息*/
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
System.out.println("Server return Message:"+body);
}
/**当发生异常时,打印异常 日志,释放客户端资源*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
/**释放资源*/
logger.warning("Unexpected exception from downstream : "+cause.getMessage());
ctx.close();
}
}
·TimeClient·
package com.lct.client;
import io.netty.bootstrap.Bootstrap;
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;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* Created by Administrator on 2017/5/16.
*/
public class TimeClient {
public void connect(int port,String host){
/**配置客户端NIO线程组*/
EventLoopGroup group = new NioEventLoopGroup();
try {
/**创建客户端辅助启动类,并对其配置
* 与服务器稍微不同,这里的Channel设置为NioSocketChannel
* 然后为其添加Handler,这里直接使用匿名内部类,实现initChannel方法
* 作用是当创建NioSocketChannel成功后,在进行初始化时
* 将它的ChannelHandler设置到ChannelPipeline中,用于处理网络I/O事件*/
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
/**发起异步连接操作*/
ChannelFuture f = b.connect(host, port);
/**等待客户端链路关闭*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
/**优雅退出,释放NIO线程组*/
group.shutdownGracefully();
}
}
public static void main(String[] args) {
int port = 8080;
if (args!=null && args.length>0){
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
/**采用默认值*/
}
}
new TimeClient().connect(port,"127.0.0.1");
}
}
测试运行
- 先运行服务端,再运行客户端:
自学建议
- 对于和我一样自学的兄弟姐妹来说,除了百度以外,还可以参考下载包中更多官方示例,如下所示:
- 也可以参考官网的用户手册:https://netty.io/wiki/user-guide-for-4.x.html
- 也可以购买书籍,或者网上的 PDF 文档,如下所示的《Netty 权威指南》可以下载:https://download.csdn.net/download/wangmx1993328/10717896
············下一篇《传统 BIO 编程》