本文先讲解一下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的更多相关文章

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

    这是一个系列文章的第一篇. 标题还是费了点脑子才确定的,起什么名字比较好呢.Tomcat工作原理?深入浅出Tomcat运行机制?从零开始研究Tomcat?Tomcat是怎么运行起来的?Tomcat是如 ...

  2. Tomcat内部结构、工作原理、工作模式和运行模式

    TOMCAT的内部结构 Tomcat是一个基于组件的服务器,它的构成组件都是可配置的,其中最外层的是Catalina servlet容器,其他组件按照一定的格式要求配置在这个顶层容器中.Tomcat的 ...

  3. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

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

    链接地址:http://gogole.iteye.com/blog/587163 之前有javaeyer推荐了一本书<how tomcat works>,今天晚上看了看,确实不错,第一眼就 ...

  5. paip.java 开发中web server的选择jboss resin tomcat比较..

    paip.java 开发中web server的选择jboss resin tomcat比较.. 作者Attilax  艾龙, EMAIL:1466519819@qq.com 来源:attilax的专 ...

  6. 解决 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 [localhos ...

  7. 自己动手实现网络服务器(Web Server)——基于C#

    前言 最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用python.PHP写的那部分代码都迁移到了C#(别问我为什么这么喜欢C#),之前使用 ...

  8. Tomcat建立多个应用(Web Server),多个主机,多个站点的方法

    https://blog.csdn.net/chungle2011/article/details/52317433 http://piperzero.iteye.com/blog/1475773 转 ...

  9. Server MyEclipse Tomcat v7.0 was unable to start within 45 seconds. If the server requires more time

    启动Tomcat服务器时经常遇到这个错误, Server MyEclipse Tomcat v7.0 was unable to start within 45 seconds. If the ser ...

随机推荐

  1. SpringBoot引入监听器

    方法一: 实现ServletContextListener ,并添加@WebListener注解 因为ServletContextListener 是由servlet容器管理,游离于spring容器之 ...

  2. 根据参数优化nginx的服务性能

    一.优化nginx服务的worker进程数 在高并发.高访问量的Web服务场景,需要事先启动好更多的nginx进程,以保证快速响应并处理大量并发用户的请求. 1).优化nginx进程对应的配置 优化n ...

  3. Vue实例和生命周期

    创建一个Vue实例 每个Vue应用都是通过Vue函数创建一个新的Vue实例开始: var vm = new Vue({ //选项 }) 数据与方法 当一个Vue实例被创建时,它向Vue的响应式系统中加 ...

  4. Linux 安装Nginx+PHP+MySQL教程

    一.安装nginx 通过yum安装openssl: yum -y install openssl openssl-devel 通过yum安装pcre: yum -y install pcre-deve ...

  5. 树莓派编译ncnn

    1.从github上下载ncnn git clone --recursive https://github.com/Tencent/ncnn 2.在ncnn根目录下创建build目录,安装cmake编 ...

  6. poj-1700 crossing river(贪心题)

    题目描述: A group of N people wishes to go across a river with only one boat, which can at most carry tw ...

  7. 小x的质数(线性O(n)筛素数)

    小x的质数 题目描述 小 X 是一位热爱数学的男孩子,在茫茫的数字中,他对质数更有一种独特的情感.小 X 认为,质数是一切自然数起源的地方. 在小 X 的认知里,质数是除了本身和 11 以外,没有其他 ...

  8. oracle结构-内存结构与动态内存管理

    内存结构与动态内存管理 内存是影响数据库性能的重要因素. oracle8i使用静态内存管理,即,SGA内是预先在参数中配置好的,数据库启动时就按这些配置来进行内在分配,oracle10g引入了动态内存 ...

  9. vs2012打开低版本项目时 出现vs2012警告未能加载包“visual c++ package 解决办法

    vs2012 打开 vs2010 项目时 提示的 错误信息. 解决办法 是下载一个 vs2012的 一个补丁包 http://www.microsoft.com/en-us/download/deta ...

  10. MyBatis多个接口参数报错:Available parameters are [0, 1, param1, param2], 及解决方法

    1. sql语句如下: SELECT * FROM tb_crm_user WHERE id = #{userId, jdbcType=INTEGER} AND user_name = #{userN ...