1.1.1. 引言
现在网络是人们日常生活的一部分,是连接外部世界获得外界信息的主要途径之一。我们其中的任何一个人如果打开电脑,启动浏览器,输入网址,那么浏览器里边就展现了互联网上的信息了。实际上,我们的浏览器信息来自网络服务供应商,但是网络服务商并不是自己将自己的信息发送到我们的计算机里,而是依靠中间件——WEB服务器来完成这个任务。WEB服务器为网络服务供应商的软件提供一个平台,网络服务商可以将他的软件部署在这个平台上,让WEB服务器来负责完成客户和自己软件之间信息的交互和交互过程中所有状态的维护。
我们想象下面场景,国家在某地建立了科技园区,科技园区有如下设计:
第一,科技园区为某一行业的企业提供入住,比如生活日用品。
第二,为这一行业提供所有需要服务,比如,对企业的开业,经营,破产等的管理,对物业,通信,外联服务的提供等等。
第三,任何生产日用品的企业可以随时入住,只要填写必要的信息,办理注册就可以了,科技园会安排开业事宜,期间企业在科技园安排差不多的时候可以介入,安排自己的事情。
第四、开业后,企业负责生产和经营,期间需要的条件由科技园来提供。
第五,当企业需要破产的时候,科技园负责企业的破产事宜。
其实,这就是一个服务器和部署在上面的应用的形象描述。科技园就是服务器,负责对企业的生命周期管理,运行期间的服务,企业的对外通信服务;而企业就是部署在服务器上的应用,负责完成具体的事情,然后科技园负责和客户交互信息。
1.1.2. Web服务器特点
WEB服务器也称为WWW(WORLD WIDE WEB)服务器,具有如下显著特点:
(1)和客户端的浏览器一起工作
(2)浏览器统一资源定位器(URL)
(3)客户端一般使用HTTP协议向服务器发送请求
(4)服务器发送给客户浏览器的文本格式是HTML
(5)服务器可以支持多种协议,但是必须支持HTTP协议
(6)服务器一般都支持J2EE标准
我们简单介绍了WEB服务器的概念,那么下面我们将编写一个最简单的服务器。你将会了解WEB服务器的原理
1.2. 服务器的相关知识准备
我们服务器实现简单客户端和服务器的通信。和现行大多数WEB服务器一样,这个通信使用套接字作为底层基础,在套接字上使用HTTP协议进行信息通信。其中套接字通信涉及JDK(java development kit)的两个重要的类Socket 和 ServerSocket。. 因此,在编写我们的简单服务器的之前,我们先讨论HTTP协议和这2个类,接下来温习javax.servlet.Servlet接口。然后开始我们的简单服务器的编写。
1.2.1. HTTP协议
协议的含义
在讨论HTTP协议之前,我们先阐述一下协议的概念。协议,就是双方双方都遵守的契约。假设张三和李四是同班同学,都是中学生,按照中学生守则,中学生是不许谈恋爱的,但是两个人相互有好感,私下谈了恋爱。为了避免被家长或者学校发觉,他们决定,在风声紧的时候两个人装作不认,但是要定期传纸条,同时要纸条内容不能外泄,即使外泄也要别人也不知道纸条上文字的意思,但是他们自己要明白。于是,他们订立如下协议(契约):
1、纸条上是字母”J“代表要对方小心,最近风声有点紧
2、纸条上是“O”代表我爱你
3、纸条上是”Y“,代表有事要和对方说
4、纸条上”D“,代表我等你
5、纸条上是”F“,代表放学后
现在,张三和李四可以准备有这几个字母的纸条,当需要的时候,将纸条传给对方,比如一天,张三向李四传递了一个纸条,内容是”Y F D“,于是放学后,李四留下等张三商谈事宜。
从上面例子我们可以看出协议有如下特点
1、协议是通信双方都认同并遵守的。比如张三和李四都认同这个协议,通信时都遵守了此协议。
2、协议的规定是双发都明白的,比如上面的协议,张三和李四都知道字母代表的意思。
3、只要了解了协议的内容,就可以用协议通信。比如王五知道了张三和李四的协议,那么王五得到张三传给李四的纸条后,王五就会明白张三要对李四说什么。
4、要想和使用协议的对象通信,那么他必须遵守这个协议。比如王五看纸条时,他无形中遵守了张三和李四的协议。
对于WEB服务器而言,协议就是客户端向服务器端传送数据时要遵守的格式,服务器端会按照这个格式解析客户端请求数据。
HTTP协议
现在的所有WEB服务器都支持HTTP协议,它规定了客户端向服务器发送请求时要遵守的规则。
HTTP协议是英文The Hypertext Transfer Protocol ,中文意思是“超文本传输协议”。
它是一种请求——响应式的协议,客户端发起一个请求,服务器解析,完成请求,给客户端响应。HTTP协议的第一个版本是 HTTP/0.9, 然后是 HTTP/1.0. 当前版本是由 RFC 2616(.pdf) 定义的 HTTP/1.1.。由于现在绝大多数网络应用都遵守HTTP1.1,为了理解WEB服务器的原理,这里简单介绍一下HTTP1.1协议。如果你想详细了解HTTP1.1的细节,请阅读官方文档RPC2616。(至于HTTP0.9、HTTP1.0和HTTP1.1的区别将在以后章节中阐述)。
使用 HTTP 的时候, 客户端总是先和服务器建立连接,建立连接后,发送给服务器一个 HTTP 请求.。服务器解析客户端的请求,代理客户端的任务,将结果返回给客户端。服务器不会主动和客户端建立连接。客户端和服务器都可以提前中断一个连接。例如, 当你使用 web 浏览器的浏览网页的时候,在载入网页的时候,你可以点击浏览器上的停止按钮结束网页打开过程,关闭与 web 服务器的连接.。
--------------------------------------------------------------
HTTP Requests 上面提过,HTTP1.1协议是请求/应答模式的协议,那么HTTP协议分为两个部分,一是请求协议,一是应答协议。下面看一下请求协议。 HTTP请求的格式如下: 1、方法-统一资源标识符-协议/协议版本 2、请求头 3、请求实体 下面是一个HTTP请求实例 POST /servlet/testt.jsp HTTP/1.1 Accept: text/plain; text/html Accept-Language: en-us Connection: Keep-Alive Host: localhost Referer: http://localhost/begin.jsp User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Content-Length: 33 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip name=lisi&age=40 为了使您对HTTP协议有个大概印象,下面将就上面涉及的HTTP请求的相关项做以下介绍: POST /servlet/test.jsp HTTP/1.1 POST是请求的方式, HTTP 1.1 支持7种请求,分别是::GET, POST, HEAD, OPTIONS, PUT, DELETE, 和 TRACE. GET 和 POST 。其中最常用的是GET和POST,他们区别如下: 1、Post传输数据时,不需要在URL中显示出来,而Get方法要在URL中显示。 2、Post传输的数据量大,可以达到2M,而GET方法只能传递大约1024字节。 3、Post目的是将数据传送到服务器段。而Get目的是给服务器消息,要从服务器下载数据。、所以我们可以轻而易举的明白:GET既然只是通知服务器需要什么数据,那么它需要传递的信息量就很少,用来传送数据到服务器,那么它需要的信息量就很大,比如传递文件。这也是上面第二条之所以那样规定的理由。 POST /servlet/testt.jsp HTTP/1.1 其中POST规定了传输数据使用的方法,/servlet/test.jsp则是请求要访问的统一资源标识符(URI),要访问根目录下servlet目录下的test.jsp页面。URI 通常以相对于服务器的根目录来进行解析.,因此,它总是以 / 开始,比如上面的/servlet/test.jsp。(URL 实际是 URI 的一种)。HTTP表明使用的是HTTP协议,1.1表示使用的版本是1.1。 Accept: text/plain; text/html 客户浏览器可以接受的内容类型,这里是简单文本和html文档。 Accept-Language: en-us 客户浏览器希望从服务器接收的内容使用的语言。当服务器提供多于一种语言的时,这个选项起作用。 Connection: Keep-Alive Content-Length: 33 Keep-Alive表示需要持久连接。如果Servlet看到这里的值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(HTTP 1.1默认进行持久连接),他就建立持久连接,当页面包含多个元素时(例如Applet,图片)时,使用这个连接将全部资源发给客户端后,才关闭连接,这样可以大量节约宝贵的网络和服务器资源,减少页面下载时间。这个选项和Content-Length一起使用。 因为使用持久连接,服务器需要知道何时读完请求字符串。 Host: localhost 指定请求资源的Intenet主机 Referer: http://localhost/begin.jsp 表示访问网络资源的出发点 User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) 浏览器类型,如果Servlet返回的内容与浏览器类型有关则该值非常有用 Content-Type: application/x-www-form-urlencoded 表示后面的文档属于什么类型。Servlet默认为text/plain,但通常需要显式地指定。这里是指定为表单。 Accept-Encoding: gzip 文档的编码方法。只有在解码之后才可以得到Content-Type头指定的内容类型。利用gzip压缩文档能够显著地减少HTML文档的下载时间。Java的GZIPOutputStream可以很方便地进行gzip压缩。 HTTP请求使用两个回车换行将请求头和请求体分开,也就是说在请求头和请求体之间有一个空白行。 请求主体很简单,内容如下 name=lisi&age=40 当然我们使用的是post方法,请求体可以有很长,最大是2M。 HTTP Responses HTTP 响应也是由3部分组成: 1、协议-状态代码-描述 2、响应头 3、响应实体 下面是一个 HTTP 响应的例子: HTTP/1.1 200 OK Server: Microsoft-IIS/4.0 Date: Thu, 13 Jul 1998 13:13:33 GMT Content-Type: text/html Content-Length: 84 <html> <head> <title>HTTP Response</title></head><body> Welcome to HTTP word! </body> </html> HTTP/1.1 200 OK 标示使用的是HTTP协议,版本是1.1,状态200,标示成功,ok,标示一切正确。 Server: Microsoft-IIS/4.0 标示服务器类型 Content-Type: text/html 内容类型是HTML Content-Length: 112 内容长度是112字符 下面是两个回车换行,也就是隔一行。后面是响应体。 1.1.1. Socket类 此类是程序进行网络数据交换使用的类,通常用来发送请求和读取响应。位于java.net包中。下面我们看一下Socket类的相关重要方法方法签名。 1、构造方法签名 public Socket(String host, int port) host是远程服务器名称或者IP 地址,port 是服务器的端口号。例如 new Socket("127.0.0.1", 80) 2、 获得向发送数据的输出流 public OutputStream getOutputStream() throws IOException 当你创建了套接字实例后,你就可以使用它发送和接收消息。发送消息的时候, 使用Socket 类的 getOutputStream 方法获得 java.io.OutputStream 对象。为了方便,可以对OutputStream 进行包装,包装成一个 java.io.PrintWriter 对象,用来发送文本。 3、获得从服务器读取消息的输入流 public InputStream getInputStream()throws IOException 输入流用来从目标机器读取消息。
-------------------------------------------------- ServerSocket类 此类通常服务器端,通常使用它来接收和响应客户请求。在互联网中,客户端可能有好多,每个客户随时可能向服务器发送请求,服务器根本不知道谁什么时间要和自己通信。所以,服务器端必须一直处于运行状态,等待客户连接请求。ServerSocket正是为此设计。 此类位于java.net包中,下面看一下它的其中几个重要方法签名。 1、其中的一个构造方法 public ServerSocket(int port, int backLog, InetAddress bindingAddress) 此类的构造器共有四个,我们来看一下其中的一个,也就是我们的简单服务器将要用到的构造器。其余三个可以参看JDK文档。 在此构造器中,port是服务器监听的端口,所有来自port端口指定的请求都会被监听到,也就是说,每个从port端口传来的数据都会由套接字来处理。backLog是服务器所能容忍的最大请求队列长度,当请求数量大约此数值时,服务器将拒绝响应新的请求。bindingAddress是服务器绑定的地址,它必须是java.net.InetAddress 的一个实例,构造此实例的方法之一是调用方法InetAddress.getByName("127.0.0.1")。 2、accept 方法 public Socket accept() throws IOException 创建了ServerSocket 实例,后,你就可以调用它的 accept 方法使服务器进入监听状态。它只有在有连接请求的时候才会有返回。它返回一个 Socket 类的实例,用于和客户端通信。 1.1.2. Servlet接口 J2EE作为一个标准,它规定了J2EE编程需要遵循的规范。servlet接口就是此标准中的一个规定。也就是说所有的servlet都必须实现标准中规定servlet接口。 Servlet接口有5个方法,其签名如下: public void init(ServletConfig config) throws ServletException public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException public void destroy() public ServletConfig getServletConfig() public java.lang.String getServletInfo() 其中,init, service, 和 destroy 方法是 servlet 的生命周期方法。init 方法是初始化方法,只在Servlet初始化时调用一次。初始化成功后,servlet调用service 方法.,对客户端进行服务。它接受 服务器传递的一个 javax.servlet.ServletRequest 对象和一个 javax.servlet.ServletResponse 对象。ServletRequest 对象包含客户端 HTTP 请求信息,ServletResponse 封装了servlet 的响应。当服务器需要关闭或者需要释放内存时,服务器删会除一个servlet实例,在删除之前会执行destroy方法,允许servlet程序员做一些清理工作。getServletConfig()方法用来获得servlet配置对象,getServletInfo()用来获得servlet的信息。 1.1. 一个简单服务器 通过上面的准备,我们已经有了编写简单服务器的知识准备。现在,我们开始我们简单服务器。此简单服务器实现对客户端请求的静态资源和动态servlet的处理和响应。 1.1.1. HttpWebServer类 此类是我们简单服务器的核心类,在随书光盘中的chapter/execixe1/src/server/core目录下可以看到此类的源码。下面是此类的关键代码: public class HttpWebServer { private boolean shut = false; 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); } while (!shut) { Socket socket = null; InputStream is = null; OutputStream os = null; try { socket = serverSocket.accept(); is = socket.getInputStream(); os = socket.getOutputStream(); HttpRequest request = new HttpRequest(); request.setIs(is); request.parseRequest(); HttpResponse response = new HttpResponse(); response.setRequest(request); response.setOs(os);
RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); //响应动态servlet if (request.getUri().startsWith("/servlet/")) { ServletProcessor processor = new ServletProcessor(); processor.process(requestFacade, responseFacade); }else{ StaticResourceProcessor processor = new StaticResourceProcessor(); processor.process(requestFacade, responseFacade); } socket.close(); shut = request.getUri().equals(GlobalConstants.SHUTDOWN_COMMAND); } catch (Exception e) { e.printStackTrace(); continue; } } } }
2011-03-28 14:12
2011-03-28 14:13
|