关于这篇文章也确实筹划了很久,今天决定开篇写第一篇,说起 tomcat 首先很容易联想到 IIS ,因为我最开始使用的就是 .net 技术,我第一次使用 asp 写学生成绩管理系统后,很茫然如何让别人都能看到或者说使用这个系统呢?由此认识了 IIS ,它是一个 web 容器,天生的多线程,及时响应用户提交的请求返回 html 页面,这就是我了解的最初的 web 容器的功能,由此我们来认识 tomcat 也并不困难,可以的话,在了解完 tomcat 后我们可以继续了解 jboss 、 jetty 等,好我们进入主题。
我们在平时开发的过程中是在使用 eclipse 时候才启动 tomcat ,对于一个 web 容器而言,简而言之,它是系统的一个守护进程,守护着对这台服务器某个端口发起的请求,基于这一点,它就需要一个监听程序,这个监听程序来获取来自这个端口的特定请求的数据, ok ,直接点讲,我们这里使用 Socket 来获取某个端口,通常是 80 端口的 http 请求,通过简单的 Java
程序的死循环(粗糙的做法,后面逐步优化)来实现不断的获取 80 端口 http 请求,来达到监听 80 端口 http 请求的目的。j ava.net包 下面的 Socket 和 ServerSocket 两个类就能实现我们对 8080 端口的监听,去除中间的逻辑代码,我们只看这个两个类的演绎的话如下:
1 ServerSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("10.10.10.106"));
对本机的 8080 端口进行监听
1 socket = serverSocket.accept(); 2 input = socket.getInputStream(); 3 output = socket.getOutputStream();
以上代码就是获取监听结果。
这是最简单和最精简的Socket 通讯原理,基于这个核心点我们来开发一个简易的,可以提供静态页面访问的 custom tomcat ,准备一个 index.html 文件放到 /home/webroot 目录下,那么除去拓展上面代码外,我们还需要一个 Response 和一个 Request 。
类设计如下:
HttpServer : 主函数所在类,负载启动 ServerSocket 和 操作整合 Socket 监听到的数据,以及返回结果,即操作 Response 和 Request 。
Request: 封装 Socket 监听到的用户端请求,包括请求的 http uri 信息。
Response : 封装需要推送到客户端的结果数据,即我们需要根据 http uri 去本机寻找相应的资源,写给客户端。
言简意赅,进入代码,首先 Request 类代码:
1 public class Request 2 { 3 private InputStream input; 4 private String uri; 5 6 public Request(InputStream input) { 7 this .input = input; 8 } 9 10 public void parse() 11 { 12 StringBuffer request = new StringBuffer(2048 ); 13 int i; 14 byte [] buffer = new byte [2048 ]; 15 try 16 { 17 i = input.read(buffer); 18 } 19 catch (IOException e) 20 { 21 e.printStackTrace(); 22 i = -1 ; 23 } 24 25 for ( int j=0; j<i; j++ ) 26 { 27 request.append(( char ) buffer[j]); 28 } 29 System.out.print(request.toString()); 30 uri = parseUri(request.toString()); 31 } 32 33 private String parseUri(String requestString) 34 { 35 int index1, index2; 36 index1 = requestString.indexOf(' ' ); 37 if (index1 != -1 ) { 38 index2 = requestString.indexOf(' ', index1 + 1 ); 39 if (index2 > index1) 40 return requestString.substring(index1 + 1 , index2); 41 } 42 return null ; 43 } 44 45 public String getUri() 46 { 47 return uri; 48 } 49 }
代码解释:类包括一个属性和两个方法, input 属性即是从 Socket 监听到的信息, Socket 会将监听到的信息放入一个 InputStream 中,我们使用 Reqeust 类的 Input 属性来接受。接收到输入流后,在 parse 中对这个输入流进行解析成字符串,即对 Http 请求进行拆解,得到完整的 Http URL ,所以这个方法是私有的,是类存在意义的核心所在,而提供的对外方法 parseUri 是负载将 parse 解析的 url 结果提供给外界,即,客户端发来请求那个文件,具体的是最终提供给 Response 类, Response 类得到这个文件名称后,去本地制定目录读取文件。 Tomcat 中通常就是 webapps 目录啦,很熟悉了吧,哈哈。
Response 类如何实现这个读取文件的历史使命呢,代码如下 :
1 public class Response { 2 3 private static final int BUFFER_SIZE = 1024 ; 4 Request request; 5 OutputStream output; 6 7 public Response(OutputStream output) 8 { 9 this .output = output; 10 } 11 12 public void setRequest(Request request) 13 { 14 this .request = request; 15 } 16 17 public void sendStaticResource() throws IOException 18 { 19 byte [] bytes = new byte [BUFFER_SIZE]; 20 FileInputStream fis = null ; 21 try 22 { 23 File file = new File(HttpServer.WEB_ROOT, request.getUri()); 24 if (file.exists()) 25 { 26 fis = new FileInputStream(file); 27 int ch = fis.read(bytes, 0 , BUFFER_SIZE); 28 while (ch!=-1 ) { 29 output.write(bytes, 0 , ch); 30 ch = fis.read(bytes, 0 , BUFFER_SIZE); 31 } 32 } 33 else 34 { 35 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 36 "Content-Type: text/html\r\n" + 37 "Content-Length: 23\r\n" + 38 "\r\n" + 39 "<h1>File Not Found</h1>" ; 40 output.write(errorMessage.getBytes()); 41 } 42 } 43 catch (Exception e) 44 { 45 System.out.println(e.toString()); 46 } 47 finally { 48 fis.close(); 49 } 50 51 } 52 }
代码解释: Response 一共三个属性,一个方法。三个属性,一个是设置属性, BUFFER_SIZE 设置读写字节流大小,关于读写文件,我个人觉得和服务器的性能和程序性能息息相关,不宜设定过大或过小(此处有不同见解的同仁欢迎来喷,我对这块理解目前限于此)。 Reqeust 属性,对照前文呼应, Response 需要获取 Request 类的 uri 结果信息,所以这里放了一个 Request 属性,获取 uri 。 Output ,就不用说了,也是这个类存在的核心意义,依照 Request 类提供的 uri 信息,在本地读写文件后,形成一个输出来,存放到 output 中,那么这项工作就由 sendStaticResource 这个共有方法完成啦。
好,代码到这个,可以说我们大家已经看到一个 tomcat 模型了,有点万事俱备,只欠东风的感觉,客户端发起请求, Response 和 Reqeust 有了,那么继续往上游考虑, Reqeust 依赖于客户端的请求,自然以来于 Socket 数据。我们在这里做得简便一点,将 ServerSocket 和 Socket 封装到一个 HttpServer 类中来,代码如下:
1 public class HttpServer { 2 3 public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot" ; 4 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN" ; 5 private boolean shutdown = false ; 6 public static void main(String[] args) 7 { 8 HttpServer httpServer = new HttpServer(); 9 httpServer.await(); 10 } 11 12 public void await() 13 { 14 ServerSocket serverSocket = null ; 15 Integer port = 8080 ; 16 try 17 { 18 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("10.10.10.106" )); 19 } 20 catch (IOException e) 21 { 22 e.printStackTrace(); 23 System.exit(1 ); 24 } 25 26 while (! shutdown) 27 { 28 Socket socket = null ; 29 InputStream input = null ; 30 OutputStream output = null ; 31 try 32 { 33 socket = serverSocket.accept(); 34 35 input = socket.getInputStream(); 36 output = socket.getOutputStream(); 37 Request request = new Request(input); 38 request.parse(); 39 Response response = new Response(output); 40 response.setRequest(request); response.sendStaticResource(); socket.close(); 41 shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 42 } 43 catch (Exception e) 44 { 45 e.printStackTrace(); continue ; 46 } 47 } 48 } 49 }
代码解释:我们知道启动 tomcat 之后,只要服务正常,客户端任意时候发起一个 http 请求, tomcat 就会响应,那么这里我们肯定需要一个 while 循环来模拟不间断的监听,类 await 方法就是负责不断的获取 socket 监听到的结果,有立刻调动 Reqeust 和 Response 进行响应,加入主函数,为的是我们这个是模拟的控制台程序,需要一个程序入口, main 函数就是程序入口。此外, HttpServer 类包括一个静态属性 SHUTDOWN_COMMAND ,输入为 true 则停止这个 main 函数,变量初始值为 false ,当客户端也就是 Request 响应得到客户端输入 http://10.10.10.108:8080/SHUTDOWN 时候,则变量在 while 中会置成 true ,紧接着停止 main ,结束应用程序进程。
在 eclipse 中或者在命令行中启动这个 main 函数,命令行则是输入 java HttpServer.java 。 eclipse 则是在 main 函数中右键 run as application 启动。 我们打开浏览器,输入 http://10.10.10.108:8080/index.html, 回车结果如下:
本地文件:
好了,夜深啦,就此搁笔了,抛砖引玉,欢迎提议和讨论,这个系列会继续下去,直到一个完整的可以响应一个java action请求的custom tomcat产品出来。
最后附上我的源代码:http://files.cnblogs.com/aspnetdream/Project.zip
参考:《How tomcat works》 作者:Budi Kurniawan & Paul Deck