深入刨析tomcat 之---第3篇 HTTP/1.1 長連線的實現原理
阿新 • • 發佈:2021-07-02
writedby 張豔濤
長連線是HTTP/1.1的特徵之一,1.1出現的原因是因為一個客戶請求一個網頁,這是一個http請求,這個網頁中如果有圖片,那麼也會變為一個http請求,對於java客戶端,一個http請求
是通過socket.getinputstream.cast(PrintWriter).println("http請求頭"),如果倆個請求都通過一個socket來寫資料,那麼這個就是http長連線,如果你寫一個簡單http伺服器,那你實現的就不是長連線,每次請求都把socket.close()了,
所以判斷一個http請求時不是長連線就是判斷socket.close有沒有執行
那麼我們來看tomcat是如何實現長連線了的,對應深入理解tomcat第4章
實現思路是,如果socket不斷開的話,那麼socket.getInputStream(),得到的in流 會呼叫in.read()方法,進行阻塞,如果來了資料,讀取新進來的請求,如果滿足http協議進行解析;
HttpProcessor類 public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } }
進入方法
private void process(Socket socket) { boolean ok = true; boolean finishResponse = true; SocketInputStream input = null; OutputStream output = null; // Construct and initialize the objects we will need try { input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize()); } catch (Exception e) { log("process.create", e); ok = false; } keepAlive = true; while (!stopped && ok && keepAlive) { finishResponse = true; try { 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 try { if (ok) { parseConnection(socket); parseRequest(input, output); if (!request.getRequest().getProtocol() .startsWith("HTTP/0")) parseHeaders(input); if (http11) { // Sending a request acknowledge back to the client if // requested. ackRequest(output); // If the protocol is HTTP/1.1, chunking is allowed. if (connector.isChunkingAllowed()) response.setAllowChunking(true); } } 異常...略 } // Finish up the handling of the request if (finishResponse) { try { response.finishResponse(); } catch (IOException e) { ok = false; } catch (Throwable e) { log("process.invoke", e); ok = false; } try { request.finishRequest(); } catch (IOException e) { ok = false; } catch (Throwable e) { log("process.invoke", e); ok = false; } 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(); } catch (IOException e) { ; } catch (Throwable e) { log("process.invoke", e); } socket = null; }
進入方法
input.readRequestLine(requestLine);
接著進入
public void readRequestLine(HttpRequestLine requestLine) throws IOException { // Recycling check if (requestLine.methodEnd != 0) requestLine.recycle(); // Checking for a blank line int chr = 0; do { // Skipping CR or LF try { chr = read(); } catch (IOException e) { chr = -1; } } while ((chr == CR) || (chr == LF)); if (chr == -1) throw new EOFException (sm.getString("requestStream.readline.error")); pos--; // Reading the method name
接著進入
public int read() throws IOException { if (pos >= count) {//讀到了結尾 fill(); if (pos >= count) return -1; } return buf[pos++] & 0xff; }
接著
/** * Fill the internal buffer using data from the undelying input stream. */ protected void fill() throws IOException { pos = 0; count = 0; int nRead = is.read(buf, 0, buf.length); if (nRead > 0) { count = nRead; } }
parseRequest(input, output); //input的構造方法 input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize()); // public SocketInputStream(InputStream is, int bufferSize) { this.is = is; buf = new byte[bufferSize]; }
上述倆個方法中的fill() 的is.read() 底層就是socket.getInputStream進行讀到緩衝區
這個方法是有阻塞的,那麼就實現了處理完一個http請求,接著讀取第二個請求
如果以上過程報錯,跳出while迴圈
try { shutdownInput(input); socket.close(); } catch (IOException e) { ; } catch (Throwable e) { log("process.invoke", e); } socket = null;
關閉socket,那麼就斷開了socket長連線
此文結束