1. 程式人生 > 其它 >手寫tomcat——netty版

手寫tomcat——netty版

點選檢視程式碼
package com.grady.diytomcat;

import com.grady.diytomcat.handler.DiyNettyTomcatHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.List;

public class DiyTomcat {

    private int port = 8080;

    public static final HashMap<String, DiyNettyServlet> SERVLET_MAPPING = new HashMap<>();

    public static final HashMap<String,String> URL_MAPPING = new HashMap<>();


    static {
        loadServlet();
    }

    private static void loadServlet() {
        try {
            //獲取web.xml目錄地址
            String path = DiyTomcat.class.getResource("/").getPath();
            SAXReader reader = new SAXReader();
            //讀取web.xml檔案
            Document document = reader.read(new File(path + "web.xml"));
            //獲取根標籤(servlet和servlet-mapping),放在一個List中
            Element rootElement = document.getRootElement();
            List<Element> elements = rootElement.elements();
            //迴圈將對映寫進map對映裡
            for(Element element : elements){
                if ("servlet".equalsIgnoreCase(element.getName())){
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    //需要注意的是servletMapping對映的第二個引數,要通過反射的方式進行例項化
                    SERVLET_MAPPING.put(servletName.getText(),
                            (DiyNettyServlet) Class.forName(servletClass.getText().trim()).newInstance());
                }else if ("servlet-mapping".equalsIgnoreCase(element.getName())){
                    Element servletName = element.element("servlet-name");
                    Element urlPattern = element.element("url-pattern");
                    URL_MAPPING.put(urlPattern.getText(), servletName.getText());
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    public void start() throws IOException {
        // Boss執行緒
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // worker執行緒
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        // 1.建立物件
        ServerBootstrap server = new ServerBootstrap();
        //2. 配置引數
        server.group(bossGroup,workerGroup)
                // 主執行緒處理類
                .channel(NioServerSocketChannel.class)
                // 子執行緒處理類 Handler
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new HttpResponseEncoder());
                        socketChannel.pipeline().addLast(new HttpRequestDecoder());
                        socketChannel.pipeline().addLast(new DiyNettyTomcatHandler());
                    }
                })
                //針對主執行緒的配置,最大執行緒數128
                .option(ChannelOption.SO_BACKLOG,128)
                // 針對子執行緒的配置,保持長連線
                .childOption(ChannelOption.SO_KEEPALIVE,true);

        try {
            // 3 啟動伺服器
            ChannelFuture f = server.bind(port).sync();
            System.out.println("DiyTomcat啟動成功,監聽的埠是:" + port);
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1. netty版會稍有些不同,因為netty內部的核心是channel(對socket進行了一層封裝處理),所以我們不能直接拿到socket對應的InputStreamOutputStream
2. 我們的核心處理邏輯在DiyNettyTomcatHandler
package com.grady.diytomcat.handler;

import com.grady.diytomcat.DiyNettyRequest;
import com.grady.diytomcat.DiyNettyResponse;
import com.grady.diytomcat.DiyNettyServlet;
import com.grady.diytomcat.DiyTomcat;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpRequest;

public class DiyNettyTomcatHandler extends ChannelInboundHandlerAdapter {

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

        if (msg instanceof HttpRequest){
            HttpRequest req = (HttpRequest) msg;

            DiyNettyRequest request = new DiyNettyRequest(ctx, req);
            DiyNettyResponse response = new DiyNettyResponse(ctx, req);

            // 實際業務處理
            String url = request.getUrl();
            if(DiyTomcat.URL_MAPPING.containsKey(url)) {
                String servletName = DiyTomcat.URL_MAPPING.get(url);
                DiyNettyServlet servlet= DiyTomcat.SERVLET_MAPPING.get(servletName);
                servlet.service(request, response);
            } else {
                response.write("404 - Not Found");
            }

        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

    }

}

在這裡,將ChannelHandlerContext 封裝到DiyNettyRequest
DiyNettyResponse中,這樣我們的requestresponse就擁有了讀資料和寫資料的能力

<br/>

原始碼地址:
https://github.com/ZhongJinHacker/diy-tomcat/tree/netty-tomcat