Netty學習筆記--1(使用Netty搭建一個基于Http協議的服務)

0.what is Netty?

簡單來說,Netty是異步的,事務驅動的,高性能的NIO框架
Netty官網
其中有詳細的netty介紹,剛開始學習,有什么不對,歡迎指出。

1.Netty中幾個比較重要的概念

1.0 Handler

Handler其實就是事件的處理器,Netty通過Channel讀入請求內容后會分配給Handler進行事件處理,Handler能夠處理的事件包括:數據接收,異常處理,數據轉換,編碼解碼等問題,其中包含兩個非常重要的接口ChannelInboundHandler,ChannelOutboundHandler,前者負責處理客戶端發送到服務端的請求,后者反之。關于Handler執行順序的一些介紹可以看一看這篇文章: handler的執行順序
在這個地方有一個值得注意的點,無論是InboundHandler還是OutboundHandler都不適合用于做耗時操作,官方要求耗時操作應當使用單獨的EventExcutorGroup+專門的Handler來進行操作

static final EventExecutorGroup group = new DefaultEventExecutorGroup(16); 
... 
ChannelPipeline pipeline = ch.pipeline(); 
pipeline.addLast("decoder", new MyProtocolDecoder()); pipeline.addLast("encoder", new MyProtocolEncoder()); 
// Tell the pipeline to run MyBusinessLogicHandler's event handler methods 
// in a different thread than an I/O thread so that the I/O thread is not blocked by 
// a time-consuming task. 
// If your business logic is fully asynchronous or finished very quickly, you don't 
// need to specify a group. 
pipeline.addLast(group, "handler", new MyBusinessLogicHandler());

1.1 Channel

這里的Channel的概念和NIO中Channel的概念是一樣的,相當于一個Socket連接

1.2 Bootstrap

Bootstrap其實就是Netty服務的啟動器,服務端使用的是ServerBootstrap,客戶端使用的是Bootstrap,我們可以通過配置Bootstrap來配置Netty使用哪種的Channel,Group,Handler和Encoder,Decoder……

1.3 LoopGroup

一個LoopGroup可以包含多個EventLoop,我目前的理解是將其理解為一個線程池,其中的EventLoop為其中的線程

1.4 ChannelFuture

這點在官方的Guide中也有提到,在Netty中,所有的處理都是異步的,因此需要一個Future對象,可以注冊監聽在異步線程處理完以后進行一些處理

2.HelloWorld

2.0 Server端代碼

public class Server{

    public void start(int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        EventExecutorGroup bussinessGroup = new DefaultEventExecutorGroup(16);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                     //使用哪一種Channel
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //添加http請求所需要的編碼器與解碼器
                            ch.pipeline().addLast(new HttpResponseEncoder());
                            ch.pipeline().addLast(new HttpRequestDecoder());
                            //ch.pipeline().addLast(new ServerOutboundHandler()); //如果存在OutboundHandler則必須在最后一個inbound前面
                            ch.pipeline().addLast(new ServerHandler());
                            //耗時操作使用的group和專門的handler
                            ch.pipeline().addLast(bussinessGroup, "handler", new BusinessHandler());
                        }
                    })
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture f = b.bind(port);
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Server server = new Server();
        server.start(8090);
    }

}

2.1 ServerHandler

public class ServerHandler extends ChannelInboundHandlerAdapter {

    private HttpRequest request;
    private HttpContent content;
    private FullHttpResponse response;
    private StringBuilder sb = new StringBuilder();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (msg instanceof HttpRequest) {
            request = (HttpRequest) msg;
            System.out.println(request.uri());
            System.out.println("request received success");
            sb.append("request received success \n");
        }
        if (msg instanceof HttpContent) {
            content = (HttpContent) msg;
            ByteBuf buf = content.content();
            String result = buf.toString(CharsetUtil.UTF_8);
            System.out.println(result);
            buf.release();
            System.out.println("content received success");
            sb.append("content received success");
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(sb.toString().getBytes()));
            ctx.writeAndFlush(response);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_GATEWAY);
        ctx.writeAndFlush(response);
        ctx.close();
    }
}

3. 請求服務器

3.0 使用postman來請求服務器

postman訪問netty服務器

我使用post方式隨意發送了任意內容到服務器,能夠得到返回內容,并且idea控制臺輸出如下,表明可以獲得postman發送的數據

idea控制臺輸出內容

3.1 使用NettyClient訪問Netty服務器

3.1.0 Client

public class Client {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new HttpClientCodec());
                    ch.pipeline().addLast(new ClientHandler());
                }
            });
            ChannelFuture f = b.connect("127.0.0.1", 8090).sync();
            Channel ch = f.channel();
            FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, "http://127.0.0.1:8090", Unpooled.wrappedBuffer("{ \"test\" : \"test\" }".getBytes()));  //發送請求道服務端
            request.headers().set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
            request.headers().set(HttpHeaderNames.CONTENT_LENGTH, 1024);  //必須設置Content length否則服務端收不到content
            System.out.println(request.content().toString(CharsetUtil.UTF_8));
            ch.writeAndFlush(request);
            //wait until server to close the connection
            ch.closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }

}

3.1.1 ClientHandler

public class ClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        HttpContent content;
        HttpResponse response;
        if (msg instanceof HttpResponse) {
            response = (HttpResponse) msg;
            System.out.println(response.status().toString());
        }

        if (msg instanceof HttpContent) {
            content = (HttpContent) msg;
            ByteBuf buf = content.content();
            String responseContent = buf.toString(CharsetUtil.UTF_8);
            System.out.println(responseContent);
            buf.release();
        }
    }

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

以上代碼也能獲得和postman訪問相同的效果

4.其中思考的問題

  • Q:耗時的業務邏輯操作應該放在哪里
    A:使用EventExcutorGroup和單獨的Handler來實現業務邏輯代碼(在上面的代碼中有提到)
  • Q :如何將InboundMessageHandler讀到的msg傳到其他Handler
    A:使用ctx.pipeline().channel().attr()可以設置屬性來在Handler傳遞屬性值
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • netty常用API學習 netty簡介 Netty是基于Java NIO的網絡應用框架. Netty是一個NIO...
    花丶小偉閱讀 6,053評論 0 20
  • 本文是Netty文集中“Netty 源碼解析”系列的文章。主要對Netty的重要流程以及類進行源碼解析,以使得我們...
    tomas家的小撥浪鼓閱讀 6,004評論 9 12
  • 一場秋雨,將彌漫的塵霧一掃而空。 碧空萬里,書寫的不光是夏日的澄澈 行走在秋的環視中,是增添秋的美還是享受秋的吻?...
    沙漠里的胡楊v閱讀 325評論 0 2
  • 周末,早飯很遲。 不忍心叫醒正在熟睡的兒子。 早已做好的早餐就只等兒子起來用餐了。餐桌上擺好了兒子喜歡吃的飯菜,牛...
    薛靜春閱讀 282評論 2 1