什么是netty?
百度百科描述
Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty 相当于简化和流线化了网络应用的编程开发过程,例如:基于 TCP 和 UDP 的 socket 服务开发。
如上摘录自百度百科的描述。
废话不多说,直接上代码:
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
NettyServer.java
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建两个线程组,一个用于处理服务器端接受客户端的连接,一个用于进行网络通讯(数据读写)
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
//创建辅助工具类,用于服务器通道的一系列配置
ServerBootstrap serverBootStrap = new ServerBootstrap();
serverBootStrap.group(bossGroup, workGroup) //绑定两个线程组
.channel(NioServerSocketChannel.class) //指定NIO模式
.option(ChannelOption.SO_BACKLOG,1024) //设置TCP缓冲区
.handler(new LoggingHandler(LogLevel.INFO)) //设置日志
.childHandler(new NettyInitializer()); //指定自定义handler的初始化器
//进行端口绑定
ChannelFuture channelFuture = serverBootStrap.bind(8002).sync();
//等待关闭
channelFuture.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
首先是Netty服务端启动代码,具体每一步的大概作用我也有写行尾注释,可以细心看一下代码,不用死记,模板代码基本上大同小异,对设置ServerBootstrap的各个参数有点印象就行。那么这里最主要的就是这个
ServerBootstrap
类了,其次就是对channelFuture
也要熟悉一点,下面是来自官方英文的翻译。
ChannelFuture的作用是用来保存Channel异步操作的结果。
我们知道,在Netty中所有的I/O操作都是异步的。这意味着任何的I/O调用都将立即返回,而不保证这些被请求的I/O操作在调用结束的时候已经完成。取而代之地,你会得到一个返回的ChannelFuture实例,这个实例将给你一些关于I/O操作结果或者状态的信息。
对于一个ChannelFuture可能已经完成,也可能未完成。当一个I/O操作开始的时候,一个新的future对象就会被创建。在开始的时候,新的future是未完成的状态--它既非成功、失败,也非被取消,因为I/O操作还没有结束。如果I/O操作以成功、失败或者被取消中的任何一种状态结束了,那么这个future将会被标记为已完成,并包含更多详细的信息(例如:失败的原因)。请注意,即使是失败和被取消的状态,也是属于已完成的状态。
那么细心的你肯定已经发现了ServerBootstrap.childHandler()方法,可能注释有点不易理解。简单来说,就是指定一个自定义的handler,但是这个handler又是由一个initializer来帮我们初始化的,所以这里指定的并不是直接的handler,而是一个initializer对象,在该对象中,我们再来指定和初始化我们的自定义handler。
NettyInitializer.java
public class NettyInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(new ReadTimeoutHandler(3))
.addLast(new NettyHandler()); //自定义的处理器
}
}
到了我们的initlilizer类中,继承ChannelInitializer对象,重写initChannel()方法,这里我们首先是获取到了pipeline对象 ,为通道设置一些对象参数。这里需要注意的是,Encoder和 Decoder必须要指定,否则双方都接收不到数据(或者自己转字节来传递,
java网络编程中,API所规定的的数据最小传输单元就是字节
)。在最后一行代码可以看到,我们指定了自定义的handler---->NettyHandler
NettyHandler.java
public class NettyHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("---通道连接成功---");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("开始读取通道数据...");
System.out.println("收到客户端信息为:"+msg.toString());
ctx.writeAndFlush("已收到信息"+msg+"---(来自服务端的回应)");
}
}
如代码所示,继承ChannelHandlerAdapter类(我这里是 ChannelInboundHandlerAdapter ),其中channelActive在通道激活的时候会被调用,可以理解为''连接上了服务器''这么一个环节,其中ChannnelHandlerContext对象是管理它所关联的ChannelHandler和在同一个ChannelPipeline中的其他ChannelHandler直接的交互,而msg则是接收到的所有数据。
-------小总结--------
简单来说:搭建Netty服务端需要三个类,NettySerrver.java、Nettyinitializer.java、NettyHandler.java。其中Server和initializer两个类几乎是模板写法,就是配置连接和通道参数,真正做数据处理的是在我们自定义的handler类中。,
因为Client端和服务端非常类似,就不再多说了,唯一需要注意的就是服务端中的ServerBootstrap对象在Client端使用的是Bootstrap,两个类的属性几乎大差不差,以及线程组只需要一个。总之相对服务端简单了很多,看看代码就能懂。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
Client.java(其中写了一个内部类initializer)
public class Client {
private static class SingletonHolder {
static final Client INSTANCE = new Client();
}
public static Client getInstance(){
return SingletonHolder.INSTANCE;
}
private EventLoopGroup group;
private Bootstrap bootstrap;
private ChannelFuture channelFuture;
private Client(){
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//通过JBOSS的Marshalling进行编码解码
// sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
// sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new StringEncoder());
sc.pipeline().addLast(new StringDecoder());
//客户端与服务端在3s内没有任何通信则关闭响应通道 节省服务器资源
sc.pipeline().addLast(new ReadTimeoutHandler(3));
//
sc.pipeline().addLast(new ClientHandler());
}
});
}
public void connect(){
try {
this.channelFuture = bootstrap.connect("127.0.0.1", 8002).sync();
System.out.println("远程服务器已经连接, 可以进行数据交换..");
} catch (Exception e) {
e.printStackTrace();
}
}
public ChannelFuture getChannelFuture(){
if(this.channelFuture == null){
this.connect();
}
if(!this.channelFuture.channel().isActive()){
this.connect();
}
return this.channelFuture;
}
public static void main(String[] args) throws Exception{
final Client c = Client.getInstance();
//c.connect();
ChannelFuture cf = c.getChannelFuture();
for(int i = 1; i <= 3; i++ ){
String request = new String();
request+=("数据信息---" + i+"---次");
cf.channel().writeAndFlush(request);
Thread.sleep(3000);
}
cf.channel().closeFuture().sync();
//通过new一个子线程来重新连接服务端
// new Thread(new Runnable() {
// @Override
// public void run() {
// try {
// System.out.println("进入子线程...");
// ChannelFuture cf = c.getChannelFuture();
// System.out.println(cf.channel().isActive());
// System.out.println(cf.channel().isOpen());
// //再次发送数据
// String request = new String();
// request+=("数据信息---4---次");
// cf.channel().writeAndFlush(request);
// cf.channel().closeFuture().sync();
// System.out.println("子线程结束.");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }).start();
System.out.println("断开连接,主线程结束..");
}
}
ClientHandler.java
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端收到数据...");
System.out.println("收到服务器端信息为:"+msg.toString());
}
}
本文只是一个简单的快速入门案例,其中一些细节可能不全,解释也相对简单。后续我继续学习的总结会继续更新到该分类中。