Tomcat是怎么工作的(2) -- 动手实现山寨版的简单Web Server

本文先讲解一下Java web server都是怎么工作的。web server也叫HTTP server——顾名思义它是用HTTP协议和客户端交互的。客户端一般就是各种各样的浏览器了。相信所有朋友都清楚这个基本事实,否则你也不会看到这个系列文章了。

基于Java的web server必然用到两个极其重要的类:java.net.Socket和java.net.ServerSocket,然后以HTTP消息进行交互。

1. HTTP协议简介(The Hypertext Transfer Protocol)

HTTP是用于web server和浏览器之间发送、接收数据的基础核心协议——客户端发起请求然后服务端进行响应。它使用应答式TCP连接,默认情况下监听在80端口上。第一版协议是HTTP/0.9,然后又被HTTP/1.0重写了,随后HTTP/1.1又替换掉了HTTP/1.0——当前我们使用的正是HTTP/1.1,它的协议文件叫RFC2616,有兴趣的可以去w3网站上下载回来研究一下,对你理解和掌握HTTP以及整个互联网的核心有着无可替代的作用。接地气的说法就是:明白了RFC2616,你就明白了易筋经和九阳神功,自此之后横行天下无所顾忌。。。

HTTP里,永远都是客户端主动发起请求,然后服务端才有可能和它建立连接。web server永远不会主动连接或者回调客户端,但是两边都可以直接断开连接。

总结成一句话就是:服务端永远处于绝对优势地位,客户端你不连我我就绝对不会连你,只有你客户端发起请求了,我服务端才会和你连接,当然,心情不好时我也照样可以不对你的请求做出任何响应。像极了男人追女人的恋爱过程吧。。。

1.1 HTTP请求

它由以下部分组成:

第一部分:方式 — URI — 协议/版本号

第二部分:请求头

第三部分:实体数据

典型例如如下:

   1:  POST /baidu.com/小苹果歌词.txt HTTP/1.1
   2:   
   3:  Accept: text/plain; text/html 
   4:  Accept-Language: en-gb 
   5:  Connection: Keep-Alive 
   6:  Host: localhost 
   7:  User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 
   8:  Content-Length: 33 
   9:  Content-Type: application/x-www-form-urlencoded 
  10:  Accept-Encoding: gzip, deflate 
  11:   
  12:  lastName=Franks&firstName=Michael

对应的第一部分就是这段请求信息的第一行,下面再详细讲解下:

POST /baidu.com/小苹果歌词.txt HTTP/1.1

这一行的“POST”是请求方式,“/baidu.com/小苹果歌词.txt”是对应的URI,而“HTTP/1.1”就是对应的协议/版本号了。

HTTP协议定义了很多请求方式,每一个HTTP请求都可以使用其中的一种。HTTP 1.1 支持7种请求类型:GET,POST,HEAD,OPTIONS,PUT,DELETE以及TRACE。一般情况下,我们只用到GET和POST就足够了。

URI完整的指定了一个网络资源,一般情况下它都是相对于服务器的根目录进行资源定位,你看到的URI才经常以斜杠“/”开头,当然,通常我们只知道URL,URL实际上只是URI的一种而已(细节可研究RFC2396协议)。第一行的协议版本号,顾名思义就是当前使用的是哪版HTTP协议了。

请求头包含了一些关于客户端环境和请求体的有用信息。例如,它可以指示浏览器使用的语言、请求体的数据长度等等。每一个请求头和请求体之间都通过回车换行符(CRLF)分隔。

请求头和请求体之间的空白行(CRLF)是HTTP请求格式中不可或缺的一部分,它用于指明请求体数据开始的w位置。甚至在一些网络编程书中,这个空白行(CRLF)直接被当作了HTTP请求标准格式的第四个组成部分。

在上面那个例子中,请求体的实体数据只有简单的一行,不过实际应用中实体数据往往比较多:

lastName=Franks&firstName=Michael

1.2 HTTP响应

和HTTP请求相似,HTTP响应也由三部分组成:

第一部分:协议 -- 状态码 --描述

第二部分:响应头

第三部分:响应体

举个例子:

   1:  HTTP/1.1 200 OK 
   2:  Server: Microsoft-IIS/4.0 
   3:  Date: Mon, 5 Jan 2004 13:13:33 GMT 
   4:  Content-Type: text/html 
   5:  Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT 
   6:  Content-Length: 112 
   7:   
   8:  <html> 
   9:  <head> 
  10:      <title>HTTP Response Example</title> 
  11:  </head> 
  12:  <body> 
  13:       Welcome to Brainy Software 
  14:  </body> 
  15:  </html>
 

响应头第一行和请求头极为相似,它指示了当前使用的协议是HTTP/1.1版本,而且请求成功了(200=成功),一切顺利。
响应头包含的有用信息类似于请求头的。响应体是一段HTML内容,响应头和响应体之间以CRLF分隔。

2. Socket类
      socket是网络连接的一个端点,它赋予应用程序读写网络流的能力。两台电脑通过发送和接收基于连接的字节流来进行交流沟通。若要发消息给另一个程序,你需要知道这个程序socket的ip地址和端口号。在java里,socket指的是java.net.Socket类。

你可以使用Socket类的诸多构造器中任意一个来创建socket,下面这个构造器接收主机名和端口号作为参数:
      public Socket (java.lang.String host, int port)

在此,host可以是主机名或者ip地址,端口号就是对应的程序占用的端口。例如,要想连接80端口上的yahoo.com,你需要如下构造方式:
      new Socket("yahoo.com", 80);

一旦成功创建Socket实例,你就可以用它来发送接收字节流了。要发送字节流,你必须首先调用Socket类的getOutputStream方法获取java.io.OutputStream对象,要发送纯文本的话,我们通常构造一个OutputStream对象返回的java.io.PrintWriter对象。要接收字节流,你就应该调用Socket类的getInputStream方法来获取 java.io.InputStream。

下面就是代码展示了,各位看官请好:

   1:  Socket socket = new Socket("127.0.0.1", "8080"); 
   2:  OutputStream os = socket.getOutputStream(); 
   3:  boolean autoflush = true; 
   4:  PrintWriter out = new PrintWriter( socket.getOutputStream(), autoflush); 
   5:  BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputstream() )); 
   6:   
   7:  // 向web server发送HTTP请求
   8:  out.println("GET /index.jsp HTTP/1.1"); 
   9:  out.println("Host: localhost:8080");
  10:  out.println("Connection: Close"); 
  11:  out.println(); 
  12:   
  13:  // 读取响应
  14:  boolean loop = true; 
  15:  StringBuffer sb = new StringBuffer(8096); 
  16:  while (loop) { 
  17:      if ( in.ready() ) { 
  18:          int i=0;
  19:          while (i!=-1) { 
  20:              i = in.read(); 
  21:              sb.append((char) i); 
  22:          } 
  23:          loop = false; 
  24:      } 
  25:      Thread.currentThread().sleep(50); 
  26:  } 
  27:   
  28:  // 输出响应内容 
  29:  System.out.println(sb.toString()); 
  30:  socket.close();

3. ServerSocket类
      Socket类代表的是客户端Socket,例如IE浏览器、chrome、火狐、safari等发起的连接。如果你想实现一个服务器应用程序,像HTTP server或者FTP server的话,你就必须使用不同的方法了。这是因为服务端根本不知道客户端会发起请求建立连接,它必须永不停歇的等待客户端请求。为此,你必须使用java.net.ServerSocket类,它是服务端socket的实现。

ServerSocket不同于Socket,服务端的ServerSocket必须一直等着客户端请求的到来。一旦server socket接到连接请求,它必须创建一个Socket实例来处理和客户端的交互。

要创建server socket,你得用ServerSocket类提供的四个构造器之一。它需要你指明IP地址和server socket要监听的端口号。经典的127.0.0.1意味着server socket将监听本机。server socket监听的IP地址通常也叫绑定地址。另一个重要的属性是backlog,它意味着接入的连接请求超过此数值之后server socket就会拒绝后续请求。

public ServerSocket(int port, int backlog, InetAddress bindingAddress);

值得注意的是,这个构造器的绑定地址必须是java.net.InetAddress类的实例。构造InetAddress对象的简易方法就是调用它的静态方法getByname,并传一个主机名字符创参数,如下所示:
      InetAddress.getByName("127.0.0.1");

下面这行代码构造了一个ServerSocket,监听本机8080端口,同时backlog为1:
      new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

一旦ServerSocket实例构造完成,它就可以一直监听在绑定地址的对应端口上等待请求的到来,你需要做的就是调用ServerSocket类的accept方法来启动这个监听过程。这个方法只返回何时产生了连接请求并且返回值是一个Sokcet类的实例。然后通过这个Socket可以发送和接收字节流。

4. 动手实现自己的山寨版web server
      这个山寨web server由三个类组成:HttpServer、Request、Response。
      HttpServer的main方法创建一个HttpServer实例并调用它的await方法,顾名思义,这个await方法一直等着请求到来,然后处理请求、发送响应信息到客户端。它会一直等,直到程序终止或停机。
      这个山寨版的server目前只能发送静态资源,它会在控制台显示HTTP请求的字节流,但不能发送任何响应头,比如data、cookie之类的。

4.1 HTTPServer.java

   1:  import java.io.File;
   2:  import java.io.IOException;
   3:  import java.io.InputStream;
   4:  import java.io.OutputStream;
   5:  import java.net.InetAddress;
   6:  import java.net.ServerSocket;
   7:  import java.net.Socket;
   8:   
   9:  public class HttpServer {
  10:   
  11:      /**
  12:       * WEB_ROOT is the directory where our HTML and other files reside. For this
  13:       * package, WEB_ROOT is the "webroot" directory under the working directory.
  14:       * The working directory is the location in the file system from where the
  15:       * java command was invoked.
  16:       */
  17:      public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
  18:   
  19:      // 关机命令
  20:      private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
  21:   
  22:      // the shutdown command received
  23:      private boolean shutdown = false;
  24:   
  25:      public static void main(String[] args) {
  26:          HttpServer server = new HttpServer();
  27:          server.await();
  28:      }
  29:   
  30:      public void await() {
  31:          ServerSocket serverSocket = null;
  32:          int port = 8080;
  33:          try {
  34:              serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
  35:          } catch(IOException e) {
  36:              e.printStackTrace();
  37:              System.exit(1);
  38:          }
  39:          // 轮询是否有请求进来
  40:          while(!shutdown) {
  41:              Socket socket = null;
  42:              InputStream input = null;
  43:              OutputStream output = null;
  44:              try {
  45:                  socket = serverSocket.accept();
  46:                  input = socket.getInputStream();
  47:                  output = socket.getOutputStream();
  48:                  // create Request object and parse
  49:                  Request request = new Request(input);
  50:                  request.parse();
  51:                  // create Response object
  52:                  Response response = new Response(output);
  53:                  response.setRequest(request);
  54:                  response.sendStaticResource();
  55:                  // Close the socket
  56:                  socket.close();
  57:                  // check if the previous URI is a shutdown command
  58:                  shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
  59:              } catch(Exception e) {
  60:                  e.printStackTrace();
  61:                  continue;
  62:              }
  63:          }
  64:      }
  65:  }

4.2 Request.java

   1:  package ex01.pyrmont;
   2:   
   3:  import java.io.IOException;
   4:  import java.io.InputStream;
   5:   
   6:  public class Request {
   7:   
   8:      private InputStream input;
   9:   
  10:      private String uri;
  11:   
  12:      public Request(InputStream input) {
  13:          this.input = input;
  14:      }
  15:   
  16:      public void parse() {
  17:          // Read a set of characters from the socket
  18:          StringBuffer request = new StringBuffer(2048);
  19:          int i;
  20:          byte[] buffer = new byte[2048];
  21:          try {
  22:              i = input.read(buffer);
  23:          } catch(IOException e) {
  24:              e.printStackTrace();
  25:              i = -1;
  26:          }
  27:          for(int j = 0; j < i; j++) {
  28:              request.append((char)buffer[j]);
  29:          }
  30:          System.out.print(request.toString());
  31:          uri = parseUri(request.toString());
  32:      }
  33:   
  34:      private String parseUri(String requestString) {
  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:          return uri;
  47:      }
  48:  }
  49:   

4.3 Response.java

   1:  package ex01.pyrmont;
   2:   
   3:  import java.io.File;
   4:  import java.io.FileInputStream;
   5:  import java.io.IOException;
   6:  import java.io.OutputStream;
   7:   
   8:  /*
   9:  * HTTP Response = Status-Line (( general-header | response-header |
  10:  * entity-header ) CRLF) CRLF [ message-body ] Status-Line = HTTP-Version SP
  11:  * Status-Code SP Reason-Phrase CRLF
  12:  */
  13:  public class Response {
  14:   
  15:      private static final int BUFFER_SIZE = 1024;
  16:   
  17:      Request request;
  18:   
  19:      OutputStream output;
  20:   
  21:      public Response(OutputStream output) {
  22:          this.output = output;
  23:      }
  24:   
  25:      public void setRequest(Request request) {
  26:          this.request = request;
  27:      }
  28:   
  29:      public void sendStaticResource() throws IOException {
  30:          byte[] bytes = new byte[BUFFER_SIZE];
  31:          FileInputStream fis = null;
  32:          try {
  33:              File file = new File(HttpServer.WEB_ROOT, request.getUri());
  34:              if(file.exists()) {
  35:                  fis = new FileInputStream(file);
  36:                  int ch = fis.read(bytes, 0, BUFFER_SIZE);
  37:                  while(ch != -1) {
  38:                      output.write(bytes, 0, ch);
  39:                      ch = fis.read(bytes, 0, BUFFER_SIZE);
  40:                  }
  41:              } else {
  42:                  // file not found
  43:                  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>";
  44:                  output.write(errorMessage.getBytes());
  45:              }
  46:          } catch(Exception e) {
  47:              // thrown if cannot instantiate a File object
  48:              System.out.println(e.toString());
  49:          } finally {
  50:              if(fis != null)
  51:                  fis.close();
  52:          }
  53:      }
  54:  }
  55:   

5. 总结

本文讲解了web server的基本原理,同时代码贴出来了一个粗糙山寨的web server。它只有三个类构成,当然不是全功能的,不过呢,毕竟刚开始,我们会不断的逐步完善这个web server,到本系列结束时,基本上就有一个完整的web server了。

文档信息

Tomcat是怎么工作的(2) -- 动手实现山寨版的简单Web Server,布布扣,bubuko.com

时间: 2024-10-14 01:44:05

Tomcat是怎么工作的(2) -- 动手实现山寨版的简单Web Server的相关文章

Tomcat是怎么工作的(1) -- 开篇

这是一个系列文章的第一篇. 标题还是费了点脑子才确定的,起什么名字比较好呢.Tomcat工作原理?深入浅出Tomcat运行机制?从零开始研究Tomcat?Tomcat是怎么运行起来的?Tomcat是如何为我们提供服务的?本想起个酷炫狂拽吊炸天的名字,显得咱也高大上一些,令万人敬仰,思来想去最后还是用了最土最朴素的标题:Tomcat是怎么工作的. 本系列源于<How Tomcat Works>一书,这本书我搜了一下,是没有正式的中文版的.充其量有些出于兴趣爱好才翻译到网上的章节,至少我没找到完整

tomcat安装与基本配置,动手来部署一个jsp站点。

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选.对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应对HTML页面的访问请求.实际上Tomcat 部分是Apache 服务器的扩展,但它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的. 另外,Tomcat和IIS等Web服务器一样,具有处理H

tomcat组成及工作原理

1 - Tomcat Server的组成部分 1.1 - Server A Server element represents the entire Catalina servlet container. (Singleton) 1.2 - Service A Service element represents the combination of one or more Connector components that share a single EngineService是这样一个集合

Tomcat的基础知识和一些应用,session cluster和session server

Tomcat简单来说就是JAVA 2 EE 加上Servlet和JSP类库的实现,tomcat=JWS(Sun)+ Jserv(ASF) Tomcat的核心组件: catalina:servlet container Coyote:http connection Jasper:JSP Engine 执行引擎 TomcatInstance: 运行中的tomcat进程(java进程) Server:即一个tomcat实例: Service:用于将connector关联至engine组件:一个serv

tomcat解析之简单web服务器(图)

链接地址:http://gogole.iteye.com/blog/587163 之前有javaeyer推荐了一本书<how tomcat works>,今天晚上看了看,确实不错,第一眼就着迷了. 于是乎就学着书上的例子敲了敲,学会了一个简单web服务器的大概实现,当然,这个简直就无法称之为web服务器,但是也算是走进web服务器的第一步吧. 这篇文章仅限于学习记录,文笔凌乱之处,还望各位见谅. OK,下面进入正题: 开始之前,首先我们要清楚以下几个内容. 首先,一个最简单服务器包括三个部分:

自己动手创建一个Web Server(非Socket实现)

目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 介绍 本篇文章主要介绍使用HTTPListener类型自己动手创建一个Web Server,创建的Web Server能够接收来自浏览器端的HTTP请求,并且能够传递给对应的Web站点进行处理,最后将处理结果(Html或者其他格式)返回给浏览器. 博主前面曾经介绍过使用Socket模拟Web Se

Linux定期清Tomcat web server日志

Linux系统中,Tomcat Web Server服务器如果跑的时间久了,系统就会产生大量的日志. 本文指出一个定期清理Tomcat日志的方法: 1.在/root下编写一个清理tomcat日志的shell脚本,内容如下: more autoclearup_tomcat_logs.sh: #! /bin/bash #clear up tomcat logs for centos6.6 #created by lutaoxu. #copyright lutaoxu. #2015-02-02 v1.

解决 Tomcat reload WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [] registered the JDBC driver [com.mysql.jdbc.Driver] but fail

转自:http://www.cnblogs.com/interdrp/p/5632529.html 我的错误如下: 06-Sep-2016 18:57:10.595 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [] registered the JDBC driver [com.mysql.jdbc.

tomcat下部署了多个项目启动报错java web error:Choose unique values for the &#39;webAppRootKey&#39; context-param in your web.xml files

应该是tomcat下部署了多个项目且都使用log4j. <!--如果不定义webAppRootKey参数,那么webAppRootKey就是缺省的"webapp.root".但最好设置,以免项目之间的名称冲突. 定义以后,在Web Container启动时将把ROOT的绝对路径写到系统变量里. 然后log4j的配置文件里就可以用${webName.root }来表示Web目录的绝对路径,把log文件存放于webapp中. 此参数用于后面的“Log4jConfigListener”