1. 程式人生 > >Netty4+SpringBoot實現http server

Netty4+SpringBoot實現http server

一.Netty是什麼?

Netty:Netty是 一個非同步事件驅動的網路應用程式框架, 用於快速開發可維護的高效能協議伺服器和客戶端。Netty是一個NIO客戶端伺服器框架,可以快速輕鬆地開發協議伺服器和客戶端等網路應用程式。它極大地簡化並簡化了TCP和UDP套接字伺服器等網路程式設計。“快速簡便”並不意味著最終的應用程式會受到可維護性或效能問題的影響。Netty經過精心設計,具有豐富的協議,如FTP,SMTP,HTTP以及各種二進位制和基於文字的傳統協議。因此,Netty成功地找到了一種在不妥協的情況下實現易於開發,效能,穩定性和靈活性的方法。來自於官網的描述。

個人理解:是建立在客戶端和伺服器之間,通過某個指定協議實現網路資料傳輸,高效能,非同步,事件驅動,的java nio框架(協議的容器)。

二.Netty執行流程

圖片來源部落格:圖解Netty5.0https://blog.csdn.net/KouLouYiMaSi/article/details/80589095,侵權刪。

三.SpringBoot整合Netty,實現http協議的server伺服器端 

1.http協議下的客戶端和伺服器端的請求執行流程圖,圖片來源:https://blog.csdn.net/wangshuang1631/article/details/73251180/

2.maven的pom.xml中引入Netty的jar包,新增以下依賴:

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.31.Final</version>
        </dependency>

3.Netty服務端啟動器類NettyServer的編碼:

package demo.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * description:
 * author: 
 * date: 2018-11-28 12:07
 **/
@Component
public class NettyServer {

    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    //boss事件輪詢執行緒組
    private EventLoopGroup boss = new NioEventLoopGroup();
    //worker事件輪詢執行緒組
    private EventLoopGroup worker = new NioEventLoopGroup();

    private Channel channel;

    @Autowired
    ServerChannelInitializer serverChannelInitializer;
    @Value("${n.port}")
    private Integer port;

    /**
     * 開啟Netty服務
     *
     * @return
     */
    public ChannelFuture start() {
        //啟動類
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boss, worker)//設定引數,組配置
                .option(ChannelOption.SO_BACKLOG, 128)//socket引數,當伺服器請求處理程全滿時,用於臨時存放已完成三次握手的請求的佇列的最大長度。如果未設定或所設定的值小於1,Java將使用預設值50。
                .channel(NioServerSocketChannel.class)///構造channel通道工廠//bossGroup的通道,只是負責連線
                .childHandler(serverChannelInitializer);//設定通道處理者ChannelHandler////workerGroup的處理器
        //Future:非同步操作的結果
        ChannelFuture channelFuture = serverBootstrap.bind(port);//繫結埠
        ChannelFuture channelFuture1 = channelFuture.syncUninterruptibly();//接收連線
        channel = channelFuture1.channel();//獲取通道
        if (channelFuture1 != null && channelFuture1.isSuccess()) {
            log.info("Netty server 服務啟動成功,埠port = {}", port);
        } else {
            log.info("Netty server start fail");
        }

        return channelFuture1;
    }

    /**
     * 停止Netty服務
     */
    public void destroy() {
        if (channel != null) {
            channel.close();
        }
        worker.shutdownGracefully();
        boss.shutdownGracefully();
        log.info("Netty server shutdown success");
    }

}

其中@Value("${n.port}")取的是application.properties配置檔案中的n.port=7000。

4.通道初始化類ServerChannelInitializer的編碼,主要用於設定各種Handler:

package demo.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * description: 通道初始化,主要用於設定各種Handler
 * author: 
 * date: 2018-11-28 14:55
 **/
@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    ServerChannelHandler serverChannelHandler;

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        //編碼解碼
        ch.pipeline().addLast(new HttpRequestDecoder());
        ch.pipeline().addLast(new HttpResponseEncoder());

        ch.pipeline().addLast(serverChannelHandler);//ChannelHandler
    }
}

5.通道處理者ServerChannelHandler的編碼,就是用來處理請求的:

package demo.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import org.springframework.stereotype.Component;

/**
 * description:
 * author: 
 * date: 2018-11-28 15:49
 **/
@Component
@ChannelHandler.Sharable
public class ServerChannelHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Netty Server receive msg : " + msg);
        if (msg instanceof HttpRequest) {
            //要返回的內容, Channel可以理解為連線,而連線中傳輸的資訊要為ByteBuf
            ByteBuf content = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);
            //構造響應
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);

            //設定頭資訊的的MIME型別
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");  //內容型別
            //設定要返回的內容長度
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); //內容長度
            //將響應物件返回
            ctx.channel().writeAndFlush(response);
            ctx.close();
        } else {
            System.out.println("the request is not HttpRequest");
        }
    }
    
}

6.最後,SpringBoot啟動類中新增Netty的命令列啟動:

package demo;

import demo.netty.NettyServer;
import io.netty.channel.ChannelFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;

/**
 * ClassName: SpringBootApplication
 * description:
 * author: 
 * date: 2018-09-30 09:15
 **/
@org.springframework.boot.autoconfigure.SpringBootApplication//@EnableAutoConfiguration @ComponentScan
public class SpringBootApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class,args);
    }


    @Autowired
    NettyServer nettyServer;

    @Override
    public void run(String... args) throws Exception {
        ChannelFuture start = nettyServer.start();
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                nettyServer.destroy();
            }
        });
        start.channel().closeFuture().syncUninterruptibly();

    }
}

四.啟動程式,瀏覽器訪問

1.啟動程式後的日誌:

2.瀏覽器訪問:127.0.0.1:7000,會看見hello world顯示在頁面,後端輸出:

可以發現,瀏覽器一次http請求向後端傳送了4次請求,原因是一個完整的http請求DefaultFullHttpRequest包含了所有的http請求資訊,但實際http請求的時候好像會將http請求行、請求頭、請求體分開發送,詳細可百度FullHttpRequest(推薦一篇:netty對http協議解析原理解析)。