我们这些可怜虫,只有沿着大神的思路,这样我们才能进步得更快;因为我们不是跟大神处于同一级别上。所以我这里是参考《How Tomcat Works》这本英文版的大作来理解tomcat的工作原理
本人认为,Tomcat容器源码是学习java编程以及设计模式等的绝佳范例,深入理解其源码对我辈开发人员的编程水平提高大有裨益!
我们可以从该书指定的官方网址下载相关示例源码 http://www.brainysoftware.com,本文顺着作者的思路,介绍一个简单的web服务器
我们知道,web服务器是使用http协议与客户端进行通信,所以读者有必要先了解http协议格式;基于java的web服务器会使用两个重要的类 java.net.Socket与java.net.ServerSocket(服务器端与客户端通过Socket通信)
关于http协议,网上的资料汗牛充栋,本人在这里加上简略的描述(http协议基于tcp协议)
http客户端请求包括如下部分:
Method-URI-Protocol/Version 方法-地址-版本
Request header 请求头
Entity body 请求实体
比如 http://www.outletss.com/ 是本人以前帮客户做的网站,如果我们在浏览器打开该url地址,实际上客户端向服务器发送了如下格式的消息
GET http://www.outletss.com/ HTTP/1.1 Host: www.outletss.com Connection: keep-alive Accept: text/html ,application/xhtml+xml,application/xml ; q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Windows NT 5.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36 Accept-Encoding: gzip ,deflate, sdch Accept-Language: zh-CN ,zh ; q=0.8 Cookie: lzstat_uv=12863370662865423613|2989608 ; CKFinder_Path=Images%3A%2F%3A1; JSESSIONID=D7F9EC74149CB674D19A253B46273A77; lzstat_ss=1366758708_0_1375562495_2989608
http服务器端响应包括如下部分:
Protocol-Status code-Description 协议状态 描述代码
Response headers 响应头
Entity body 响应实体
然后服务器端向客户端响应了如下格式的消息
HTTP/1.1 200 OK Connection: close Date: Sat , 03 Aug 2013 15:00:30 GMT Server: Microsoft-IIS/ 6.0 X-UA-Compatible: IE = EmulateIE7 X-Powered-By: ASP.NET Set-Cookie: JSESSIONID =0A5B07FF5661CA6F8D87937A54B4EEF5 ; Path=/; HttpOnly Content-Type: text/html ; charset=UTF-8 Content-Language: zh-CN <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" > <html xmlns ="http://www.w3.org/1999/xhtml" > //这里省略了html代码 </html>
基于java的Socket编程,可以参考java网络编程相关资料,Socket服务器端与客户端的网络交互与本地文件系统I/O具有一致的编程模型,基本上也是输入流与输出流的概念(如果你不懂输入流输出流的概念,基本上还要去修炼)
Socket分为客户端与服务器端,Socket表示客户端套接字,ServerSocket表示服务器端套接字,我们参考书中示例,看一个简单的服务器怎么实现
HttpServer类表示一个web服务器,示例代码如下:
public class HttpServer { /** WEB_ROOT is the directory where our HTML and other files reside. * For this package, WEB_ROOT is the "webroot" directory under the working * directory. * The working directory is the location in the file system * from where the java command was invoked. */ public static final String WEB_ROOT = System.getProperty( "user.dir") + File.separator + "webroot" ; // shutdown command private static final String SHUTDOWN_COMMAND = "/SHUTDOWN" ; // the shutdown command received private boolean shutdown = false ; public static void main(String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await() { ServerSocket serverSocket = null ; int port = 8080 ; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1" )); } catch (IOException e) { e.printStackTrace(); System.exit( 1 ); } // Loop waiting for a request while (! shutdown) { Socket socket = null ; InputStream input = null ; OutputStream output = null ; try { socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // create Request object and parse Request request = new Request(input); request.parse(); // create Response object Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); // Close the socket socket.close(); // check if the previous URI is a shutdown command shutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); continue ; } } } }
在上面代码里面,首先创建一个ServerSocket实例,然后用一个while循环监听客户端请求,接收到客户端请求后,通过ServerSocket实例的accept方法返回Socket实例,将该Socket实例的输入流与输出流封装成Request实例与Response实例,并调用Response实例的void sendStaticResource()方法响应请求。
Request类代码如下:
public class Request { private InputStream input; private String uri; public Request(InputStream input) { this .input = input; } public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048 ); int i; byte [] buffer = new byte [2048 ]; try { i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1 ; } for ( int j=0; j<i; j++ ) { request.append(( char ) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(' ' ); if (index1 != -1 ) { index2 = requestString.indexOf(' ', index1 + 1 ); if (index2 > index1) return requestString.substring(index1 + 1 , index2); } return null ; } public String getUri() { return uri; } }
上面的void parse()方法是解析客户端的请求参数,这里是解析客户端请求的URL地址
Response类的代码如下:
/* HTTP Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF [ message-body ] Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF */ public class Response { private static final int BUFFER_SIZE = 1024 ; Request request; OutputStream output; public Response(OutputStream output) { this .output = output; } public void setRequest(Request request) { this .request = request; } public void sendStaticResource() throws IOException { byte [] bytes = new byte [BUFFER_SIZE]; FileInputStream fis = null ; try { File file = new File(HttpServer.WEB_ROOT, request.getUri()); if (file.exists()) { fis = new FileInputStream(file); int ch = fis.read(bytes, 0 , BUFFER_SIZE); while (ch!=-1 ) { output.write(bytes, 0 , ch); ch = fis.read(bytes, 0 , BUFFER_SIZE); } } else { // file not found String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>" ; output.write(errorMessage.getBytes()); } } catch (Exception e) { // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if (fis!= null ) fis.close(); } } }
这里最重要的是void sendStaticResource()方法,用于向输出流写入数据(这里是静态文件),响应客户端请求
本文介绍的是一个最简单的web服务器,Tomcat容器的复杂性远不止如此简单,待后文接着分析
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179 # 163.com ( #改为@ )