本章主要介绍Web服务器是如何运行的。基于Java的Web服务器有两个重要的类:Socket和ServerSocket,本章介绍此二类和HTTP和一个简单的Web服务器。
1、是什么:HTTP允许Web服务器和浏览器通过Internet发送并接收数据,是一种基于“请求——响应”的协议。
2、内容
1.HTTP请求:包括请求方法——统一资源标识符(URI)——协议/版本、请求头、请求体。(如图)
URI:指定Internet资源的完整路径。通常被解释为相对于服务器根目录的相对路径。故以“/”开头。URL实际为URI的一种。
请求头:请求头间用CRLF(即回车换行符,\r\n)来分割。请求头和请求行用一个空行分开。
2.HTTP响应:协议——状态码——描述、响应头、响应实体段。
Socket表示客户端套接字。
Sokect,即为套接字。套接字使应用程序可以从网络中读取或写入数据。不同的计算机之间可以通过连接发送或接收字节流,以此达到互相通信的目的。
创建套接字:public Socket(host,port)
1、ServerSocket类
ServerSocket表示服务端套接字,其要等待客户端套接字的连接请求。
创建ServerSokcet:用四个构造函数之一,需要监听的IP地址和监听端口号。(必须),可有backlog(表示传入连接请求的最大队列长度)
IP地址(绑定地址):必须为InetAddress的实例,可用InetAddress.getByName(ip)获得。
该程序仅发送位于指定目录的静态资源的请求。
包含三个类,分别为HttpServer,Request,Response。
HttpServer:表示一个Web服务器,负责接收请求并响应请求。其通过Socket获得请求,并将请求封装成Request对象,把解析过后的Request传给Response对象,让Response找到静态资源并返回给客户端。
代码可从书籍给出的网址获取:https://www.brainysoftware.com/download;jsessionid=0C65689E1931A351F6B14844DE9EBF29
Servlet编程需用到 javax.servlet.Servlet和javax.servlet.Http两个包。
1、5个方法
init()、service()、destroy()是与Servlet生命周期相关的方法。(调用顺序从前到后)
init():某个servlet被实例化后,容器会调用且仅调用一次init方法来初始化Servlet。init可被重写,也可为空。
service():处理请求的方法。request和response会被传入,在servlet对象的整个生命周期中,service()会被多次调用。
destroy():此方法在servlet实例从服务中移除时被调用(servlet容器关闭或servlet容器要释放内存)。当且仅当service()方法中所有线程都退出或执行超时后,才会被调用。此方法让servlet对象有机会去清理自身持有的资源,如内存文件等。该方法被调用后service()方法不会再被调用。
此应用程序实现了如下功能:
实现的类:
该程序还用了门面模式。创建了RequestFacade和ResponseFacade,把真正的requst和response作为私有成员放进facade里面,在facade的方法中调用其方法,防止其他程序员窜改request和response里面的具体处理方法。
连接器:负责创建request和response实例,并传入servlet中。这意味着连接器还要解析http报文,解析请求头、cookie、请求参数等。
Tomcat中的错误消息处理:错误消息存储在properties文件中,并为了方便管理,将properties放在不同的包下。每个property存储其包下的错误信息。
作用:每个包的一个StringManager处理每个包下的properties文件。(一对用,用单例模式)
获取错误消息:getString(错误码)
本章应用程序包括连接器模块、启动模块、核心模块。
启动模块:只有一个类Bootstrap。
核心模块:ServletProcessor 和 StaticResouceProcessor
该连接器已弃用,被Coyote取代。
默认连接器的优化:1.使用对象池;2.使用字符数组代替字符串。3.实现了HTTP1.1的全部新特性,向下兼容。
共三个,分别为:持久连接、
默认使用,也可以显示使用,方法是浏览器发送如下请求头:connection:keep-alive
解决问题:接收方在不知道发送内容长度的情况下,能解析已收到的内容。
实现:HTTP1.1使用名为”transfer-encoding“的请求头,指明字节流会分块发送。每块的结束为\r\n(回车换行符),0\r\n表示事务已经完成。
使用HTTP1.1的客户端可以在向服务器发送请求体前发送如下请求头:Expect:100-continue,并等待服务端的确认(HTTP/1.1 100 Continue\r\n)
作用:当客户端发送一个较长的请求体而不确定服务端是否能接收时,可发送此请求头,以避免因拒收而造成较大的网络资源耗费。
连接器必须要继承org.apache.catalina.Connector 接口,重要方法有4个,getContainer ()、setContainer()、creatRequest()、createResponse().
setContainer:将连接器和某个servlet()关联。
getContainer:获取与当前连接器相关联的servlet容器。
将介绍其与第三章不同的新功能,创建服务器套接字、维护HttpProcessor实例、接收HTTP请求服务。
HttpConnector的innitialize()方法会调用一个私有方法open(),此方法是从一个服务器套接字工厂获得一个连接,返回一个java.net.ServerSocket实例,赋值给成员变量serverSocket。
HttpConnector维护一个HttpProcessor池,避免每次创建和销毁的开销。HttpProcessor实例存储在一个名为processors的java.io.Stack类型变量中:private Stack processors = new Stack();
minProcessors<=processor的数目<=maxProcessor(两个为int变量)
若希望HttpConnector可持续创建processor,maxProcessor设置为-1
HttpConnector的主要业务逻辑在run()方法中:该方法包含一个while循环,该循环内服务器套接字等待HTTP请求,直到HttpConnector对象关闭。
具体流程:Http请求——> HttpConnector调用createProcess——>从processors中获得一个processor对象——>若超过最大限度,则返回null,并关闭socket;若没有,执行processor.asssign(socket).(注意,assign()方法是直接返回,而无需等待HttpProcessor实例完成解析,这样才能持续地接收传入的Http请求)
本章着重讲解该类中assign()方法的异步实现,使本类实例可以同时处理多个HTTP请求。
1. HttpProcessor实现Runnable接口,让其可以运行在自己的进程中。
2.通过available(布尔变量)、wait()、notifyAll()方法来实现处理器进程和连接器进程的通信。
关键方法
void assign(Socket socket):HttpProcessor中的方法,当其接收到新的socket时,使得available=true,然后notifyAll(),唤醒处理器进程。
Socket await():HttpProcessor中的方法, 在run()方法中,当available=false时,阻塞;否则获得socket,并返回socket,让run()方法继续后续处理。
具体过程
连接器进程启动(available=false)——>启动处理器进程,调用其assign(socket)方法(此时处理器进程调用了await方法,阻塞中)——>assign唤醒处理器进程——>处理器进程接收socket,并继续执行后续操作。
注意点
1.await()使用一个局部变量socket,不直接将成员变量socket返回:可以在当前Socket对象完成之前,继续接收下一个Socket对象。
2.await()方法为何需要调用notifiyAll():防止出现,另一个Socket对象已经到达,而此时变量available的值还是true的情况。在此情况下,连接器线程会在assign()内阻塞,知道处理器线程调用notifyAll()。
(本章只是看了,没有实现容器,不知道connnector是在什么时候、如何把servlet容器启动起来的)
servlet容器用于处理请求servlet资源,并为Web客户端填充response对象的模块。其为....catalina.Container接口的实例。Tomcat中共有Engine、Host、Wrapper、Context四种容器,本章讲解Context和Wrapper。
Engine:表示整个Catalina servlet引擎;
Host:表示包含有一个或多个Context容器的虚拟主机;
Context:表示一个Web应用程序,一个Context可用多个Wrapper;
Wrapper:表示一个单独的servlet。
1.一个容器可以用0个或多个低层级别的子容器。
对子容器的操作:addChild(Container child)、removeChild((Container child)、findChild((Container child)、findChildren()等。
2. 容器可以包含一些支持的组件,如载入器、记录器、管理器、领域和资源等。其提供了getter和setter方法将组件和容器关联起来。
对组件的操作:get\setLoader()、get\setLogger()等。
PS:Tomcat管理员还可通过配置文件(server.xml)来决定使用哪种容器。这是通过引入容器中的管道和阀的集合实现的。
该节旨在说明当连接器调用了servlet容器的invoke()方法后发生了什么。然后在对应小节中讨论Pineline、Value、ValueContext、Contained四个相关接口。
其包含servlet容器中将要被调用的任务。一个阀指一个具体的任务。阀的数量不包括基础阀。
工作机制:一个servlet对应一条管道。调用了容器的invoke()后,容器将处理任务交给管道,管道从第一个阀开始处理,直到所有阀处理完成。基础阀在最后处理。
invoke():处理阀
get/setBasic():获取/设置基础阀
add/get/remove Values():增加/获取/移除 阀
阀是Value的实例,用来处理接收到的请求。
invoke():处理阀
getInfo():返回阀的具体信息
invokeNext():处理下一个阀
getInfo():返回ValueContext的实现信息
阀可以选择是否实现此接口,该接口的实现类可以通过接口中的方法至多与一个servlet容器相关联。
Wrapper接口的实现类负责管理其基础servlet类的servlet生命周期,即调用servlet的init()、service()、destroy()等方法。其为最低级的servlet容器。
重要方法:load()、allocation()
allocate():分配一个已经初始化的servlet实例
load():载入并初始化servlet类
重要方法:addWrapper()、createWrapper()。
本章介绍通过Lifecycle、LifecycleEvent和LifecycleListener及工具类lifecycleSupport来管理生命周期的方法。
Catalina允许一个组件包含其他组件。故Catalina的组件只要启动一个,就可启动全部。这种单一启动/关闭机制是通过Lifecycle接口实现的。每个父组件启动后,会去寻找其子组件,并逐个启用start()/stop()方法。
重要方法:statrt()、stop()。供其父组件使用,实现对其进行启动/关闭操作。
最上面三个方法:与事件监听器相关。
生命周期事件是其的实例。
生命周期事件监听器为其实例。
其唯一方法为lifecycleEvent(),当事件监听器监听到某事件发生,会调用此方法。
其帮助组件管理监听器,并触发相应的生命周期事件。所有的监听器都会被放进listeners数组进行统一管理。
其为记录消息的组件,需要与某个Servlet容器相关联。