likes
comments
collection
share

Netty入门级示例,看这一篇就够了!

作者站长头像
站长
· 阅读数 1

前言

netty是Java中高级工程师绕不过的网络编程技能,也是非常值得学习的一个元老级项目。

尤其是其中的线程模型,是面试中netty相关的面试题非常热门的一类问题。

今天,首先带大家入门一下netty,给后续走入netty的世界有个基本的了解。

三种线程模型

首先,需要了解的是三种线程模型:BIONIOAIO

我从网上搜集到一个对于三种线程模型非常容易领会且十分有意思的讲法,大家一看便知。

  • BIO:去上厕所,坑全满,此时我一直光等着,主动观察哪个坑位好了,只要有坑位释放了,我就立马去占坑。
  • NIO:厕所坑全满,此时我跑出去抽烟或者做别的事,然后时不时主动去厕所有没有坑位释放,如果有坑了自己去占坑。
  • AIO:我在厕所外抽烟玩手机,等有人好了之后来通知我去占坑。

是不是很好懂了,哈哈,佩服前人留下的经验之谈。

而netty使用的就是其中的NIO模型,也就是同步非阻塞模型。

说白了,就是对于Java本身的NIO做了相当厉害的封装和优化,简化了原来复杂的用法,并且解决了几乎所有的BUG,这也是netty受欢迎的原因之一。

这里有一个思维导图,可以清晰的了解下这几种模型。

Netty入门级示例,看这一篇就够了!

案例

接下来,我们写一个入门级的案例,让大家对于netty有个清晰的认识。

1、netty服务端

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 实现客户端发送一个请求,服务器会返回hello netty
 * @author 程序员济癫
 *
 */
public class HelloServer {

   public static void main(String[] args) throws Exception {

      // 定义一对线程组
      // 主线程组,用户接受客户端的连接,但是不做任何处理,跟老板一样,不做事。
      EventLoopGroup bossGroup = new NioEventLoopGroup();
      
      // 从线程组,老板线程组会把任务丢给它,让手下线程组去做任务。
      EventLoopGroup workGroup = new NioEventLoopGroup();
      
      try {
      
          // netty服务器的创建,ServerBootstrap是一个启动类。
          ServerBootstrap serverBootstrap = new ServerBootstrap();
          
          serverBootstrap.group(bossGroup, workGroup)          // 设置主从线程组
                      .channel(NioServerSocketChannel.class)   // 设置nio双向通道
                      .childHandler(new HelloServerInitializer()); // 子处理器,用于处理workGroup。      
                      
          // 启动server,并且设置8088为启动的端口号,同时启动方式为同步。
          ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();    
          
          // 监听关闭的channel,设置为同步方式。
          channelFuture.channel().closeFuture().sync();
      } finally {
          // 优雅的关闭
          bossGroup.shutdownGracefully();
          workGroup.shutdownGracefully();
      }
   }
}

2、初始化器Initializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
 
/**
 * 初始化器,channel注册后,会执行里面的相应的初始化方法。
 * @author 程序员济癫
 *
 */
public class HelloServerInitializer extends ChannelInitializer {

   @Override
   protected void initChannel(SocketChannel channel) throws Exception {

      // 通过socketchannel去获得对应的管道
      ChannelPipeline pipeline = channel.pipeline();    
      
      // 通过管道,添加handler
      // HttpServerCodec是由netty自己提供的助手类,可以理解为拦截器。
      // 当请求到服务端,我们需要做解码,响应到客户端做编码。
      pipeline.addLast("HttpServerCodec", new HttpServerCodec());
     
      // 添加自定义的助手类,返回“hello netty~”。
      pipeline.addLast("customHandler", new CustomHandler()); 

   }
}

3、自定义处理器Handler

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

/**
 * 创建自定义处理类
 * @author 程序员济癫
 *   PS:SimpleChannelInboundHandler:对于请求来讲,其实相当于[入站,入境]
 */ 
public class CustomHandler extends SimpleChannelInboundHandler {

   @Override
   protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
   
      // 获取channel
      Channel channel = ctx.channel();

      if (msg instanceof HttpRequest) {

          // 显示客户端的远程地址
          System.out.println(channel.remoteAddress());

          // 定义发送的数据消息
          ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8);

          // 构建一个http response
          FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK, content);
                
          // 为响应增加数据类型和长度
          response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
          response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
          
          // 把响应刷到客户端
          ctx.writeAndFlush(response);
      }
   }

   @Override
   public void channelRegistered(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......注册");
      super.channelRegistered(ctx);
   }

   @Override
   public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......移除");
      super.channelUnregistered(ctx);
   }

   @Override
   public void channelActive(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......活跃");
      super.channelActive(ctx);
   }

   @Override
   public void channelInactive(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......不活跃");
      super.channelInactive(ctx);
   }

   @Override
   public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......读取完毕");
      super.channelReadComplete(ctx);
   }

   @Override
   public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

      System.out.println("用户事件触发....");
      super.userEventTriggered(ctx, evt);
   }

   @Override
   public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {

      System.out.println("channel......可写可改");
      super.channelWritabilityChanged(ctx);
   }

   @Override
   public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
      System.out.println("捕获到异常....");
      super.exceptionCaught(ctx, cause);
   }

   @Override
   public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

      System.out.println("处理类添加....");
      super.handlerAdded(ctx);
   }

   @Override
   public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

      System.out.println("处理类移除....");
      super.handlerRemoved(ctx);
   }

}

总结

这个示例非常简单,主要教大家以下几点:

  • 如何创建启动优雅关闭netty
  • 如何初始化netty,进行编解码自定义处理器的声明
  • 如何自定义处理器,完成netty具体的业务操作

下一节,我会通过一个聊天案例教大家如何使用netty的websocket

好了,今天的小知识你学会了吗?