浅析tomcat原理
淺析tomcat原理
上上個星期,看了一下how tomcat works這本書,今天撿起來看一會,發(fā)現(xiàn)忘得有點快,特地寫點東西,加深一下記憶。因為書講的是tomcat4,5的內(nèi)容,比較舊了,所以和最新的tomcat的差距還是有點大的。而且還沒看完,以后再補充吧。
而java實現(xiàn)web最簡單的方式就是對socket和serversocket的封裝。也是早期tomcat的實現(xiàn)方式。
計算機網(wǎng)絡之間的通信是基于端口之間通信,對于服務器,從端口讀取數(shù)據(jù),也就是inputStream, 往端口寫數(shù)據(jù),也就是outputStream。
serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1")); socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream();HttpServletRequest是請求,也就是客戶端往服務器端口發(fā)送消息,服務器從端口讀取數(shù)據(jù),所以HttpServletRequest封裝了上面的inputStream。 同理HttpServletResponse封裝了上面的outputStream。
ServerSocket 監(jiān)聽本機端口, 我們可以去瀏覽器輸入: http://127.0.0.1:8080 然后后臺讀取程序中讀取一個叫做index.html的文件(FileInputStream->bytes),output.write(bytes, 0, lenth). 將文件內(nèi)容輸出到端口,網(wǎng)頁中就會顯示內(nèi)容了。是不是有點熟悉。當然tomcat肯定不是這么簡單的實現(xiàn),但是最基本的原理就是如此。
當然這個是靜態(tài)資源,如果是動態(tài)servlet的處理,大致是通過類加載器,加載進內(nèi)存,反射得到對象。然后根據(jù)request請求信息,判斷是請求靜態(tài)文件還是動態(tài)servlet,然后執(zhí)行servlet的service(request, response)方法;
Connector 連接器
直接跳到連接器,有點太快了,書中還有很多小細節(jié),門面設計模式, response中getWriter方法就是封裝了outputStream… 推薦去看一下。
連接器,顧名思義,就是用來處理連接的。
private int port = 8080;private ServerSocket serverSocket = null;這是我直接在tomcat4源碼中HttpConnector拷貝過來的兩個參數(shù),然后再來看看這個類的一個重要方法。
public void run() {// Loop until we receive a shutdown commandwhile (!stopped) {Socket socket = null;try {socket = serverSocket.accept();...} catch () {...}HttpProcessor processor = createProcessor();...processor.assign(socket);...}...}一個循環(huán),等待連接的到來,得到一個socket對象, 然后創(chuàng)建一個處理器,去處理這個socket的請求。然后繼續(xù)等待下一個連接accept()。
當然如果tomcat這樣順序進行,那肯定是不行的,所以一個連接器有一個processor(處理器)的對象池。而每個處理器實現(xiàn)了runnable接口,在創(chuàng)建的時候就啟動了線程,然后阻塞自己,直到自己調(diào)用了assign() 方法,就是上面的那個方法,然后執(zhí)行處理request的方法,處理servlet還是靜態(tài)資源。完成后繼續(xù)阻塞自己。
所以tomcat的connector有多個processor,請求來了調(diào)用一個processor去執(zhí)行,而本身不需要等待這個processor完成,繼續(xù)接收下一個請求。是一種異步實現(xiàn)的感覺。這樣可以處理多個請求,而無需阻塞。這也是早期bio的解決方案。現(xiàn)在的解決方案應該是nio了。
final class HttpProcessor implements Lifecycle, Runnable {public void run() {while (!stopped) {Socket socket = await();if (socket == null)continue;try {process(socket);} catch (Throwable t) {log("process.invoke", t);}connector.recycle(this);}synchronized (threadSync) {threadSync.notifyAll();}} }就是在await()方法阻塞,然后釋放,執(zhí)行process完成,循環(huán)繼續(xù)阻塞。
processor 處理器
首先看看這個processor做了什么吧。
- 創(chuàng)建HttpServletRequest, HttpServletResponse對象。
- 解析連接
- 解析請求
- 解析頭部, 給request.setHeader。
- 隱式調(diào)用了servlet的service方法。 雖然從這里開始,但并不是直接調(diào)用。
具體是怎么解析的請求,我沒有深入去了解,大概就是拆分字符串,截取之類的吧,還是看看這個類最重要的方法。process() 方法。
private void process(Socket socket) {...try {input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());} catch (Exception e) {...}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) {...}...//這里解析請求,給豐富request和response。try {...if (ok) {// 重點。 畫上!!!connector.getContainer().invoke(request, response);}} catch (ServletException e) {...}}大致就是上面,可以看出,request和response封裝了輸入輸出流,然后解析請求, 調(diào)用了container的invoke(request, response)方法。所以我們希望見到的service方法就藏在這里面了。
Container 容器
這個可以說是tomat中最被人熟知的東西之一。tomcat4中有四大容器,以我的理解簡單介紹一下, 可能有點不對。
- Engine 引擎, 啟動一個tomcat服務,也就是啟動一個引擎
- host 虛擬主機, 一個Engine啟動,下面項目都會啟動,localhost:8080/work1,work2
- context 上下文, 一個項目對應一個上下文,通過map映射到不同的servlet。
- wrapper 包裝器, 一個wrapper對應一個servlet。
上面的四個都實現(xiàn)了Container接口,就先來談談wrapper吧。connector中有個方法,setContainer(Container container); 如果一個wrapper被一個connector綁定,那么回到上面畫重點的方法。
connector.getContainer().invoke(request, response);wrapper 包裝器
所以點進wrapper類中找尋這個方法,發(fā)現(xiàn)沒有,這個方法的實現(xiàn)在他的父類ContainerBase中有實現(xiàn)。
public void invoke(Request request, Response response) throws IOException, ServletException {pipeline.invoke(request, response);}出現(xiàn)了一個新的東西,叫做管道(pipeline)。點下去,會發(fā)現(xiàn)很麻煩,先來捋一捋Container中這些的關系。
container 中包含了一個pipeline, 而pipeline執(zhí)行invoke方法是創(chuàng)建了一個pipelineContext對象,并執(zhí)行invokeNext方法。 然后pipelineContext對象就會去執(zhí)行Valve的invoke方法。當然,執(zhí)行方式有點像遞歸,一直往下,直到執(zhí)行basicvalve的invoke方法,這個方法中會隱式執(zhí)行service方法,然后再回去執(zhí)行上一個valve。看看basicvalve的invoke方法。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {...StandardWrapper wrapper = (StandardWrapper) getContainer();ServletRequest sreq = request.getRequest();ServletResponse sres = response.getResponse();Servlet servlet = null;HttpServletRequest hreq = null;if (sreq instanceof HttpServletRequest)hreq = (HttpServletRequest) sreq;HttpServletResponse hres = null;if (sres instanceof HttpServletResponse)hres = (HttpServletResponse) sres;...try {if (!unavailable) {// 劃重點。。。servlet = wrapper.allocate();}} catch (ServletException e) {...}ApplicationFilterChain filterChain = createFilterChain(request, servlet);try {...if ((servlet != null) && (filterChain != null)) {// 劃重點2。。。filterChain.doFilter(sreq, sres);}...}}通過getContainer得到wrapper, wrapper.allocate(). 這個方法中通過反射之類的得到了servlet對象,返回。然后創(chuàng)建一個過濾器鏈, 調(diào)用doFilter方法傳遞request和response。我們再看看doFilter方法。
if ((request instanceof HttpServletRequest) &&(response instanceof HttpServletResponse)) {servlet.service((HttpServletRequest) request, (HttpServletResponse) response);} else {servlet.service(request, response);}這個方法中,終于找到了調(diào)用service方法了。當然上面說的都是wrapper。一個wrapper只對應一個servlet,但是一個項目肯定有多個servlet,那么這就涉及到我們熟悉的Context(上下文)了。
context 上下文
我們再回到connector,connector.setContainer(context). 如果connector設置的容器是上下文。再來分析一下。想起我們之前的那個方法。
connector.getContainer().invoke(request, response);context的invoke和wrapper一樣,pipeline.invoke, 然后同樣是pipelineContext.invokeNext。 那么不一樣的地方在哪里呢? 那就是basicvalve, 我們稱為contextValve。 我們看看contextValve的invoke方法。
public void invoke(Request request, Response response, ValveContext valveContext) throws IOException, ServletException {...Context context = (Context) getContainer();Wrapper wrapper = null;try {wrapper = (Wrapper) context.map(request, true);} catch (IllegalArgumentException e) {badRequest(requestURI, (HttpServletResponse) response.getResponse());return;}if (wrapper == null) {notFound((HttpServletResponse) response.getResponse());return;}response.setContext(context);wrapper.invoke(request, response);}相當簡潔, 就是context從request中讀取到類似 /IndexServlet, 然后就會去map中搜索,找到處理這個IndexServlet的servlet。也就是wrapper,執(zhí)行wrapper的invoke,又回到了上面wrapper的方法,記得上面的說的嗎,一個wrapper只處理一個servlet。而一個上下文對應多個servlet。 而這里的map就是我們經(jīng)常寫的xml映射關系。
<servlet-mapping> <servlet-name>IndexServlet</servlet-name> <url-pattern>/IndexServlet</url-pattern> </servlet-mapping>當然這個map有一個專門的類ContextMapper, 這個就不深究如何映射的。還有上面類加載,反射servlet的方法,其實也有一個專門的接口叫Loader。我也沒看太懂。
然后至于host和engine的實現(xiàn),沒看完,看看以后看完了有時間再來補上。原理應該差不多。
總結
以上是生活随笔為你收集整理的浅析tomcat原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySql连接异常解决
- 下一篇: [dp]Leetcode 5. Long