编写Tomcat的方法是什么
本篇内容介绍了“编写Tomcat的方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
创新互联建站提供高防服务器租用、云服务器、香港服务器、遂宁联通机房等
Apache Tomcat 是Java Servlet, JavaServer Pages (JSP),Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。
基本结构
tomcat架构图
我们可以把上面这张架构图做简化,简化后为:
什么是http协议
Http是一种网络应用层协议,规定了浏览器与web服务器之间如何通信以及数据包的结构。
通信大致可以分为四步:
先建立连接。
发送请求数据包。
发送响应数据包。
关闭连接。
优点
web服务器可以利用有限的连接为尽可能多的客户请求服务。
tomcat中Servlet的运作方式
在浏览器地址栏输入http://ip:port/servlet-day01/hello
浏览器依据IP、port建立连接(即与web服务器之间建立网络连接)。
浏览器需要将相关数据打包(即按照http协议要求,制作一个 请求数据包,包含了一些数据,比如请求资源路径),并且将请求 数据包发送出去。
web服务器会将请求数据包中数据解析出来,并且将这些数据添加 到request对象,同时,还会创建一个response对象。
web服务器创建Servlet对象,然后调用该对象的service方法(会将request和response作为参数)。注:在service方法里面,通过使用request获得请求相关的数据, 比如请求参数值,然后将处理结果写到response。
web服务器将response中的数据取出来,制作响应数据包,然后发送给浏览器。
浏览器解析响应数据包,然后展现。
可以总结唯一张图:
什么是Servlet呢?
Servlet是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能,统一接口。由其他内部厂商如tomcat,jetty内部实现web的功能。如一个http请求到来:容器将请求封装为servlet中的HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse返回给客户端的过程。
什么是Servlet规范?
从 Jar 包上来说,Servlet 规范就是两个 Jar 文件。servlet-api.jar 和 jsp-api.jar,Jsp 也是一种 Servlet。
从package上来说,就是 javax.servlet 和 javax.servlet.http 两个包。
从接口来说,就是规范了 Servlet 接口、Filter 接口、Listener 接口、ServletRequest 接口、ServletResponse 接口等。类图如下:
第一版:Socket版
使用Socket编程,实现简单的客户端和服务端的聊天。
服务端代码如下:
package com.tian.v1; import java.io.*; import java.net.*; public class Server { public static String readline = null; public static String inTemp = null; public static String turnLine = "\n"; public static final String client = "客户端:"; public static final String server = "服务端:"; public static final int PORT = 8090; public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(PORT); System.out.println("服务端已经准备好了"); Socket socket = serverSocket.accept(); BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in)); BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter socketOut = new PrintWriter(socket.getOutputStream()); while (true) { inTemp = socketIn.readLine(); if (inTemp != null &&inTemp.contains("over")) { systemIn.close(); socketIn.close(); socketOut.close(); socket.close(); serverSocket.close(); } System.out.println(client + inTemp); System.out.print(server); readline = systemIn.readLine(); socketOut.println(readline); socketOut.flush(); } } }
客户端代码如下:
package com.tian.v1; import java.io.*; import java.net.*; public class Client { public static void main(String[] args) throws Exception { String readline; String inTemp; final String client = "客户端说:"; final String server = "服务端回复:"; int port = 8090; byte[] ipAddressTemp = {127, 0, 0, 1}; InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp); //首先直接创建socket,端口号1~1023为系统保存,一般设在1023之外 Socket socket = new Socket(ipAddress, port); BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in)); BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter socketOut = new PrintWriter(socket.getOutputStream()); while (true) { System.out.print(client); readline = systemIn.readLine(); socketOut.println(readline); socketOut.flush(); //处理 inTemp = socketIn.readLine(); if (inTemp != null && inTemp.contains("over")) { systemIn.close(); socketIn.close(); socketOut.close(); socket.close(); } System.out.println(server + inTemp); } } }
过程如下:
第二版:我们直接请求http://localhost:8090
实现代码如下:
package com.tian.v2; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class MyTomcat { /** * 设定启动和监听端口 */ private int port = 8090; /** * 启动函数 * * @throws IOException */ public void start() throws IOException { System.out.println("my tomcat starting..."); String responseData = "6666666"; ServerSocket socket = new ServerSocket(port); while (true) { Socket accept = socket.accept(); OutputStream outputStream = accept.getOutputStream(); String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData; outputStream.write(responseText.getBytes()); accept.close(); } } /** * 启动入口 */ public static void main(String[] args) throws IOException { MyTomcat tomcat = new MyTomcat(); tomcat.start(); } }
再写一个工具类,内容如下;
ackage com.tian.v2; public class HttpProtocolUtil { /** * 200 状态码,头信息 * * @param contentLength 响应信息长度 * @return 200 header info */ public static String getHttpHeader200(long contentLength) { return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n" + "Content-Length: " + contentLength + " \n" + "\r\n"; } /** * 为响应码 404 提供请求头信息(此处也包含了数据内容) * * @return 404 header info */ public static String getHttpHeader404() { String str404 = "404 not found
"; return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n" + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404; } }
启动main方法:
使用IDEA访问:
在浏览器访问:
自此,我们的第二版本搞定。下面继续第三个版本;
第三版:封装请求信息和响应信息
一个http协议的请求包含三部分:
方法 URI 协议/版本
请求的头部
主体内容
比如
POST /index.html HTTP/1.1 Accept: text/plain; text/html Accept-Language: en-gb Connection: Keep-Alive Host: localhost User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98) Content-Length: 33 Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate lastName=tian&firstName=JohnTian
简单的解释
数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/index.html,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。
请求头部从第二行开始,使用英文冒号(:)来分离键和值。
请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。
类似于http协议的请求,响应也包含三个部分。
协议 状态 状态描述
响应的头部
主体内容
比如:
HTTP/1.1 200 OK Server: Microsoft-IIS/4.0 Date: Mon, 5 Jan 2004 13:13:33 GMT Content-Type: text/html Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT Content-Length: 112HTTP Response Example Welcome to Brainy Software
简单解释
第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。
之后表示响应头部。
响应头部和主体内容之间使用空行来分离。
代码实现
创建一个工具类,用来获取静态资源信息。
package com.tian.v3; import com.tian.v2.HttpProtocolUtil; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * 提取了一些共用类和函数 */ public class ResourceUtil { /** * 根据请求 url 获取完整绝对路径 */ public static String getPath(String url) { String path = ResourceUtil.class.getResource("/").getPath(); return path.replaceAll("\\\\", "/") + url; } /** * 输出静态资源信息 */ public static void outputResource(InputStream input, OutputStream output) throws IOException { int count = 0; while (count == 0) { count = input.available(); } int resourceSize = count; output.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes()); long written = 0; int byteSize = 1024; byte[] bytes = new byte[byteSize]; while (written < resourceSize) { if (written + byteSize > resourceSize) { byteSize = (int) (resourceSize - written); bytes = new byte[byteSize]; } input.read(bytes); output.write(bytes); output.flush(); written += byteSize; } } }
另外HttpProtocolUtil照样用第二版本中。
再创建Request类,用来解析并存放请求相关参数。
package com.tian.v3; import java.io.IOException; import java.io.InputStream; public class Request { /** * 请求方式, eg: GET、POST */ private String method; /** * 请求路径,eg: /index.html */ private String url; /** * 请求信息输入流
* 示例 ** GET / HTTP/1.1 * Host: localhost * Connection: keep-alive * Pragma: no-cache * Cache-Control: no-cache * Upgrade-Insecure-Requests: 1 * User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 **/ private InputStream inputStream; public Request() { } public Request(InputStream inputStream) throws IOException { this.inputStream = inputStream; int count = 0; while (count == 0) { count = inputStream.available(); } byte[] bytes = new byte[count]; inputStream.read(bytes); // requestString 参考:this.inputStream 示例 String requestString = new String(bytes); // 按换行分隔 String[] requestStringArray = requestString.split("\\n"); // 读取第一行数据,即:GET / HTTP/1.1 String firstLine = requestStringArray[0]; // 遍历第一行数据按空格分隔 String[] firstLineArray = firstLine.split(" "); this.method = firstLineArray[0]; this.url = firstLineArray[1]; } public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public InputStream getInputStream() { return inputStream; } public void setInputStream(InputStream inputStream) { this.inputStream = inputStream; } }
把第二版的MyTomcat进行小小调整:
package com.tian.v3; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class MyTomcat { private static final int PORT = 8090; public void start() throws IOException { System.out.println("my tomcat starting..."); ServerSocket socket = new ServerSocket(PORT); while (true) { Socket accept = socket.accept(); OutputStream outputStream = accept.getOutputStream(); // 分别封装 Request 和 Response Request request = new Request(accept.getInputStream()); Response response = new Response(outputStream); // 根据 request 中的 url,输出 response.outputHtml(request.getUrl()); accept.close(); } } public static void main(String[] args) throws IOException { MyTomcat tomcat = new MyTomcat(); tomcat.start(); } }
然后再创建一个index.html,内容很简单:
hello world you already succeed!
这一需要注意,index.html文件的存放路径不放错了,视本地路径来定哈,放在classes文件夹下的。你可以debug试试,看看你应该放在那个目录下。
启动MyTomcat。
访问http://localhost:8090/index.html
自此,我们针对于Http请求参数和相应参数做了一个简单的解析以及封装。
尽管其中还有很多问题,但是字少看起来有那点像样了。我们继续第四版,
第四版:实现动态请求资源
用过servlet的同学都知道,Servlet中有三个很重要的方法init、destroy 、service 。其中还记得我们自己写LoginServlet的时候,还会重写HttpServlet中的doGet()和doPost()方法。下面们就自己来搞一个:
Servlet类代码如下:
public interface Servlet { void init() throws Exception; void destroy() throws Exception; void service(Request request, Response response) throws Exception; }
然后再写一个HttpServlet来实现Servlet。
代码实现如下:
package com.tian.v4; public abstract class HttpServlet implements Servlet { @Override public void init() throws Exception { } @Override public void destroy() throws Exception { } @Override public void service(Request request, Response response) throws Exception { String method = request.getMethod(); if ("GET".equalsIgnoreCase(method)) { doGet(request, response); } else { doPost(request, response); } } public abstract void doGet(Request request, Response response) throws Exception; public abstract void doPost(Request request, Response response) throws Exception; }
下面我们就来写一个自己的Servlet,比如LoginServlet。
package com.tian.v4; public class LoginServlet extends HttpServlet { @Override public void doGet(Request request, Response response) throws Exception { String repText = "LoginServlet by GET method
"; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void doPost(Request request, Response response) throws Exception { String repText = "LoginServlet by POST method
"; response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText); } @Override public void init() throws Exception { } @Override public void destroy() throws Exception { } }
大家是否还记得,我们在学习Servlet的时候,在resources目录下面有个web.xml。我们这个版本也把这个xml文件给引入。
login com.tian.v4.LoginServlet login /login
既然引入了xml文件,那我们就需要去读取这个xml文件,并解析器内容。所以这里我们需要引入两个jar包。
dom4j dom4j 1.6.1 jaxen jaxen 1.1.6
万事俱备,只欠东风了。这时候我们来吧MyTomcat这个类做一些调整即可。
下面有个很重要的initServlet()方法,刚刚是对应下面这张图中的List servlets,但是我们代码里使用的是Map来存储Servlet的,意思就那么个意思,把Servlet放在集合里。
这也就是为什么大家都把Tomcat叫做Servlet容器的原因,其实真正的容器还是java集合。
package com.tian.v4; import com.tian.v3.RequestV3; import com.tian.v3.ResponseV3; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.List; import java.util.Map; public class MyTomcat { /** * 设定启动和监听端口 */ private static final int PORT = 8090; /** * 存放 Servlet信息,url: Servlet 实例 */ private MapservletMap = new HashMap<>(); public void start() throws Exception { System.out.println("my tomcat starting..."); initServlet(); ServerSocket socket = new ServerSocket(PORT); while (true) { Socket accept = socket.accept(); OutputStream outputStream = accept.getOutputStream(); // 分别封装 RequestV3 和 ResponseV3 RequestV4 requestV3 = new RequestV4(accept.getInputStream()); ResponseV4 responseV3 = new ResponseV4(outputStream); // 根据 url 来获取 Servlet HttpServlet httpServlet = servletMap.get(requestV3.getUrl()); // 如果 Servlet 为空,说明是静态资源,不为空即为动态资源,需要执行 Servlet 里的方法 if (httpServlet == null) { responseV3.outputHtml(requestV3.getUrl()); } else { httpServlet.service(requestV3, responseV3); } accept.close(); } } public static void main(String[] args) throws Exception { MyTomcat tomcat = new MyTomcat(); tomcat.start(); } /** * 解析web.xml文件,把url和servlet解析出来, * 并保存到一个java集合里(Map) */ public void initServlet() throws Exception { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml"); SAXReader saxReader = new SAXReader(); Document document = saxReader.read(resourceAsStream); Element rootElement = document.getRootElement(); List list = rootElement.selectNodes("//servlet"); for (Element element : list) { // show Element servletnameElement = (Element) element.selectSingleNode("servlet-name"); String servletName = servletnameElement.getStringValue(); //server.ShowServlet Element servletclassElement = (Element) element.selectSingleNode("servlet-class"); String servletClass = servletclassElement.getStringValue(); // 根据 servlet-name 的值找到 url-pattern Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']"); // /show String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue(); servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance()); } } }
启动,再次访问http://localhost:8090/index.html
同时,我们可以访问http://localhost:8090/login图片
到此,第四个版本也搞定了。
但是前面四个版本都有一个共同的问题,全部使用的是BIO。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
所以,大家在网上看到的手写tomcat的,也有使用线程池来做的,这里希望大家能get到为什么使用线程池来实现。另外,其实在tomcat高版本中已经没有使用BIO了。
而 HTTP/1.1默认使用的就是NIO了。
但这个只是通信方式,重点是我们要理解和掌握tomcat的整体实现。
“编写Tomcat的方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注创新互联网站,小编将为大家输出更多高质量的实用文章!
网站栏目:编写Tomcat的方法是什么
标题网址:http://myzitong.com/article/gdhsdh.html