在上一篇文章写了JAVA的NIO1.0,这一篇写的Netty其实是基于NIO2(AIO)的一个NIO框架,我们知道传统的IO是一种同步阻塞式的IO,而JDK1.4之后处理的NIO1是一种同步非阻塞式的IO,他们都有一个共同的特点就是都是同步的,这样在多个线程并发访问的时候效率是不高的,NIO2是一种异步非阻塞的IO,效率非常的高,类似ajax和Future模式。如果自己去实现NIO经常会出现各种各样的困难,如果思维不谨慎的话。Netty就是一种基于NIO的框架,使用起来非常的轻松。
下面来看下官方文章中的描述:
一 问题
现如今我们使用通用的应用程序或者类库来实现系统之间地互相访问,比如我们经常使用一个HTTP客户端来从web服务器上获取信息,或者通过web service来执行一个远程的调用。
然而,有时候一个通用的协议和他的实现并没有覆盖一些场景。比如我们无法使用一个通用的HTTP服务器来处理大文件、电子邮件、近实时消息比如财务信息和多人游戏数据。我们需要一个合适的协议来处理一些特殊的场景。例如你可以实现一个优化的Ajax的聊天应用、媒体流传输或者是大文件传输的HTTP服务器,你甚至可以自己设计和实现一个新的协议来准确地实现你的需求。
另外不可避免的事情是你不得不处理这些私有协议来确保和原有系统的互通。这个例子将会展示如何快速实现一个不影响应用程序稳定性和性能的协议。
二 解决方案
Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议。Netty大大简化了网络程序的开发过程比如TCP和UDP的 Socket的开发。
“快速和简单”并不意味着应用程序会有难维护和性能低的问题,Netty是一个精心设计的框架,它从许多协议的实现中吸收了很多的经验比如FTP、SMTP、HTTP、许多二进制和基于文本的传统协议,Netty在不降低开发效率、性能、稳定性、灵活性情况下,成功地找到了解决方案。
有一些用户可能已经发现其他的一些网络框架也声称自己有同样的优势,所以你可能会问是Netty和它们的不同之处。答案就是Netty的哲学设计理念。Netty从第一天开始就为用户提供了用户体验最好的API以及实现设计。正是因为Netty的设计理念,才让我们得以轻松地阅读本指南并使用Netty。
三 Helloworld
使用netty来写一个服务器端的程序:
首先需要导入netty-all-5.0.0.Alpha2.jar包,或者maven引用
Server.java
package com.lxj.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 Server {
/*
* 官方文档解释:
* NioEventLoopGroup 是用来处理I/O操作的多线程事件循环器,Netty提供了许多不同的EventLoopGroup的实现用来处理不同传输协议。
* 在这个例子中我们实现了一个服务端的应用,因此会有2个NioEventLoopGroup会被使用。第一个经常被叫做‘boss’,用来接收进来的连接。
* 第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。如何知道多少个线程已经被使用,
* 如何映射到已经创建的Channels上都需要依赖于EventLoopGroup的实现,并且可以通过构造函数来配置他们的关系。
ServerBootstrap 是一个启动NIO服务的辅助启动类。你可以在这个服务中直接使用Channel,但是这会是一个复杂的处理过程,在很多情况下你并不需要这样做。
这里我们指定使用NioServerSocketChannel类来举例说明一个新的Channel如何接收进来的连接。
这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。ChannelInitializer是一个特殊的处理类,他的目的是帮助使用者配置一个新的Channel。
也许你想通过增加一些处理类比如xxxxServerHandle来配置一个新的Channel或者其对应的ChannelPipeline来实现你的网络程序。当你的程序变的复杂时,
可能你会增加更多的处理类到pipline上,然后提取这些匿名类到最顶层的类上。
你可以设置这里指定的通道实现的配置参数。我们正在写一个TCP/IP的服务端,因此我们被允许设置socket的参数选项比如tcpNoDelay和keepAlive。
option()是提供给NioServerSocketChannel用来接收进来的连接。
childOption()是提供给由父管道ServerChannel接收到的连接,在这个例子中也是NioServerSocketChannel。
*/
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口号
ChannelFuture f = b.bind(9999).sync();
//等待直到服务器关闭
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}ServerHandler.java
package com.lxj.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/*
* 处理一个服务器的通道
*/
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
try {
// while(byteBuf.isReadable()) {
// byte b = byteBuf.readByte();
// System.out.println((char)b);
// }
//与上面注释的是一样的,打印ASCll码
System.out.print(byteBuf.toString(CharsetUtil.US_ASCII));
}finally {
//释放连接
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//打印异常信息
cause.printStackTrace();
ctx.close();
}
}这样就可以开启一个Netty服务器了,运行Server ,在CMD中输入 telnet 127.0.0.1 9999 ,就可以给服务器发送数据了。

现在只能实现服务器的响应,要实现响应给客户端,如下
ServerHandler.java
package com.lxj.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
/*
* 处理一个服务器的通道
*/
public class ServerHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf byteBuf = (ByteBuf)msg;
// try {
//// while(byteBuf.isReadable()) {
//// byte b = byteBuf.readByte();
//// System.out.println((char)b);
//// }
// //与上面注释的是一样的,打印ASCll码
// System.out.print(byteBuf.toString(CharsetUtil.US_ASCII));
// }finally {
// //释放连接
// ReferenceCountUtil.release(msg);
// }
/*
* 1. ChannelHandlerContext对象提供了许多操作,使你能够触发各种各样的I/O事件和操作。
* 这里调用了write(Object)方法来逐字地把接受到的消息写入。并没有释放接受到的消息,
* 这是因为当写入的时候Netty已经释放了。
2. ctx.write(Object)方法不会使消息写入到通道上,他被缓冲在了内部,你需要调用ctx.flush()方法来把缓冲区中数据强行输出。
或者你可以用更简洁的cxt.writeAndFlush(msg)以达到同样的目的。
*/
ByteBuf byteBuf = (ByteBuf)msg;
String string = "Server: "+byteBuf.toString(CharsetUtil.US_ASCII)+"\n";
byteBuf.clear();
byteBuf.writeBytes(string.getBytes());
ctx.write(byteBuf);
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//打印异常信息
cause.printStackTrace();
ctx.close();
}
}
TIME服务(时间协议的服务)
在这个部分被实现的协议是TIME协议。和之前的例子不同的是在不接受任何请求时他会发送一个含32位的整数的消息, 并且一旦消息发送就会立即关闭连接。在这个例子中,你会学习到如何构建和发送一个消息,然后在完成时主动关闭连连接完成后会关闭。
只需要覆盖ServerHandler.java的channelActive方法:
package com.lxj.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
/*
* 处理一个服务器的通道
*/
public class ServerHandler extends ChannelHandlerAdapter{
/*
* TIME服务(时间协议的服务)
在这个部分被实现的协议是TIME协议。和之前的例子不同的是在不接受任何请求时他会发送一个含32位的整数的消息,
并且一旦消息发送就会立即关闭连接。在这个例子中,你会学习到如何构建和发送一个消息,然后在完成时主动关闭连
* 连接完成后会关闭
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
final ByteBuf time = ctx.alloc().buffer(4);
time.writeInt((int) (97)); //a
time.writeInt((int) (98)); //b
time.writeInt((int) (99)); //c
time.writeInt((int) (100)); //d
final ChannelFuture f = ctx.writeAndFlush(time);
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) {
assert f == future;
ctx.close();
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//打印异常信息
cause.printStackTrace();
ctx.close();
}
}
连接后就关闭连接。
随时随地看视频