深入tomcat(一)

在熟悉java se 之后,我们可以试着去看一些开源的东西,比如tomcat。

 

本章目标:

A. 了解HTTP协议

B. 熟悉Socket类和ServerSocket类

C. 编写一个简单的web server

 

1.1HTTP协议

    http协议就是客户端和服务器之间交互必须遵循的一个规范。它的标准定义如下:

一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。

问题:既然TCP/UDP是广泛使用的网络通信协议,那为啥有多出个http协议来呢?

     UDP协议具有不可靠性和不安全性,显然这很难满足web应用的需要。而TCP协议是基于连接和三次握手的,虽然具有可靠性,但有一定的缺陷。试想一下,普通的C/S架构软件,顶多上千个Client同时连接,而B/S架构的网站,十万人同时在线也是很平常的事儿。如果十万个客户端和服务器一直保持连接状态,那服务器如何满足承载呢?这就衍生出了http协议。基于TCP的可靠性连接。通俗点说,就是在请求之后,服务器端立即关闭连接、释放资源。这样既保证了资源可用,也吸取了TCP的可靠性的优点。正因为这点,所以大家通常说http协议是“无状态”的,也就是“服务器不知道你客户端干了啥”,其实很大程度上是基于性能考虑的。以至于后来有了session之类的玩意

1.1.1HTTP请求

一个http请求,包含以下三部分:

A请求方法

B 请求头

C 实体

在研究http协议时,推荐大家使用一款叫作httpwatch的工具。(遗憾的是,该工具是收费的。该咋办就咋办,你懂的)。安装完成后,可以在IE浏览器的tools中直接打开(目前也支持firefox)。

下面是我用httpwatch监视http请求的图例:



点击“Stream”:

GET/rest/general/mnt/server/listgroup HTTP/1.1

Accept: image/gif, image/jpeg,image/pjpeg, image/pjpeg, application/x-shockwave-flash,application/x-ms-application,application/x-ms-xbap,application/vnd.ms-xpsdocument,application/xaml+xml,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword, */*

Accept-Language: zh-cn

User-Agent: Mozilla/4.0(compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NETCLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.1)

Accept-Encoding: gzip, deflate

Host: localhost:8080

Connection: Keep-Alive

 

注意:在请求头和请求实体中间有一个空行,由于本例是GET,故没有请求实体。

 

1.1.2HTTP响应

   和HTTP请求对应,响应分为:

A.    响应——状态码——描述

B.    响应头

C.    响应实体

下面是上图的响应内容:

 

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Content-Type: application/xml

Content-Length: 250

Date: Mon, 04 Jun 2012 07:25:47GMT

 

<?xml version="1.0"encoding="UTF-8"standalone="yes"?><ServerGroupList><ServerGroupname="UML_Z" groupId="19" description="UMLCluster"/><ServerGroup name="groupName122" groupId="20"/><ServerGroupname="serverGroup1" groupId="21"/></ServerGroupList>

 

1.2Socket与ServerSocket

1.2.1Socket

    套接字是网络连接的端点。它使得应用程序可以从网络中读取数据,也可以向网络中写入数据。不同计算机上的两个应用程序可以通过套接字来完成相互交互通信。在java中套接字使用java.net.Socket表示。

 

1.2.2ServerSocket

Socket类表示一个客户端套接字,即,当想要连接到远程服务器应用程序是创建的套接字。但是如果你想要实现一个服务器应用程序(在这里我们想要实

 

现一个简单的web服务器),那么需要使用java.net.ServerSocket类,这是服务器套接字的实现。

1.2.3简单示例

(编写一个小程序:向本机8888端口发送一个字符串,然后接受并显示)

示例代码如下:

 

SendMessage.java

import java.io.IOException;

import java.io.OutputStream;

import java.net.InetAddress;

import java.net.Socket;

import java.net.UnknownHostException;

 

public class SendMessage {

    public static void main(String[]args) {

       try {

           Socketsocket=new socket(InetAddress.getByName("127.0.0.1"),

                        8888);

           OutputStreamoutput=socket.getOutputStream();

           output.write("Helloworld!".getBytes());

           output.close();

           System.out.println("sendover");

       } catch(UnknownHostException e) {

           e.printStackTrace();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

 

}

 

ReceiveMessage.java

 

import java.io.IOException;

import java.io.InputStream;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.UnknownHostException;

 

public class ReceiveMessage {

    public static void main(String[]args) {

       Socketsocket=null;

       try {

           ServerSocketserverSocket=new ServerSocket(8888,

                                1, InetAddress.getByName("127.0.0.1"));

           socket=serverSocket.accept();

           //获取输入流

           InputStreaminput=socket.getInputStream();

           byte[] b=newbyte[2048];

           StringBuffermessage=new StringBuffer(2048);

           try {

              int i=input.read(b);

              for(int j=0;j<i;j++){

                  message.append((char)b[j]);

              }

           } catch (IOException e) {

              e.printStackTrace();

           }

           System.out.println("messageis:"+message.toString());

       } catch(UnknownHostException e) {

           e.printStackTrace();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

 

1.3一个简单的web服务器

本章的应用程序放在ex01.pyromnt包下,包括三个类:

a.HttpServer

b.HttpRequest

c.HttpResponse

1.3.1HttpServer类

 

 

本章应用程序位于ex01.pyrmont包下,包括三个类

 

import java.io.File;

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.InetAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.net.UnknownHostException;

 

public class HttpServer {

    public static final String WEB_ROOT = System.getProperty("user.dir")

                                          +File.separator+"webroot";

    private static booleanshutdown=false;

    public static void main(String[]args){

       newHttpServer().await();

    }

    privatevoid await(){

       int port=7890;

       ServerSocketserverSocket=null;

       try{

           serverSocket=newServerSocket(port,1,

                  InetAddress.getByName("127.0.0.1"));

       }catch(IOException e){

           e.printStackTrace();

           System.exit(1);

       }

       try {

           Socketsocket=null;

           InputStreaminput=null;

           OutputStreamoutput=null;

           while(!shutdown){

              socket=serverSocket.accept();

              input=socket.getInputStream();

              output=socket.getOutputStream();

              HttpRequestrequest=new HttpRequest(input);

              request.parse();

              HttpResponseresponse=new HttpResponse(output);

              response.setRequest(request);

              response.sendStaticResource();

              socket.close();

              shutdown=request.getUri().equals("shutdown");

           }

       } catch(UnknownHostException e) {

           e.printStackTrace();

       } catch (IOException e) {

           e.printStackTrace();

       }

    }

}

    在这里,我们简单的看看HttpServer中的await方法:

    它首先实例化了一个ServerSocket对象实例(如下),

 

    serverSocket=new ServerSocket(port, 1,InetAddress.getByName("127.0.0.1"));该对象侦听本地主机的8765端口,其backlog值为1;

 

    随后进入了一个while循环,当serverSocket从8765端口收到HTTP请求后,serSocket对象的accept()方法返回一个Socket对象,等待结束:

    socke=serverSocket.accept();

   

    接受到HTTP请求之后,await方法从socket对象中获取输入流和输出流:

    input=socket.getInputStream();

    output=socket.getOutputStream();

   

    之后,创建了HttpRequest对象,并调用其parse()方法解析HTTP请求的原始数据:

 

    HttpRequest request=new HttpRequest(input);    

    request.parse();

 

    然后,await()方法会创建一个HttpResponse对象来响应客户端:

    HttpResponse response=newHttpResponse(output);

    response.setRequest(request);

    response.sendStaticResource(); 

   

    最后,await()关闭套接字,调用Request类的getUri()方法来测试HTTP请求的URL是否是关闭命令,若是则程序退出while循环:

 

    socket.close();

   shutdown=request.getUri().equals(SHUTDOWN_COMMAND);

 

   

1.3.2HttpRequest类

import java.io.IOException;

import java.io.InputStream;

public class HttpRequest {

    private InputStream input;

    private String uri;

    public String getUri() {

       returnuri;

    }

 

    publicvoid setUri(String uri){

       this.uri = uri;

    }

    public InputStreamgetInput() {

       returninput;

    }

    publicvoidsetInput(InputStream input) {

       this.input = input;

    }

    public HttpRequest(){

 

    }

    publicHttpRequest(InputStream input){

       this.input=input;

    }

    //解析HTTP请求的原始数据

    publicvoid parse(){

        byte[] b=newbyte[2048];

       StringBufferrequest=new StringBuffer(2048);

       try {

           int i=input.read(b);

           for(int j=0;j<i;j++){

              request.append((char)b[j]);

           }

       } catch (IOException e) {

           e.printStackTrace();

       }

       this.uri=parseUri(request.toString());

    }

    //解析HTTP请求的URL

    private StringparseUri(String requestString){

       int index1,index2;

       index1=requestString.indexOf(' ');

       if(index1!=-1){

           index2=requestString.indexOf(' ', index1+1);

           if(index2>index1){

              returnrequestString.substring(index1+1, index2);

           }

       }

       returnnull

    }

}

说明:parse()方法是public的,它会从InputStream中读取整个字节流,并将字节数组存储到缓冲区中,然后它用该数组填充StringBuffer,最后调用privateparseUri(StringrequestString)来解析URL

假设我们请求的URL是:http://localhost:7890/index.html

那么:

 

1.3.3HttpResponse类

一个HttpResponse对象表示一个Http响应,代码如下:

import java.io.File;

import java.io.FileInputStream;

import java.io.OutputStream;

 

public class HttpResponse {

    private static final intBUFFER_SIZE=1024;

    HttpRequest request;

    OutputStream output;

    public HttpResponse(){

 

    }

    publicHttpResponse(OutputStream output){

       this.output=output;

    }

    public HttpRequestgetRequest() {

       returnrequest;

    }

    publicvoidsetRequest(HttpRequest request) {

       this.request = request;

    }

    public OutputStreamgetOutput() {

       returnoutput;

    }

    publicvoid setOutput(OutputStreamoutput) {

       this.output = output;

    }

    publicvoidsendStaticResource(){

       byte[] bytes=newbyte[BUFFER_SIZE];

       FileInputStreamfis=null;

       File file=new File(HttpServer.WEB_ROOT,request.getUri());

       try {

           if(file.exists()){

              fis=newFileInputStream(file);

              intch=fis.read(bytes,0, BUFFER_SIZE);

              while(ch!=-1){

                  output.write(bytes, 0,ch);

                  ch=fis.read(bytes,0,BUFFER_SIZE);

              }

           }

           else{

              output.write("file notfound".getBytes());

           }

 

       } catch (Exception e) {

           e.printStackTrace();

       }

       finally{

           if(fis!=null){

              try{

                  fis.close();

              }catch(Exception e){

                  e.printStackTrace();

              }

           }

       }

    }

}

 

说明:HTTP是基于请求/响应范式的,故在HttpResponse中首先需要获取HttpRequest中的uri,即,HttpResponse必须知道你(客户端)想要什么,这里还以http://localhost:7890/index.html为例,客户端请求的页面是index.html,然后将父路径和子路径传到java.IO.File类的构造函数中去构造File对象(此例中Fileindex.html),之后判断该文件是否存在,若不存在,则输出“file not found”到浏览器;否则使用OutputStream对象的write()方法将index.html输出到浏览器。

 

1.3.4运行本章程序

在编译java类之后,测试的url可以参考前文。效果如下图:

 

本章中我们可以请求静态资源,而不仅仅是html文件,比如图片:

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值