Netty入门应用一

来源:互联网 发布:数码宝贝3 知乎 编辑:程序博客网 时间:2024/05/16 06:55

      在本例子中使用的是netty5,以及后面的例子中也是。先去官网进下载5.0.0.Alphal,解压后要引用的jar包就是netty-5.0.0.Alphal.jar包。对于用netty进行编写TimeServer的nio程序如下:

package zou;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.handler.codec.LineBasedFrameDecoder;import io.netty.handler.codec.string.StringDecoder;public class TimeServer {public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.parseInt(args[0]);} catch (Exception e) {}}new TimeServer().bind(port);}public void bind(int port) throws Exception {//配置服务端的nio线程组,//EventLoopGroup是一个线程组,它包含了一组nio线程,专门用于网络事件的处理,实际上他们就是Reactor线程组//这里创建2个的原因是一个用于服务端接受客户的连接,另一个用于SocketChannel的网络读写。EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {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();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel arg0) throws Exception {arg0.pipeline().addLast(new TimeServerHandler());}}}
    从bind方法看到,这里创建了2个nio的EventLoopGroup实例。EventLoopGroup是一个线程组,它包含了一组NIO线程,专门用于网络事件的处理,实际上他们就是Reactor线程组。这里创建2个的原因是一个用于服务端接受客户端的连接,另一个用于进行SocketChannel的网络读写。ServerBootstrap对象是启动nio服务端的辅助启动类。

    接着设置创建的Channel为NioServerSocketChannel,并设置tcp参数backlog。最后绑定i/o事件的处理类ChildChannleHandler,它的作用类似于Reactor模式中的Handler类,主要用于处理网络i/o事件,例如记录日志、对消息进行编解码等。ght

    finally里面的代码是线程组的关闭,进行优雅的退出。

   TimeServerHandler类继承自ChannelHandlerAdapater,它用于对网络事件进行读写操作。通常我们只需要关注channelRead和exceptionCaught方法。其代码如下:

package zou;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;public class TimeServerHandler extends ChannelHandlerAdapter {private int counter;@Overridepublic 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").substring(0, req.length - System.getProperty("line.separator").length());System.out.println("the time server receive order :" + body + "the counter:" + ++counter);String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";currentTime += System.getProperty("line.separator");ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());//进行发送消息到客户端ctx.writeAndFlush(resp);}//@Override//public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {////通过调用此方法,将发送的缓冲区的消息全部写到SocketChannel中//ctx.flush();//}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}}
   在进行读取的时候进行了类型的转换(将字节转换为字符串)。ctx的flush方法是将消息发送队列的消息写入到SocketChannel中发送给对方。从性能的角度思考,为了防止频繁唤醒selector消息进行发送,Netty的write方法并不是直接将消息写入SocketChannel中,而是放入到发送缓冲数组中,再通过调用flush方法将发送缓冲区中的消息全部写到SocketChannel中。另外在捕捉异常的方法中是进行资源的关闭和释放。

    其客户端代码如下:

      

package zou;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 void connet(int port, String host) throws Exception {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();//客户端禁用nangle算法b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new TimeClientHandle());}});//发起异步连接操作ChannelFuture f = b.connect(host, port).sync();//等待客户端关闭f.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;if (args != null && args.length > 0) {try {port = Integer.parseInt(args[0]);} catch (Exception e) {}}new TimeClient().connet(port, "127.0.0.1");}}
    这里使用了线程组EventLoopGroup,然后借助辅助类来启动nio,这里设置了SocketChannel,然后设置了tcp参数,禁用了nagle算法,此处通过创建匿名内部类来实现一个ChannelInitalizer类。另外需要实现一个网路处理的handler类。

    handler代码如下:

  

package zou;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandlerAdapter;import io.netty.channel.ChannelHandlerContext;import java.util.logging.Logger;public class TimeClientHandle extends ChannelHandlerAdapter {private static final Logger logger = Logger.getLogger(TimeClientHandle.class.getName());private final ByteBuf firstMessage;public TimeClientHandle() {byte[] req = "QUERY TIME ORDER".getBytes();firstMessage = Unpooled.buffer(req.length);firstMessage.writeBytes(req);}//连接成功后发送指令@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(firstMessage);}@Overridepublic 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("Now is:" + body);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}}

   这里重点关注三个方法:channelActive,channelRead,和exceptionCaught.当客户端和服务端tcp链路建立成功后,Netty的nio线程会调用channelActive方法,发送查询时间指令给服务端,调用writeAndFlush方法将请求消息发送给服务端。当服务端返回应答的时候,channelRead方法被调用。

    需要指出的是这里没有考虑读半包处理问题,对于功能的测试没有问题,但是稍加修改或者压力测试就不能正常工作了。下个例子会进行讲解半包消息的处理。




原创粉丝点击