目录
手写tomcat主要分为3个步骤
- 提供socket服务
- 把请求和响应封装为request/response
- 根据请求进行转发
Maven依赖
request解析参数时使用到了json工具类帮助解析json字符串
<!--JSON依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.49</version>
</dependency>
</dependencies>
MyTomcat
主要作用是完成上面所说的手写tomcat的3个步骤,然后使用线程池进行多线程的管理。
package com.siyang.tomcat;
import com.siyang.tomcat.bean.MyRequest;
import com.siyang.tomcat.bean.MyResponse;
import com.siyang.tomcat.bean.MyUrlMapping;
import com.siyang.tomcat.config.MyUrlMappingConfig;
import com.siyang.tomcat.servlet.MyServlet;
import com.siyang.tomcat.thread.ExecutorPool;
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.Map;
/**
* @author siyang
* @create 2020-09-11 15:16
*/
public class MyTomcat {
private int port = 8088;
private Map<String,String> urlClazzMap = new HashMap();
public MyTomcat(int port) {
this.port = port;
}
public void start() throws IOException {
// 初始化
init();
// socket 连接
ServerSocket socket = new ServerSocket(port);
ExecutorPool executorPool = new ExecutorPool(5,10,10);
while(true){
Socket accept = socket.accept();
// 使用多线程池避免堵塞
executorPool.submit(new Runnable() {
@Override
public void run() {
try {
// 获取输入输出流
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream();
// 将请求和响应封装为request/response
MyRequest myRequest = new MyRequest(inputStream);
MyResponse myResponse = new MyResponse(outputStream);
System.out.println(myRequest.toString());
// 进行分发
if(myRequest.getUrl() != null){
// 分发
dispatch(myRequest,myResponse);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
}
private void init(){
/**
* 处理请求路径与文件的映射
*/
for (MyUrlMapping myUrlMapping : MyUrlMappingConfig.mappingList) {
urlClazzMap.put(myUrlMapping.getUrl(),myUrlMapping.getClazzPath());
}
}
/**
* 请求分发
* @param request
* @param response
*/
private void dispatch(MyRequest request,MyResponse response) {
try {
if(urlClazzMap.containsKey(request.getUrl())){
// 根据映射执行servlet
Class<?> servletClazz = Class.forName(urlClazzMap.get(request.getUrl()));
MyServlet o = (MyServlet)servletClazz.newInstance();
o.service(request,response);
}else{
// 判断有没有父路径匹配的情况 /xx/*的情况
String subUrl = request.getUrl();
while(subUrl.indexOf("/")!= -1){ // 保证url中有 /
subUrl = subUrl.substring(0,subUrl.lastIndexOf("/"));
String starUrl = subUrl + "/*";
if(urlClazzMap.containsKey(starUrl)){
// 根据映射执行servlet
Class<?> servletClazz = Class.forName(urlClazzMap.get(starUrl));
MyServlet o = (MyServlet)servletClazz.newInstance();
o.service(request,response);
break;
}
}
}
} catch (ClassNotFoundException e) {
System.out.println("映射文件类路径错误");
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("没有路径映射");
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
MyTomcat myTomcat = new MyTomcat(8888);
myTomcat.start();
}
}
bean
MyRequest
用于解析socket传来的inputStream,从流中解析请求头的信息,从中得到请求路径、请求方式、请求参数等参数。
package com.siyang.tomcat.bean;
import com.alibaba.fastjson.JSON;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @author siyang
* @create 2020-09-11 15:16
*/
public class MyRequest {
private String url;
private String method;
private Map<String,Object> param = new HashMap<>();
public MyRequest(InputStream inputStream) throws IOException {
String inputInfo = null;
byte[] bytes = new byte[1024];
int len = 0;
if((len = inputStream.read(bytes)) != -1){
inputInfo = new String(bytes);
}
/**
* GET /abc HTTP/1.1
* Host: localhost:8888
* Connection: keep-alive
* Upgrade-Insecure-Requests: 1
* User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3775.400 QQBrowser/10.6.4209.400
* Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,;q = 0.8
* Accept - Encoding:gzip, deflate, br
* Accept - Language:zh - CN, zh; q = 0.9
*/
// 只需要解析第一句话
if(inputInfo != null ){
String header = inputInfo.split("\n")[0];
String[] temp = header.split(" ");
// 获取请求方式
this.method = temp[0];
// 获取路径与参数
if(this.method.equals("GET")){
// 判断GET方法是否带有参数
if(temp[1].contains("?")){
// 参数
String p = temp[1].substring(temp[1].indexOf("?")+1);
String[] ps = p.split("&");
for (String s : ps) {
String[] kv = s.split("=");
this.param.put(kv[0],kv[1]);
}
// 请求地址
String u = temp[1].substring(0,temp[1].indexOf("?"));
this. url = u.endsWith("/") ? u.substring(0,u.lastIndexOf("/")): u ;
}else{
// 获取 url
this.url = temp[1].endsWith("/") ? temp[1].substring(0,temp[1].lastIndexOf("/")): temp[1] ;
}
}else if (this.method.equals("POST")){
// 获取 url
this.url = temp[1].endsWith("/") ? temp[1].substring(0,temp[1].lastIndexOf("/")): temp[1] ;
// 获取参数
String json = inputInfo.substring(inputInfo.indexOf("\r\n\r\n") + 4).replace("\u0000","");
try {
if(!json.equals("")){ // 参数不为空
this.param = (Map<String,Object>)JSON.parse(json);
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("post请求参数不为json格式");
}
}
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public Map<String, Object> getParam() {
return param;
}
public void setParam(Map<String, Object> param) {
this.param = param;
}
@Override
public String toString() {
return "MyRequest{" +
"url='" + url + '\'' +
", method='" + method + '\'' +
", param=" + param +
'}';
}
}
MyResponse
用来响应结果
package com.siyang.tomcat.bean;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author siyang
* @create 2020-09-11 15:41
*/
public class MyResponse {
private OutputStream outputStream;
private String contentType = "text/html";
private String charactor = "utf-8";
public MyResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void write(String content) throws IOException {
/**
* HTTP/1.1 200 OK
* Content-Type: text/html;charset=utf-8
* <html><body></body></html>
*
*/
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("HTTP/1.1 200 OK\n")
.append("Content-Type: "+contentType+";charset="+charactor+"\n")
.append("\r\n")
.append("<html><body>")
.append(content)
.append("</body></html>");
outputStream.write(stringBuffer.toString().getBytes());
outputStream.close();
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getCharactor() {
return charactor;
}
public void setCharactor(String charactor) {
this.charactor = charactor;
}
}
MyUrlMapping
封装请求与映射servlet的实体类
package com.siyang.tomcat.bean;
/**
* 封装请求与映射servlet的实体类
* @author siyang
* @create 2020-09-11 15:54
*/
public class MyUrlMapping {
private String url;
private String name;
private String clazzPath;
public MyUrlMapping(String url, String name, String clazzPath) {
this.url = url;
this.name = name;
this.clazzPath = clazzPath;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClazzPath() {
return clazzPath;
}
public void setClazzPath(String clazzPath) {
this.clazzPath = clazzPath;
}
}
servlet
AbcServlet
自定义servlet
package com.siyang.tomcat.servlet;
import com.siyang.tomcat.bean.MyRequest;
import com.siyang.tomcat.bean.MyResponse;
import java.io.IOException;
/**
* @author siyang
* @create 2020-09-11 15:57
*/
public class AbcServlet implements MyServlet {
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
this.doPost(myRequest,myResponse);
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("牛逼");
} catch (IOException e) {
e.printStackTrace();
}
}
}
MyServlet
自定义Servlet接口
package com.siyang.tomcat.servlet;
import com.siyang.tomcat.bean.MyRequest;
import com.siyang.tomcat.bean.MyResponse;
/**
* @author siyang
* @create 2020-09-11 15:47
*/
public interface MyServlet {
public void doGet(MyRequest myRequest, MyResponse myResponse);
public void doPost(MyRequest myRequest, MyResponse myResponse);
default void service(MyRequest myRequest, MyResponse myResponse){
if(myRequest.getMethod().equals("GET")){
doGet(myRequest,myResponse);
}else if(myRequest.getMethod().equals("POST")){
doPost(myRequest,myResponse);
}
}
}
config
MyUrlMappingConfig
配置请求路径与响应实体类的映射类
package com.siyang.tomcat.config;
import com.siyang.tomcat.bean.MyUrlMapping;
import java.util.ArrayList;
import java.util.List;
/**
* 配置请求路径与响应实体类的映射类
* @author siyang
* @create 2020-09-11 15:55
*/
public class MyUrlMappingConfig {
public static List<MyUrlMapping> mappingList = new ArrayList<MyUrlMapping>();
static {
mappingList.add(new MyUrlMapping("/*","请求abc","com.siyang.tomcat.servlet.AbcServlet"));
// mappingList.add(new MyUrlMapping("/abc","请求abc",""));
}
}
手写线程池
ExecutorPool
自定义线程池,进行线程任务的管理。初始化时指定最大线程数、核心线程数、任务队列数、非核心线程存活时间等参数。
当任务队列满了时,会创建非核心线程进行执行任务。非核心线程如果长时间没有执行任务(存活时间指定),会自动销毁。
这里的非核心线程和核心线程没有本质上的区别,当长时间没有执行任务,存活时间过了后,会按照空闲时间逐渐销毁,只留下核心线程数指定数目 的线程作为核心线程。
package com.siyang.tomcat.thread;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程池
* @author siyang
* @create 2020-09-11 17:24
*/
public class ExecutorPool {
private int MAX_THREAD_NUMBER = 10; // 默认最大线程数 ,最大线程数 - 核心线程数 = 可以回收的线程数
private int MAIN_THREAD_NUMBER = 3; // 核心线程数
private int ALIVE_TIME = 6*1000; // 无任务状态下非核心线程线程存活时间 (ms),默认1分钟
private List<Thread> threads = new CopyOnWriteArrayList<>(); // 线程列表
private int MAX_TASK_QUEUE_NUMBER = 10; // 默认最大任务队列数
private ArrayBlockingQueue<Runnable> taskQueue ; // 任务队列
private volatile int WORK_THREAD_NUMBER = 0; // 工作线程数
private final ReentrantLock lock = new ReentrantLock();
public ExecutorPool(){
taskQueue = new ArrayBlockingQueue(MAX_TASK_QUEUE_NUMBER);
}
/**
*
* @param mainThreadNumber 核心线程数
* @param maxThreadNumber 最大线程数
* @param taskQueueNumber 任务队列数
*/
public ExecutorPool(int mainThreadNumber,int maxThreadNumber,int taskQueueNumber){
if(mainThreadNumber>maxThreadNumber){
throw new RuntimeException("核心线程数不能大于最大线程数");
}
this.MAIN_THREAD_NUMBER = mainThreadNumber;
this.MAX_THREAD_NUMBER = maxThreadNumber;
this.MAX_TASK_QUEUE_NUMBER = taskQueueNumber;
taskQueue = new ArrayBlockingQueue(MAX_TASK_QUEUE_NUMBER);
}
/**
*
* @param mainThreadNumber 核心线程数
* @param maxThreadNumber 最大线程数
* @param taskQueueNumber 任务队列数
* @param aliveTime 非核心线程存活时间
*/
public ExecutorPool(int mainThreadNumber,int maxThreadNumber,int taskQueueNumber,int aliveTime){
this.MAIN_THREAD_NUMBER = mainThreadNumber;
this.MAX_THREAD_NUMBER = maxThreadNumber;
this.MAX_TASK_QUEUE_NUMBER = taskQueueNumber;
this.ALIVE_TIME = aliveTime;
taskQueue = new ArrayBlockingQueue(MAX_TASK_QUEUE_NUMBER);
}
/**
* 提交任务
* @param runnable
*/
public void submit(Runnable runnable){
try {
// 加锁
lock.lock();
// 存入任务队列
if(!taskQueue.offer(runnable)){
if(WORK_THREAD_NUMBER < MAX_THREAD_NUMBER){
MyThread myThread = new MyThread(runnable);
// 添加到线程列表中
threads.add(myThread);
// 执行任务
myThread.start();
WORK_THREAD_NUMBER ++;
// System.out.println("任务队列已满,创建非核心线程->名字:"+myThread.getName());
}else{
System.out.println("任务队列已满,无法创建非核心线程,请扩充任务队列大小");
}
}else{
// System.out.println("存入任务队列");
}
if(WORK_THREAD_NUMBER < MAIN_THREAD_NUMBER){
MyThread myThread = new MyThread(taskQueue.poll());
// 添加到线程列表中
threads.add(myThread);
// 执行任务
myThread.start();
WORK_THREAD_NUMBER ++;
// System.out.println("创建核心线程->名字:"+myThread.getName());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
/**
* 自定义线程类
*/
class MyThread extends Thread{
private Runnable task;
private long aliveTime; // 存活时间
public MyThread(Runnable runnable) {
this.task = runnable;
this.aliveTime = System.currentTimeMillis();
}
@Override
public void run() {
// 一直循环执行 从队列中获取任务
while(true){
if(this.task != null){
// 执行任务 不会新建线程
// System.out.println("线程名"+ super.getName());
this.task.run();
// 运行完后重置runnable;
this.task = null;
// 重置生存时间
this.aliveTime = System.currentTimeMillis();
}else{
// 超时删除
long date = System.currentTimeMillis();
if(date-aliveTime >= ALIVE_TIME){
// 核心线程无法删除
synchronized (MyThread.class){
if(WORK_THREAD_NUMBER > MAIN_THREAD_NUMBER){
threads.remove(this);
// System.out.println("删除队列"+",删除线程名字"+super.getName());
WORK_THREAD_NUMBER --;
break;
}
}
}
Runnable poll = taskQueue.poll();
this.task = poll;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
};
ExecutorPool executorPool = new ExecutorPool(10,10,10);
for(int i =0 ;i<10;i++){
Thread.sleep(1000);
executorPool.submit(r);
}
Thread.sleep(1000);
for(int i =0 ;i<100;i++){
executorPool.submit(r);
}
}
}