Netty 學習(一)

一、什麼是Netty

Netty是一個NIO服務框架,簡化了網咯應用框架的開發難度。Netty是一個事件驅動模型,(將很多階段抽象成一個個的事件,讓後將事件映射到多個回調方法上)。主要的特點有(https://netty.io/index.html):

  • 爲BIO/NIO提供統一的API
  • 是一個靈活的、可擴展的事件驅動模型
  • 提供高度可定製化的線程模型
  • 支持真正的無連接數據報傳輸
  • 提供高吞吐、低延時,並且佔用更少的資源
  • 提供最小化的不必要內存拷貝
  • 提供對proto Buffer的支持

二、Netty 的初級使用

public class TestServer{
    public static void main(String[] args){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, wokerGroup)
            .channel(NioServerSockerChannel.class)
            .childHandler(new TestServerInitializer());
        ChannelFuture future = bootstrap.bind(8899).sync();
        future.channel().closeFuture().sync();
    }
}
// 自定義啓動初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
    @Override
    protected void initChannel(SocketChannel ch) throws Exception{
        ChannelPipeline pipeline = ch.pipiline();
        // HttpServerCodec是HttpRequestDecoder和HttpResponseDecoder的組合
        pipeline.addLast("httpServerCodec", new HttpServerCodec());
        pipeline.addLast("serverHandler", new TestServerHandler());
    }
}

// 實現自己的回調方法
public class TestServerHandler extends SimpleChannelInboundHandler<HttpObject>{
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg){
        if(msg instanceof HttpObject){
            ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8);
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            // response.headers().set()
            ctx.writeAndFlush(response);
        }
    }
}
public class MyClient{
    public static void main(String[] args){
        EventLoopGroup loop = new NioEventLoopGroup();
        try{
            Bootstrap strap = new Bootstrap();
            strap.group(loop).channel(NioSocketChannel.class)
                .handler(new MyClientHandler());
            ChannelFuture fut = strap.connect("localhost", 8899).sync();
            fut.channel.closeFuture().sync();
        }finally{
            loop.shutdownGracefull();
        }
    }
}

public class MyClientInitializer extends ChannelInitializer<SocketChannel>{
    @Override
    protected void initChannel(SocketChannel ch) throws Exception{
        ChannelPipeline pipeline = ch.pipiline();
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("my client handler", new MyClientHandler());
    }
}

public class MyClientHandler extends SimpleChannelInboundHandler<String>{
    @Overrie
    protected void channelRead0(ChannelHandlerContext ctx, String msg){
        // 服務端向客戶端傳遞數據時,客戶端的處理邏輯
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause){
        cause.printStackTrace();
        ctx.close();
    }

    @Overrie
    protected void channelActive(ChannelHandlerContext ctx){
        // 當與服務端建立好連接後,操作
        ctx.writeAndFlush("test");
    }
}

2.1 SimpleChannelInboundHandler

處理流入channel的事件處理,常見的觸發有

  • channelActive() channel是活躍狀態
  • channelRegisterd()  註冊channel
  • channelAdded();    添加channel
  • channelInactive()  非活躍
  • channelUnregistered()  卸載channel

執行順序是:channelAdded() ->registered()->active()->inactive()->unregistered

2.2 ChannelInitializer

// 自定義啓動初始化器
public class TestServerInitializer extens ChannelInitializer<SocketChannel>{
    @Override
    protected void initChannel(SocketChannel ch) throws Exception{
        ChannelPipeline pipeline = ch.pipiline();
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4))
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast("my handler", new MyHandler());
    }
}
// 這裏的泛型主要依據是請求的是什麼樣的格式數據
public class MyHandler extends SimpleChannelInboundHandler<String>{
    // 收到消息後的處理邏輯
    @Overrie
    protected void channelRead0(ChannelHandlerContext ctx, String msg){

    }
}

三、聊天室實例

需求:

第一個客戶端A與服務端建立連接後,服務端打印A上線;

後續的客戶端與服務端建立好連接後,服務端打印X上線,並且廣播X上線給已經建立好的連接。

客戶端X發送一條消息後,廣播到其餘客戶端

如果客戶端下線後,服務端向所有的客戶端廣播X下線。

// 核心思想,利用channel的事件機制,將對應建立好的channel加入到一個組裏維護
public class MyChatServerHandler extends SimpleChannelInboundHandler<String>{
    private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    
    @Override
    public void channelRead0(ChannelHandlerContext ctx, String msg){
        Channel channel = ctx.channel();
        group.forEach(t -> {
            if(channel != t){
                t.writeAndFlush("非自己發出的消息");
            }else{
                t.writeAndFlush("自己發出的消息");
            }
        });
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx){
        Channel ch = ctx.channel();
        group.writeAndFlush(ch.remoteAddress() +"online");
        group.add(ch);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx){
        Chanel channel = ctx.channel();
        group.writeAndFlush(channel.remoteAddress() + "offline");
        // group.remove(channel);  這個netty會自動調用,所以可以省略
    }
    
    @Override
    public void channelActive(ChannelHandlerContext ctx){
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + "上線");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx){
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + "下線");
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章