tomcat4默认连接器简要分析

1、HTTP 1.1新特性

1)持久连接:connection: keep-alive

2)块编码:使用长连接后,发送方大部分时候无法计算出要发送的内容长度,也不能等所有资源都准备好了再发送,HTTP 1.1 使用transfer-encoding header来处理这个问题。transfer-encoding表示有多少以块形式的字节流将会被发送给接收方,对于每一个块数据,长度(16进制)+ CR/LF + 数据将会被发送,整个事物以0\r\n结束。例如:I'm as helpless as a kitten up a tree.这个数据如果以3个块发送的话,其发送格式为:

1D\r\n
I'm as helpless as a kitten u
9\r\n
p a tree.
0\r\n

3) Expect: 100-continue Header 使用,当要发送长内容请求的时候,客户端在无法保证服务器一定会接收的时候,可以先发送这个header到服务器询问是否允许客户端的长内容请求,如果允许,服务端会返回HTTP/1.1 100 Continue + CRLF + CRLF给客户端。

2、连接器的职责:

1)接收客户端的请求,解析HTTP协议:请求行(GET /api/xxx HTTP/1.1 ...)解析,header解析,数据解析
2)构建Request对象
3)构建Response对象

3、连接器处理流程

image.png
image.png
image.png

整个类图分为三块,类图1为连接器和容器、连接器和处理器之间的关系;类图2为Request结构图,类图3为Response结构图。

3、1 HttpConnector启动阶段

1)通过调用initialize()方法和open()方法,创建了一个ServerSocket对象

2)调用start()方法,一是启动接收客户端请求的线程,二是初始化HttpProcessor对象池,该对象池为Stack,这里主要看一下HttpProcessor的创建和启动。

private HttpProcessor newProcessor() {
        HttpProcessor processor = new HttpProcessor(this, curProcessors++);
        if (processor instanceof Lifecycle) {
            try {
                /**
                 * HttpProcessor 本身是一个Runnable和Lifecycle,在这里对其进行了启动操作,
                 * 其实就是启动了一个线程,来执行HttpProcessor这个Runnable
                 */
                ((Lifecycle) processor).start();
            } catch (LifecycleException e) {
                log("newProcessor", e);
                return (null);
            }
        }
        created.addElement(processor);
        return (processor);
    }
3、2 HttpConnector对请求处理过程
public void run() {
    socket = serverSocket.accept();
    HttpProcessor processor = createProcessor();
    processor.assign(socket);
}

run()主体逻辑就上面三行代码,在接收到请求后,先通过createProcessor方法获取一个HttpProcessor对象,createProcessor获取HttpProcessor对象的逻辑比较简单,直接看代码就行。

private HttpProcessor createProcessor() {
        synchronized (processors) {
            if (processors.size() > 0) {
// 对象池中还有对象,直接从池子里面获取
                return ((HttpProcessor) processors.pop());
            }
            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {
// 池子里面没有了,只要没有超过设置的最大容量,就创建一个对象
                return (newProcessor());
            } else {
                if (maxProcessors < 0) {
// 如果最大容量设置为小于0,则直接创建一个对象
                    return (newProcessor());
                } else {
                    return (null);
                }
            }
        }
    }

获取到HttpProcessor之后,调用其assign方法。

3、3 HttpProcessor启动过程

通过上面知道,HttpProcessor是在HttpConnector的newProcessor()中进行创建和启动的,在start()中,其主要工作是启动了处理线程

public void start() throws LifecycleException {
        if (started)
            throw new LifecycleException
                (sm.getString("httpProcessor.alreadyStarted"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;
// 启动线程,Runnable为本身
        threadStart();
    }

现在看一下这个线程的主要工作是啥:

 public void run() {
        // Process requests until we receive a shutdown signal
        while (!stopped) {
            // Wait for the next socket to be assigned
            // 1、调用await()方法获取一个socket
            Socket socket = await();
            if (socket == null)
                continue;
            // Process the request from this socket
            try {
            // 2、对socket进行处理
                process(socket);
            } catch (Throwable t) {
                log("process.invoke", t);
            }
            // Finish up this request
            // 3、对象回收
            connector.recycle(this);
        }
        // Tell threadStop() we have shut ourselves down successfully
        synchronized (threadSync) {
            threadSync.notifyAll();
        }
    }

可以看到,其工作主要是:从await()方法获取socket,交给process()方法处理,然后HttpConnector对该对象进行回收再利用。

看一下socket是如何获取的:

private synchronized Socket await() {
        // Wait for the Connector to provide a new Socket
        while (!available) {
            try {
                /**
                 * 进入等待状态,直到HTTPConnector线程调用了notifyAll().
                 * 这个唤醒操作在assign()中完成。
                 */
                wait();//
            } catch (InterruptedException e) {
            }
        }
        // Notify the Connector that we have received this Socket
        Socket socket = this.socket;
        available = false;
        /**
         * 这里是唤醒assign()中的wait(),让其可以继工作了。
         */
        notifyAll();
        return socket;
    }

await首先进入一个等待状态,只有被其他线程唤醒了,才会进行下一步的工作,也就是返回socket,那么这个等待由谁唤醒呢?在HTTPConnector的run()方法中可以知道,socket来源于HTTPConnector,然后其把这个socket传给了HttpProcessor的assign()方法:

synchronized void assign(Socket socket) {
        // Wait for the Processor to get the previous Socket
        while (available) {
            try {
// 让当前线程进入等待状态,assign方法由HTTPConnector所在的线程
// 调用,wait是在HttpProcessor对象中起调的,所以这里是
// HttpProcessor让HTTPConnector所在的线程进行等待
                wait();
            } catch (InterruptedException e) {
            }
        }
        // Store the newly available Socket and notify our thread
        this.socket = socket;
        available = true;
// 唤醒其他线程中的所有wait()方法,这里的调用者是HttpProcessor,
//也就是唤醒HttpProcessor线程中所有的wait()方法
        notifyAll();
    }

assign()方法接收来自于HTTPConnector的socket,然后赋值在HttpProcessor的全局变量this.socket上,在await()方法中就能获取到这个socket了,因为assign()工作在HTTPConnector所属的线程,而await()工作在HttpProcessor所属的线程中,两者之间通过wait()和notifyAll()来互相通信。

这里有个疑问,不知道assign()里面为什么要wait(),await()里面为什么要notifyAll(),根据设计,HttpProcessor在当前socket没有处理完的时候,其不会被回收到对象池中,也就根本没有机会去处理下一个socket,但是这里却这样设计了,而且看await方法返回的socket也不是全局变量,而是用了一个局部变量来存储然后返回的是这个局部变量。官方的解释是说全局的变量用来放置下一个到来的socket,以防当前socket在没有完全处理完成而下一个socket又到来的情况,但是这种情况是怎么出现的,不解。

3、4 HttpProcessor解析HTTP协议过程简析

tomcat默认连接器对socket的处理逻辑,主要在HttpProcessor类中的process()方法,其主要工作是解析连接,解析请求行,解析头部,这里它没有对参数进行解析,参数是在需要的时候才会进行解析,主要是不过多的占用CPU时间,使CPU有更多的时间来处理客户的请求,提升并发量。

private void process(Socket socket) {
        // 用来记录处理过程是否正确
        boolean ok = true;
        //  用来标记Response接口中的 finishResponse 方法是否应该被调用
        boolean finishResponse = true;
        SocketInputStream input = null;
        OutputStream output = null;// 输出流
        // Construct and initialize the objects we will need
        try {
            // 包装了一个InputStream,用来解析请求行和头部信息
            input = new SocketInputStream(socket.getInputStream(),
                                          connector.getBufferSize());
        } catch (Exception e) {
            log("process.create", e);
            ok = false;
        }

        // 是否持久连接,HTTP/1.1才设置为true,见parseRequest()
        keepAlive = true;
        while (!stopped && ok && keepAlive) {
            finishResponse = true;
            try {// request/response基本配置,输入输出流
                request.setStream(input);
                request.setResponse(response);
                output = socket.getOutputStream();
                response.setStream(output);
                response.setRequest(request);
                ((HttpServletResponse) response.getResponse()).setHeader("Server", SERVER_INFO);
            } catch (Exception e) {
                log("process.create", e);
                ok = false;
            }

            // Parse the incoming request
                if (ok) {
                    // 解析服务器地址和端口号
                    parseConnection(socket);
                    // 这个主要是用来解析请求行: GET /api/xxx?a=11&b=22.... HTTP/1.1
                    parseRequest(input, output);
                    // Header解析
                    if (!request.getRequest().getProtocol().startsWith("HTTP/0")) {
                        parseHeaders(input);
                    }
                    // 如果是HTTP/1.1,回复"HTTP/1.1 100 Continue\r\n\r\n"
                    if (http11) {
                        ackRequest(output);
                        // If the protocol is HTTP/1.1, chunking is allowed.
                        if (connector.isChunkingAllowed()) {
                            response.setAllowChunking(true);
                        }
                    }
                }
           
            // Ask our Container to process this request
            try {
                if (ok) {
                    // 交给Container来处理请求
                    connector.getContainer().invoke(request, response);
                }
            } 

            // Finish up the handling of the request
            if (finishResponse) {
                try {
                    response.finishResponse();
                } 
                try {
                    request.finishRequest();
                } 
                try {
                    if (output != null)
                        output.flush();
                } catch (IOException e) {
                    ok = false;
                }
            }

            // We have to check if the connection closure has been requested
            // by the application or the response stream (in case of HTTP/1.0
            // and keep-alive).
            if ( "close".equals(response.getHeader("Connection")) ) {
                keepAlive = false;
            }
            // End of request processing
            status = Constants.PROCESSOR_IDLE;
            // Recycling the request and the response objects
            request.recycle();
            response.recycle();
        }
        try {
            shutdownInput(input);
            socket.close();
        }
        socket = null;
    }

整体流程上还是比较清晰的,具体怎么解析的这里就不描述了,在处理完成后,是交给了容器的invoke方法进行处理的,至于容器是如何处理这个请求的,这个只有在分析完容器后才知晓了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,022评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,015评论 11 349
  • Java继承关系初始化顺序 父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父...
    第六象限阅读 2,081评论 0 9
  • 今天的拍球练习,发现好久不练退步了不少。而且和朋友聊天发现下面要把大运动训练重视起来,最好能成为至少是每周的必练项...
    Hisi阅读 229评论 0 0