您的位置:首页 > 其它

Netty4.x用户指导(1)3个HelloWorld小例子

2016-06-15 18:00 351 查看
题记最近对netty有了兴趣,现在官方推荐版本是netty4.*,但是纵观网络,大部分都是关于netty3.x的知识。最好的学习,莫过于通过官方文档进行学习,系统,透彻,权威,缺点是英文。本文,算做自己学习netty的第一篇,总体思路与User guide for 4.x基本一致,本篇文章不是严格意义的翻译文章。开始了...1.前言1.1 问题[b]现在,我们使用通用的应用程序和程序库,进行互相交流。例如,我们经常使用HTTP client库从web服务器上获取信息,通过webservices调用远程接口。然而,通用的协议或实现,有时候不能很好的扩展伸缩。就像我们不能使用通用协议进行部分信息的交换,如:hugefiles,e-mail,实时信息。我们需要高度优化的协议实现,来完成一些特殊目的。比如,你想实现一款专门的HTTP服务器,以支持基于AJAX的聊天应用,流媒体,大文件传输。你需要设计和实现一个完整的新协议,不可避免需要处理遗留协议,在不影响性能和稳定的情况下,实现新协议速度能有多块?[/b]1.2 解决方案Netty 致力于提供异步,基于事件驱动的网络应用框架,是一款进行快速开发高性能,可伸缩的协议服务器和客户端工具。换句话说,Netty是一个快速和容易开发的NIO客户端,服务端网络框架,它简化和使用流式方式处理网络编程,如TCP,UDP。"快速和容易“,并不代表netty存在可维护性和性能问题。它很完美的提供FTP,SMTP,HTTP,二进制和基于文本的协议支持。有的用户可能已经发现了拥有同样有点的其他网络应用框架。你可能想问:netty和他们的区别?这个问题不好回答,Netty被设计成提供最合适的API和实现,会让你的生活更简单。2.开始本节使用一些简单的例子让你快速的感知netty的核心结构。阅读本节之后,你可以熟练掌握netty,可以写一个client和server。准备JDK 1.6+最新版本的netty:下载地址maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
2.1写一个Discard服务最简单的协议,不是'hello world',而是DISCARD。这个协议抛弃接收的数据,没有响应。协议实现,唯一需要做的一件事就是无视所有接收到的数据,让我们开始吧。直接上Handler的实现,它处理来自netty的I/O事件。
package io.netty.examples.discard;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
* Handles a server-side channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
// Discard the received data silently.
((ByteBuf) msg).release(); // (3)
/*
//也可以这样
try {        // Do something with msg
} finally {
ReferenceCountUtil.release(msg);
}
*/

}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
1)DiscardServerHandler
继承了
ChannelInboundHandlerAdapter
实现了
ChannelInboundHandler
接口。
ChannelInboundHandler
提供了多个可重写的事件处理方法。现在,继承了
ChannelInboundHandlerAdapter,而不用自己实现handler接口。
2)重写了
channelRead
(),这个方法在接收到新信息时被调用。信息类型是ByteBuf3)实现Discard协议,ByteBuf是reference-counted对象,必须显示的调用release(),进行释放。需要记住handler的责任包括释放任意的reference-counted对象。4)exceptionCaught(),当发生异常时调用,I/O异常或Handler处理异常。一般情况下,在这里可以记录异常日志,和返回错误码。到了这里,完成了一半,接下来编写
main()
,启动Server
package io.netty.examples.discard;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.examples.discard.time.TimeServerHandler;/*** Discards any incoming data.*/public class DiscardServer {private int port;public DiscardServer(int port) {this.port = port;}public void run() throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap(); // (2)b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3).childHandler(new ChannelInitializer<SocketChannel>() { // (4)@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new TimeServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128)          // (5).childOption(ChannelOption.SO_KEEPALIVE, true); // (6)// Bind and start to accept incoming connections.ChannelFuture f = b.bind(port).sync(); // (7)// Wait until the server socket is closed.// In this example, this does not happen, but you can do that to gracefully// shut down your server.f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port;if (args.length > 0) {port = Integer.parseInt(args[0]);} else {port = 8080;}new DiscardServer(port).run();}}
NioEventLoopGroup
是一个多线程的处理I/O操作Event Loop(这是异步机制特点) Netty 提供了多个
EventLoopGroup
,支持多种传输方式。在这个例子中,我们使用了2个
NioEventLoopGroup
。第一个,叫"boss",接收进入的连接,第二个经常叫“worker",一旦boss接收了连接并注册连接到这个worker,worker就会处理这个连接。多少个线程被使用?,EventLoopGroup拥有多少个Channel?都可以通过构造函数配置
ServerBootstrap
是建立server的帮助类。你可以使用Channel直接创建server,注意,这个创建过程非常的冗长,大部分情况,你不需要直接创建。指定 NioServerSocketChannel类,当接收新接入连接时,被用在Channel的初始化。这个handler,初始化Channel时调用。ChannelInitializer是一个专门用于配置新的Channel的Handler,一般为Channel配置ChannelPipeline。当业务复杂时,会添加更多的handler到pipeline.你可以设置影响Channel实现的参数,比如keepAlive..
option()
影响的是接收进入的连接
的NioServerSocketChannel
;childOption()
影响的是来自父 ServerChannel分发的Channel, 在本例中是
NioServerSocketChannel
保证工作准备好了通过telnet进行测试。输入telnet 127.0.0.1 8080由于是Discard协议,一没响应,二服务端也没输出。通过测试,只能确认是服务正常启动了。调整下Handler逻辑,修改channelRead()方法。
@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf in = (ByteBuf) msg;try {while (in.isReadable()) { // (1)System.out.print((char) in.readByte());System.out.flush();}} finally {ReferenceCountUtil.release(msg); // (2)}}
修改之后,在使用telnet测试。你在命令行中输入什么,在服务端就能看到什么!(只能是英文)2.2写一个Echo服务作为一个服务,没有返回响应信息,明显是不合格的。接下来实现Echo协议,返回响应信息。与前面的Discard服务实现唯一的不同点就是Handler,需要回写接收到的信息,而不是打印输出。
    @Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ctx.write(msg); // (1)ctx.flush(); // (2)}
1)ChannelHandlerContext提供多个方法,触发I/O事件。在这里,不需要像Discard一样释放 接收的信息。2)ctx.write(Object)不能保证完全写入,底层存在缓存,需要通过ctx.flush()刷新,保证完全写入。完成。2.3写一个Time服务时间协议见 TIME protocol。和前面2个协议不同,服务将发送一个32位的integer值。不需要接收信息,一旦信息发出,将关闭连接。在这个例子中,将学到如何构造和发送信息,并在完成时关闭连接。由于我们不需要接收信息,所以不需要channelRead()方法,而是使用channelActive()。
package io.netty.example.time;public class TimeServerHandler extends ChannelInboundHandlerAdapter {    @Overridepublic void channelActive(final ChannelHandlerContext ctx) { // (1)final ByteBuf time = ctx.alloc().buffer(4); // (2)time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));        final ChannelFuture f = ctx.writeAndFlush(time); // (3)f.addListener(new ChannelFutureListener() {            @Overridepublic void operationComplete(ChannelFuture future) {                assert f == future;ctx.close();}}); // (4)}    @Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}}
1)channelActive()在连接建立和准备完成时调用。2) 发送一个新消息,需要分配一个buff.将来写入一个4位的integer值。获取当前的 ByteBufAllocator,可以通过ChannelHandlerContext.alloc()获取。3)注意没有java.nio.ByteBuffer.flip(),(nio 读写切换时使用),这是因为netty重写了Buffer,维护了2个指示器,分别用来(reader index)读取和( writer index )写入。注意:ChannelHandlerContext.write() (and writeAndFlush())返回的ChannelFuture 。ChannelFuture 代表一个尚未发生的I/O操作。这一个是异步操作,会触发通知listeners 。这儿的意思是当ChannelFuture完成时,才会调用close()。最终版的Server Handler
package io.netty.examples.time;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelFuture;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;public class TimeServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(final ChannelHandlerContext ctx) { // (1)final ByteBuf time = ctx.alloc().buffer(4); // (2)time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));final ChannelFuture f = ctx.writeAndFlush(time); // (3)f.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture future) {assert f == future;ctx.close();}}); // (4)}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}}
接下来实现Time客户端与Echo和Discard协议不同,人不能很好的将32位的integer,转换为可以理解的日期数据。通过学习本节,可以学习如何写一个Client。这和前面的EchoServer,DiscardServer实现有很大的不同。
package io.netty.examples.time;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 TimeClient {public static void main(String[] args) throws Exception {String host = "127.0.0.1";int port = Integer.parseInt("8080");EventLoopGroup workerGroup = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap(); // (1)b.group(workerGroup); // (2)b.channel(NioSocketChannel.class); // (3)b.option(ChannelOption.SO_KEEPALIVE, true); // (4)b.handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new TimeClientHandler());}});// Start the client.ChannelFuture f = b.connect(host, port).sync(); // (5)// Wait until the connection is closed.f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();}}}
Bootstrap
is similar to
ServerBootstrap
except that it's for non-server channels such as a client-side or connectionless channel.如果只使用一个
EventLoopGroup
,它会被当成boss group 和 worker group. boss worker不会再client使用。代替
NioServerSocketChannel
,
NioSocketChannel
是一个 客户端的
Channel
.注意没有使用
childOption()
,因为客户端没有上级。调用
connect()
,而不是
bind()
下面是对应的Handler
package io.netty.examples.time;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import java.util.Date;public class TimeClientHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf m = (ByteBuf) msg; // (1)try {long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L;ctx.close();} finally {m.release();}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}}
In TCP/IP, Netty reads the data sent from a peer into a
ByteBuf
.测试过程:略。本文出自 “简单” 博客,请务必保留此出处http://dba10g.blog.51cto.com/764602/1789639
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: