Tomcat应该都不陌生,咱们常常会把写好的代码打包放在Tomcat里并启动,而后在浏览器里就能愉快的调用咱们写的代码来实现相应的功能了,那么Tomcat是如何工做的?html
1、Tomcat工做原理java
咱们启动Tomcat时双击的startup.bat文件的主要做用是找到catalina.bat,而且把参数传递给它,而catalina.bat中有这样一段话: web
源码不过多赘述,咱们在这里只须要把握总体架构,有兴趣的同窗能够本身研究下源码。Tomcat的server.xml配置文件中能够对应构架图中位置,多层的表示能够配置多个: 浏览器
Server:服务器Tomcat的顶级元素,它包含了全部东西。服务器
Service:一组 Engine(引擎) 的集合,包括线程池 Executor 和链接器 Connector 的定义。多线程
Engine(引擎):一个 Engine表明一个完整的 Servlet引擎,它接收来自Connector的请求,并决定传给哪一个Host来处理。架构
Container(容器):Host、Context、Engine和Wraper都继承自Container接口,它们都是容器。app
Connector(链接器):将Service和Container链接起来,注册到一个Service,把来自客户端的请求转发到Container。socket
Host:即虚拟主机,所谓的”一个虚拟主机”可简单理解为”一个网站”。网站
Context(上下文 ): 即 Web 应用程序,一个 Context 即对于一个 Web 应用程序。
Context容器直接管理Servlet的运行,Servlet会被其给包装成一个StandardWrapper类去运行。Wrapper负责管理一个Servlet的装载、初始化、执行以及资源回收,它是最底层容器。
好比如今有如下网址,根据“/”切割的连接就会定位到具体的处理逻辑上,且每一个容器都有过滤功能。
2、Tomcat实现思路
下面只是简单实现效果,当浏览器访问对应地址时:
2.拿到浏览器的请求,解析并返回URL地址,用I/O输入流读取本地磁盘上相应文件。
3.读取文件,不存在构建响应报文头、HTML正文内容,存在则写到浏览器端。
3、实现Tomcat
工程文件结构和pom.xml文件:
public class HttpServer {
// 用于判断是否须要关闭容器
private boolean shutdown = false;
public void acceptWait() {
ServerSocket serverSocket = null;
try {
//端口号,最大连接数,ip地址
serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// 等待用户发请求
while (!shutdown) {
try {
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// 接受请求参数
Request request = new Request(is);
request.parse();
// 建立用于返回浏览器的对象
Response response = new Response(os);
response.setRequest(request);
response.sendStaticResource();
//关闭一次请求的socket,由于http请求就是采用短链接的方式
socket.close();
//若是请求地址是/shutdown 则关闭容器
if(null != request){
shutdown = request.getUrL().equals("/shutdown");
}
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.acceptWait();
}
}
复制代码
2.建立Request类,获取HTTP的请求头全部信息并截取URL地址返回:
public class Request {
private InputStream is;
private String url;
public Request(InputStream input) {
this.is = input;
}
public void parse() {
//从socket中读取一个2048长度字符
StringBuffer request = new StringBuffer(Response.BUFFER_SIZE);
int i;
byte[] buffer = new byte[Response.BUFFER_SIZE];
try {
i = is.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
//打印读取的socket中的内容
System.out.print(request.toString());
url = parseUrL(request.toString());
}
private String parseUrL(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');//看socket获取请求头是否有值
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public String getUrL() {
return url;
}
}
复制代码
3.建立Response类,响应请求读取文件并写回到浏览器
public class Response {
public static final int BUFFER_SIZE = 2048;
//浏览器访问D盘的文件
private static final String WEB_ROOT ="D:";
private Request request;
private OutputStream output;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
//拼接本地目录和浏览器端口号后面的目录
File file = new File(WEB_ROOT, request.getUrL());
//若是文件存在,且不是个目录
if (file.exists() && !file.isDirectory()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}else {
//文件不存在,返回给浏览器响应提示,这里能够拼接HTML任何元素
String retMessage = "<h1>"+file.getName()+" file or directory not exists</h1>";
String returnMessage ="HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: "+retMessage.length()+"\r\n" +
"\r\n" +
retMessage;
output.write(returnMessage.getBytes());
}
}
catch (Exception e) {
System.out.println(e.toString() );
}
finally {
if (fis!=null)
fis.close();
}
}
}
复制代码
4、扩展点
1.在WEB_INF文件夹下读取web.xml解析,经过请求名找到对应的类名,经过类名建立对象,用反射来初始化配置信息,如welcome页面,Servlet、servlet-mapping,filter,listener,启动加载级别等。
2.抽象Servlet类来转码处理请求和响应的业务。发过来的请求会有不少,也就意味着咱们应该会有不少的Servlet,例如:RegisterServlet、LoginServlet等等还有不少其余的访问。能够用到相似于工厂模式的方法处理,随时产生不少的Servlet,来知足不一样的功能性的请求。
3.使用多线程。本文的代码是死循环,且只能有一个连接,而现实中的状况是每每会有不少不少的客户端发请求,能够把每一个浏览器的通讯封装到一个线程当中。