– 更新信息 –
第一版代码实现了基本的交互功能,但只实现了单线程,此次迭代修改多线程,并升级为Maven项目,同时优化代码排版,提高代码可读性
第一版代码介绍博客地址:最通俗易懂的 - Tomcat 核心源码仿写
– 源码地址 –
朱元杰的开源仓库 – Tomcat核心源码仿写
– 正文内容 –
直接上main方法代码:
public static void main(String[] args) throws IOException {
//创建JUL日志执行器
Logger myTomcatLogger = Logger.getLogger("MyTomcat");
//创建线程池
ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();
//启动阶段
startupPhase(myTomcatLogger);
//注册端口
InetAddress localHost = InetAddress.getLocalHost();
myTomcatLogger.info("当前服务器信息:" + localHost);
ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);
while (true) {
myTomcatLogger.info("等待建立连接 - " + connectSerialNumber);
Socket socket = serverSocket.accept();
myTomcatLogger.info("连接已建立 - " + connectSerialNumber);
connectSerialNumber++;
threadPoolExecutor.execute(() -> {
//接收Http请求报文
ArrayList<String> httpMessage = receiveHttp(socket, myTomcatLogger);
//处理请求
//获取请求方式
String httpHead = httpMessage.get(0);
String requestStyle = httpHead.split(" ")[0];
if (requestStyle.equals("GET")) {
String requestPathAndParameter = httpHead.split(" ")[1];
//创建HttpRequest对象
MyHttpRequest myHttpRequest = new MyHttpRequest();
//获取请求路径,并将参数置入HttpRequest对象
String requestPath = getRequestPathAndSetMyHttpRequest(requestPathAndParameter, myHttpRequest, myTomcatLogger);
//还没处理 favicon.ico 请求,先屏蔽
if (requestPath.equals("favicon.ico")) {
myTomcatLogger.info("暂时无法响应 favicon.ico");
return;
}
//创建HttpResponse对象
MyHttpResponse myHttpResponse = getMyHttpResponse(socket);
//反射调用Servlet方法
servletActuator(requestPath, "doGet", myHttpRequest, myHttpResponse);
} else {
myTomcatLogger.info("还未开通其他请求方式.....");
}
});
}
本次引入线程池,将交互的内容交给线程池完成,main方法负责建立socket连接
可以看到有个while(true)循环,不断建立socket连接,建立起连接后就将连接丢给threadPoolExecutor.execute 去处理
在 threadPoolExecutor.execute 中完成的还是那几步:
- 接收Http请求报文
- 获取请求路径和请求参数
- 创建出 HttpRequest 和 HttpResponse 对象
- 通过反射调用请求路径对应的 servlet 方法
我将每一步的具体实现方法都封装成了一个方法,需要的就去我的Gitee上拿源码吧
另外,通过配置文件配置线程池的方法如下:
- 引入 org.yaml 依赖,因为我是通过 yml 文件进行配置的
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.29</version>
</dependency>
- 在resources 文件下编写配置文件 config.yml 内容如下:
thread:
pool:
coreSize: 5
maxSize: 10
keepAliveTime: 60
workQueueSize: 1000
- 接着就可以创建线程池了
private static ThreadPoolExecutor createThreadPoolExecutor() throws FileNotFoundException {
//读取yml配置文件
Yaml yaml = new Yaml();
InputStream inputStream = new FileInputStream("src\\main\\resources\\config.yml");
Map<String, Object> threadPoolMap = new HashMap<>();
threadPoolMap = yaml.load(inputStream);
Map<String, Map<String, Object>> poolMap = (Map<String, Map<String, Object>>) threadPoolMap.get("thread");
//创建线程池
return new ThreadPoolExecutor(
(Integer) poolMap.get("pool").get("coreSize"),
(Integer) poolMap.get("pool").get("maxSize"),
(Integer) poolMap.get("pool").get("keepAliveTime"),
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>((Integer) poolMap.get("pool").get("workQueueSize")),
new ThreadPoolExecutor.AbortPolicy()
);
}
现在已经可以进行多台主机的连续请求,效果如下:
第一次请求:
第二次请求 - 修改请求地址为 address2(出现了乱码问题,不过问题不大嘿嘿):
查看控制台输出:
实际上这边总共有四次请求,这是因为每次我们发送请求时,浏览器都会自动发出一个请求来获取网站的 favicon.ico 文件。favicon.ico 是网站的小图标,通常位于网站的根目录下。这个请求是浏览器自动发出的,不需要用户手动触发。
另外一个可以看到,当前都是基于 Http 1.1 的请求, Http 1.1 是短链接,因此每一次请求都要建立一次socket连接