简单的tomcat实现

做了长时间web开发,一直都是用spring,导致自己成了操作工,按照既定的模子,重复的劳动,没有丝毫的进步,所以想深入的了解一番干了这么长时间的web的整个运行流程,绝大多数web开发学习应该都是servlet开始的吧,所以又重拾了servelt狠狠的研究了一番,最后发现servlet其实就是些标准,那啥为标准,说白了,就是定了些接口,导致看源码的过程很不过瘾,感觉没啥提升,就决定了解下更底层的工作原理,也就是tomcat。
开始我并不知道tomcat是java写的,对tomcat那是神秘,惧怕,感觉太过高大上,自己做的那点web开发和tomcat这种红遍全球的软件相比简直小巫见大巫,技术含量压根儿就不是一个层次。不过我还是想一窥究竟,偶然的机会,看到了《深入剖析Tomcat》,作者对tomcat源码讲解的很是详细,通过循序渐进的例子,非常认真的讲出了tomcat的精髓,我是受益匪浅。现在我想自己也写一个,来作为这阶段学习的一个交代,就叫tiny-tomcat吧,同样通过循序渐进,慢慢完善,源码在github上:https://github.com/esiyuan/tiny-tomcat.git

简单的web服务器

git checkout step-001:主要功能如下

  • 接受http请求
private void handlerRequest() throws IOException {
           while(true) {
                Socket socket = serverSocket.accept();
                logger.info("获取连接[ address: " + socket.getInetAddress() + ", port: " + socket.getPort() + " ]");
                Request request = new Request(socket.getInputStream());
                request.parse();
                Response response = new Response(request.getUri(), socket.getOutputStream());
                response.sendStaticResource();
                socket.close();
           }
     }

通过ServerSocket监听本地端口,serverSocket.accept()等待请求。

  • 解析请求uri
public void parse() throws IOException {
           BufferedReader reader = IOUtil.getBufferedReader(inputStream);
           String requestString = reader.readLine();
           this.uri = parseUri(requestString);
     }

为了分开请求和响应,贴合servlet风格,我们通过Request对象,解析请求字符串,request对象构造的时候,传入socket的输入流,由于我们只对第一行感兴趣,所以在只读取了第一行进行解析,解析出uri作为属性保存。

  • 根据uri定位html文件,并返回响应

最后通过 Response对象进行html响应,而response在构造的时候,传入socket的输出流。

/**
      * 输出响应
      * <p>文件找到uri对应的文件,则进行输出,否则输出404
      * @throws IOException
      */
     public void sendStaticResource() throws IOException {
           File file = new File(Constants.WEB_ROOT, uri);
           if (file.exists()) {
                sendFile(file);
           } else {
                sendDefault();
           }
     }

这里写图片描述

简单的servlet容器

git checkout step-002
主要在前一节代码上进行了部分改造,增加了响应servlet的功能,并分出了简单的servlet处理器,因为HttpServletRequest和HttpServletResponse是接口,为了达到演示的效果,避免request和response类过于复杂,使用了门面RequestFacade和ResponseFacade。

public class ServletProcessor {

    private ConcurrentHashMap<String, Servlet> cache = new ConcurrentHashMap<String, Servlet>();
    /**
     * servlet没有加载,则加载并初始化
     * <p>如有加载直接取缓存
     * @param request
     * @param response
     */
    public void process(Request request, Response response){
            Servlet servlet = cache.get(request.getServerName());
            if(servlet != null) {
                try {
                    servlet.service(request, response);
                } catch (ServletException | IOException e) {
                    e.printStackTrace();
                }
                return;
            }
            try(URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file://" + Constants.TOMCAT_CLASSLOADER_REPOSITORY)});) {
                Class<?> clazz = classLoader.loadClass("web_root." + request.getServerName());
                servlet = (Servlet)clazz.newInstance();
                servlet.init(null);
                servlet.service(request, response);
                cache.putIfAbsent(request.getServerName(), servlet);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("服务异常!");
            }
    }
}

为了体现servlet的生命周期,增加了init()方法,并且通过ConcurrentHashMap缓存了sevlet实例,为什么用ConcurrentHashMap,主要是考虑线程安全,其分段锁的设计,能够拥有较高的并发写的能力,同时并不会妨碍并发读取,由于读没有进行加锁,不需要等待写完成,所有必然的会造成数据的弱一致性,不过大多数时候都会有取出非空判断逻辑,也造不成什么问题。
servlet代码如下:

public class HelloServlet extends HttpServlet {

    private static final long serialVersionUID = -2585140950753353037L;

    private static final Logger logger = Logger.getLogger(HelloServlet.class);


    public void service(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("hello servlet...");
    }


    @Override
    public void init() throws ServletException {
        System.out.println("HelloServlet..初始化开始..");
    }


}

运行结果:
这里写图片描述

非阻塞的servlet容器

git checkout step-003
前面的实现,都是非常简单的单线程,万一线程阻塞,那岂不是不能提供服务了,实际的产品肯定不能是这样的。tomcat连接器监听端口,获取创建socket,这个应该是单点,而容易阻塞的地方,应该是具体业务逻辑处理的代码了,为了保证服务的可用,提高系统的吞吐率,可以使用多线程提供多个处理器,让每个请求有单独的线程进行处理,这样性能会大幅度提高,不会以为单个连接出现问题而造成服务不可用。下面我们分析具体的代码。
Bootstrap应用启动,主要功能,初始化连接器:

    public final class Bootstrap {
    public static void main(String[] args) {
        HttpConnector connector = new HttpConnector("localhost", 8080);
        try {
            connector.initialize();
            connector.start();
            System.in.read(); //连接器线程,让主线程挂起,保证其他后台线程运行
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (TomcatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

连接器进行本地端口的监听,实例化并运行处理器:

/**
     * 初始化连接处理器和处理器
     * @throws TomcatException
     * @throws UnknownHostException
     * @throws IOException
     */
    public void initialize() throws TomcatException, UnknownHostException, IOException {
        createServer();
        while (curProcessors < minProcessors) {
            if (curProcessors >= maxProcessors)
                break;
            HttpProcessor processor = newProcessor();
            recycle(processor);
        }
    }

处理器进入wait状态(HttpProcessor中方法),等待被唤醒。

    @Override
    public void run() {
        try {
            while(!Thread.interrupted()) {
                Socket socket = waitingToNewSocket();
                process(socket);
            }
        } catch (InterruptedException | IOException | ServletException e) {
            System.out.println("处理器异常。。。");
        }
    }

    private synchronized Socket waitingToNewSocket() throws InterruptedException {
        while (!newSocketCome) {
            wait();
        }
        Socket socket = this.socket;
        newSocketCome = false;
        return (socket);
   }

当有请求到来时,唤醒挂起的处理器,处理 完成,处理器重新入栈,等待下次被使用。

public synchronized void assign(Socket socket) {
        this.socket = socket;
        newSocketCome = true;
        notifyAll();
    }

模块化的web容器

git checkout step-004
tomcat作为一个大型的产品,为了开发维护的方便,必然的会把大任务进行分解,进行分层分模块,这也是如今软件设计的思路,模块清晰,层次明了,对于后期的维护,更新都会有极大的便利。
tomcat进行功能的拆分,模拟管道与阀的思想,对于容器的处理组件,都放在阀中,请求会像流水一样流过每个阀,最后得到最终的处理。

这里写图片描述
而tomcat通过接口来定下标准,Pipeline接口表示管道,Valve表示阀,具体可以看代码。

生命周期监听

git checkout step-005
本节主要增加了生命周期控制组件,接口Lifecycle,定义组件的生命周期,主要增加了事件监听模块,监听tomcat启动状态改变,并作出反应,下图监听的逻辑。
这里写图片描述
每个实现生命周期接口的组件都能进行监听器的绑定,统一的实现监听器的控制逻辑,使用了LifecycleSupport来代理,实现监听器绑定,解绑,通知。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是简单实现Tomcat的步骤: 1.创建一个ServerSocket对象,监听80端口。 2.使用while循环,不断接收客户端的请求。 3.当接收到请求时,创建一个新的线程来处理该请求。 4.在新线程中,解析HTTP请求,获取请求的方法、URL、参数等信息。 5.根据URL找到对应的Servlet,并调用其service()方法处理请求。 6.Servlet处理完请求后,将响应结果返回给客户端。 7.关闭Socket连接。 以下是一个简单Tomcat实现示例: ```python import socket import threading class Tomcat: def __init__(self): self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.bind(('localhost', 80)) self.server_socket.listen(5) def start(self): while True: client_socket, client_address = self.server_socket.accept() t = threading.Thread(target=self.handle_request, args=(client_socket,)) t.start() def handle_request(self, client_socket): request_data = client_socket.recv(1024) request_lines = request_data.decode().split('\r\n') method, url, protocol = request_lines[0].split(' ') if '?' in url: url, query_string = url.split('?') else: query_string = '' env = { 'REQUEST_METHOD': method, 'PATH_INFO': url, 'QUERY_STRING': query_string } response_body = self.application(env, self.start_response) response_header = 'HTTP/1.1 {}\r\n'.format(self.status) for header in self.headers: response_header += '{}: {}\r\n'.format(header[0], header[1]) response_header += '\r\n' response = response_header.encode() + response_body client_socket.send(response) client_socket.close() def start_response(self, status, headers): self.status = status self.headers = headers def application(self, env, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return b'<h1>Hello, World!</h1>' if __name__ == '__main__': tomcat = Tomcat() tomcat.start() ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值