实现01

1.实现任务阶段1

编写mytomcat,该服务器能给浏览器返回“你好,我是服务器!”的简单信息。

根据之前的tomcat框架整体分析,我们将浏览器发送请求,tomcat服务器处理请求,返回资源的整个过程分为三个部分。现在来分析并初步实现第一部分的功能。

1.1基于socket开发服务端流程

1.2需求分析/图解

工作:先打通自定义web服务器和浏览器之间的通道。

如浏览器请求http://localhost:8080/Xxx,服务器可以接收请求并返回简单数据。

注意:这里的交互是都建立在http协议之上的。服务器获取到的数据是http格式的,返回的数据也要封装成http格式,浏览器才能正常解析。

http格式详见javaweb-day14-HTTP协议

1.3代码实现

MyTomcatV1:

package com.li.MyTomcat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket; /**
* @author 李
* @version 1.0
* 这是第一个版本的tomcat,可以完成接收浏览器请求,并返回信息功能
*/
public class MyTomcatV1 {
public static void main(String[] args) throws IOException { //1.创建ServerSocket,在8080端口监听
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("==========mytomcat在8080端口监听=========");
while (!serverSocket.isClosed()) {
//等待浏览器或客户端的连接
//如果有连接来,就创建socket
//这个socket就是浏览器和服务器之间的连接(通道)
Socket socket = serverSocket.accept();
//先接收浏览器发送的数据
InputStream inputStream = socket.getInputStream();//字节流
//为了方便,将其转成字符流(InputStreamReader==>转换流,将一个字节流转换成字符流)
//BufferedReader==>字符处理流
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String mes = null;
System.out.println("========接收到浏览器发送的数据=======");
//循环地读取
while ((mes = bufferedReader.readLine()) != null) {//按行读取数据,如果已到达流末尾,则返回 null
//判断mes的长度是否为0
if (mes.length() == 0) {
break;//退出while
}
System.out.println(mes);
} //我们的tomcat回送数据-按照http格式 //关闭资源
inputStream.close();
socket.close(); }
}
}

运行代码,在浏览器中发送请求http://localhost:8080/cal.html,后端输出如下:

可以看到,程序成功接收到了浏览器的请求(http格式)

下面以http格式响应浏览器请求:

package com.li.MyTomcat;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket; /**
* @author 李
* @version 1.0
* 这是第一个版本的tomcat,可以完成接收浏览器请求,并返回信息功能
*/
public class MyTomcatV1 {
public static void main(String[] args) throws IOException { //1.创建ServerSocket,在8080端口监听
/**
* ..........
*/ //mytomcat服务器回送数据-按照http格式
OutputStream outputStream = socket.getOutputStream();
//模仿响应头
//\r\n表示回车换行:因为响应头最后一行和响应体中间要隔一个空行,
// 所以最后一行要写两个\r\n(响应头换行是空行,空行再换行才是响应体)
//即 http响应体前面需要有两个换行\r\n\r\n
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "你好,我是服务器!"; System.out.println("=====MyTomcat给浏览器回送的数据=====");
System.out.println(resp);
//注意:这里返回数据要以字节流方式返回
outputStream.write(resp.getBytes());//将resp字符串以byte[]方式返回 //关闭资源
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
}
}
}

1.4测试

运行代码,在浏览器中发送请求http://localhost:8080/,后台输出如下:


浏览器输出如下:


2.实现任务阶段2-使用BIO线程模型,支持多线程

2.1BIO线程模型介绍

这里为了简单,使用方式一来完成操作,每次请求都会创建一个线程。

2.2需求分析/图解

  1. 需求分析如图所示,浏览器请求http://localhost:8080/,服务器返回“你好,我是服务器”。
  2. 后台mytomcat使用BIO线程模型,支持多线程,对数据的返回和处理移至线程里处理。

阶段1存在一个问题:当一个客户端被服务端在等待读取数据的时候,服务端会卡在那里,使得别的客户端无法与服务端连接以及收发数据。

解决方案是让等待接收数据的那块让子线程去做,主线程只需要一直监听是否有客户端连接即可,这样每个客户端就相互不影响了。

2.3代码实现

RequestHandler:

package com.li.MyTomcat.hander;

import java.io.*;
import java.net.Socket; /**
* @author 李
* @version 1.0
* RequestHandler是一个线程对象
* 用来处理一个http请求
*/
public class RequestHandler implements Runnable {
//定义一个Socket
private Socket socket = null; //在创建RequestHandler对象的时候,将主线程的socket传给线程对象来使用
public RequestHandler(Socket socket) {
this.socket = socket;
} @Override
public void run() {
//对客户端进行交互 try {
System.out.println("当前线程="+Thread.currentThread().getName()); InputStream inputStream = socket.getInputStream();
//将字节流转成字符流--方便按行读取浏览器请求
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println("=====MyTomcat接收浏览器数据=====");
String mes = null;
while ((mes = bufferedReader.readLine()) != null) {
//如果读取到的长度为零,即空串""
if (mes.length() == 0) {
break;//退出循环
}
System.out.println(mes);
} //将数据返回给浏览器/客户端(注意要将数据封装成http响应的格式,浏览器才能解析)
//构建http响应头(注意换行-响应头和响应体之间有一个空行,需要两个换行)
String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n";
String resp = respHeader + "<h1>Hello,我是服务器!<h1>";
System.out.println("=====MyTomcatV2给浏览器回送的数据=====");
System.out.println(resp);
//获取输出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write(resp.getBytes());//将字符串转成字节数组 //关闭流
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close(); } catch (IOException e) {
e.printStackTrace();
} finally {
//一定要确保socket关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

MyTomcatV2:

package com.li.MyTomcat;

import com.li.MyTomcat.hander.RequestHandler;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket; public class MyTomcatV2 {
public static void main(String[] args) throws IOException {
//监听
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("==========MyTomcatV2在8080端口监听=========");
//只要serverSocket没有关闭,就会一直等待客户端来连接
while (!serverSocket.isClosed()) {
//每接收一个浏览器连接,就会得到一个socket对象(这个socket就是服务器和浏览器的数据通道)
Socket socket = serverSocket.accept();
//创建一个线程,将该socket传给该线程,启动线程
new Thread(new RequestHandler(socket)).start();
}
}
}

2.4测试

运行代码,在浏览器输入http://localhost:8080/,输出如下:

后台输出:

3.实现任务阶段3-处理Servlet

  • Servlet声明周期

3.1需求分析/图解

问题分析:第二阶段只是简单地返回结果,没有和Servlet,web.xml关联起来

下面来完成第三阶段实现:

  • 自定义Servlet规范

要完成Servlet的调用就要先制定好Servlet规范,我们模仿Servlet的规范来制订一套自己的Servlet。

如下,模仿实际的Servlet来编写MyServlet及其抽象类MyHttpServlet,实现类MyCalServlet

  • 模拟实现request和response

另外,在真实的tomcat服务器中,接收http请求后会将其封装成HttpServletRequest对象,返回数据则是通过HttpServletResponse对象。

因此,除了自定义Servlet规范外,还要定义用于与http协议交互的两个对象

这里为了简化,就不使用实现接口的形式一层层封装了,直接用两个类来实现。

  • 在实现上面两个工作之前,先来看一个需求

我们现在做一个简单的需求:

浏览器请求http://localhost:8080/calServlet,提交数据,完成计算任务,如果该Servlet不存在,就返回404。大致的界面如下:

由于真正的HttpServletRequest和HttpServletResponse方法以及Servlet方法很多,这里为了简化,上述模拟的Servlet和自定义的request,response等只实现满足此需求的部分方法。

3.2模拟实现request和response

3.2.1MyRequest

package com.li.MyTomcat.http;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap; /**
* @author 李
* @version 1.0
* 1.MyRequest的作用是封装http请求的数据
* 2.比如 method,uri,还有参数列表等
* 3.MyRequest 的作用相当于原生的Servlet中的HttpServletRequest
* 4.这里先考虑get请求
*/
public class MyRequest { private String method;
private String uri;
//存放参数列表 参数名-参数值=>HashMap
private HashMap<String, String> parametersMapping = new HashMap<>();
private InputStream inputStream = null; //构造器==>对http请求进行封装
//在构造 MyRequest对象的时候,将关联的socket的InputStream传进来
// 这样就可以拿到该http请求的数据
public MyRequest(InputStream inputStream) {
this.inputStream = inputStream;
packageHttp();
} /**
* 将http请求的相关参数进行封装,然后提供相关的方法,进行获取
*/
public void packageHttp() {
System.out.println("MyRequest packageHttp()被调用...");
try {
//将字节流转成字符流
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
/**
* GET /myCalServlet?num1=11&num2=12 HTTP/1.1
* Host: localhost:8080
* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0
* ......
*/
//1.先读取第一行(请求行)
String requestLine = bufferedReader.readLine();
//第0组:GET 第1组:/myCalServlet?num1=11&num2=12 第2组:HTTP/1.1
String[] requestLineArr = requestLine.split(" ");
//2.得到method
method = requestLineArr[0];
//3.解析得到uri=/myCalServlet
//先看看uri有没有参数列表
int index = requestLineArr[1].indexOf("?");//找到?号的索引
if (index == -1) {//没有问号,说明uri没有参数列表
uri = requestLineArr[1];
} else {
//截取到?号的前一位,即为uri
uri = requestLineArr[1].substring(0, index);
//获取参数列表
String parameters = requestLineArr[1].substring(index + 1);//parameters => num1=11&num2=12
String[] parametersPair = parameters.split("&");//parametersPair=["num1=11","num2=12",...]
//防止浏览器的提交的地址为 /myCalServlet?
if (null != parametersPair && !"".equals(parametersPair)) {
//再分割
for (String parameterPair : parametersPair) {
//parameterVal=["num1","10"]
String[] parameterVal = parameterPair.split("=");
if (parameterVal.length == 2) {
//放入到parametersMapping中
parametersMapping.put(parameterVal[0], parameterVal[1]);
}
}
}
}
//这里不能关闭流inputStream,因为inputStream和socket关联,
// 如果关闭了inputStream则socket也会一起关闭
} catch (Exception e) {
e.printStackTrace();
}
} //request有一个特别重要的方法-getParameter()
public String getParameter(String name) {
if (parametersMapping.containsKey(name)) {
return parametersMapping.get(name);
} else {
return "";
}
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} public String getUri() {
return uri;
} public void setUri(String uri) {
this.uri = uri;
} @Override
public String toString() {
return "MyRequest{" +
"method='" + method + '\'' +
", uri='" + uri + '\'' +
", parametersMapping=\n" + parametersMapping +
'}';
}
}

3.2.2MyResponse

package com.li.MyTomcat.http;

import java.io.OutputStream;

/**
* @author 李
* @version 1.0
* 1.MyResponse可以封装OutputStream(和socket关联)
* 2.即可以通过 MyResponse对象返回http响应给浏览器或客户端
* 3.MyResponse的作用等价于原生的Servlet的HttpServletResponse
*/
public class MyResponse {
private OutputStream outputStream = null; //写一个http的响应头
public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n"; //在构造 MyResponse 对象的时候,将关联的socket的OutputStream传进来
public MyResponse(OutputStream outputStream) {
this.outputStream = outputStream;
} //当我们需要给浏览器返回数据时,可以通过MyResponse的属性获得输出流
public OutputStream getOutputStream() {
return outputStream;
}
}

3.2.3修改RequestHandler

package com.li.MyTomcat.hander;

import com.li.MyTomcat.http.MyRequest;
import com.li.MyTomcat.http.MyResponse; import java.io.*;
import java.net.Socket; /**
* @author 李
* @version 1.0
* RequestHandler是一个线程对象
* 用来处理一个http请求
*/
public class RequestHandler implements Runnable {
//定义一个Socket
private Socket socket = null; //在创建RequestHandler对象的时候,将主线程的socket传给线程对象来使用
public RequestHandler(Socket socket) {
this.socket = socket;
} @Override
public void run() {
//对客户端进行交互 try {
System.out.println("当前线程=" + Thread.currentThread().getName()); InputStream inputStream = socket.getInputStream(); MyRequest myRequest = new MyRequest(inputStream);
String num1 = myRequest.getParameter("num1");
String num2 = myRequest.getParameter("num2");
String name = myRequest.getParameter("name");
String email = myRequest.getParameter("email");
System.out.println("请求的参数num1= " + num1);
System.out.println("请求的参数num2= " + num2);
System.out.println("请求的参数name= " + name);
System.out.println("请求的参数email= " + email);
System.out.println(myRequest); //这里我们可以通过myResponse对象返回数据给客户端
MyResponse myResponse = new MyResponse(socket.getOutputStream());
String resp = MyResponse.respHeader + "<h1>Hello,我是myResponse返回的信息</h1>";
//这里的应用场景是:为了将来在Servlet中使用response对象,可以获取到输出流
OutputStream outputStream = myResponse.getOutputStream();
outputStream.write(resp.getBytes());
outputStream.flush();
outputStream.close(); inputStream.close();
socket.close(); } catch (IOException e) {
e.printStackTrace();
} finally {
//一定要确保socket关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

MyTomcatV2类保持不变。

3.2.4测试

运行MyTomcatV2,在浏览器中输入如下请求:

http://localhost:8080/myCalServlet?num1=100&num2=200&name=jack&email=jack@qq.com

浏览器输出:

后台输出:

day02-实现01的更多相关文章

  1. Python之系统交互(subprocess)

    本节内容 os与commands模块 subprocess模块 subprocess.Popen类 总结 我们几乎可以在任何操作系统上通过命令行指令与操作系统进行交互,比如Linux平台下的shell ...

  2. Python--subprocess

    本节内容 os与commands模块 subprocess模块 subprocess.Popen类 总结 我们几乎可以在任何操作系统上通过命令行指令与操作系统进行交互,比如Linux平台下的shell ...

  3. 课程8:《Maven精品教程视频》--视频目录

    2017年3月18日 老师讲的课程 \day01视频\01maven依赖管理.avi; \day01视频\02maven项目构建.avi; \day01视频\03maven程序安装.avi; \day ...

  4. 【转】Python之系统交互(subprocess)

    [转]Python之系统交互(subprocess) 本节内容 os与commands模块 subprocess模块 subprocess.Popen类 总结 我们几乎可以在任何操作系统上通过命令行指 ...

  5. Python与系统的交互方式

    本节内容 os与commands模块 subprocess模块 subprocess.Popen类 总结 我们几乎可以在任何操作系统上通过命令行指令与操作系统进行交互,比如Linux平台下的shell ...

  6. ZZJ_淘淘商城项目:day02(淘淘商城01 - 项目讲解、环境搭建)

    在用Eclipse的开发中,可手动排除不必要的依赖坐标传递. <!-- JPA的1.0依赖 --> <dependency> <groupId>javax.pers ...

  7. 《Professional JavaScript for Web Developers》day02

    <Professional JavaScript for Web Developers>day02 1.在HTML中使用JavaScript 1.1 <script>元素 HT ...

  8. python开发学习-day02(元组、字符串、列表、字典深入)

    s12-20160109-day02 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...

  9. Day02:我的Python学习之路

    1.初识模块 Python的强大之处在于他有非常丰富和强大的标准库和第三方库,现在简单的学习2个常见的标准库——sys和os. (1)系统的标准库sys # Author:GCL # 系统的标准库sy ...

  10. day02 html body中的标签

    day02 html   一.body中的标签     a标签: <!DOCTYPE html> <html lang="en"> <head> ...

随机推荐

  1. Javaweb___Jquery高级

    今日内容: 1. JQuery 高级 1. 动画 2. 遍历 3. 事件绑定 4. 案例 5. 插件 JQuery 高级 1. 动画 1. 三种方式显示和隐藏元素 1. 默认显示和隐藏方式 1. sh ...

  2. KingbaseES 局部索引

    一个列要不要建立btree索引,判断条件是其键值分布是否够离散,比如主键.唯一键,可以建立索引.如果这个列有大量重复的值,则建立索引没有意义. 在生产环境中常会碰到键值分布不均匀的列,如表t1有一个名 ...

  3. OID 与隐含列

    熟悉PostgreSQL的都知道,PG12 开始,不再支持OID伪列.KingbaseES 为了保证与旧版本兼容,特增加了OID 隐含列的支持. R3版本:OID 是整个数据库共用的"序列& ...

  4. Mac根据端口找进程id

    lsof -i:20942 以后认真的学习一下这个命令

  5. Ingress-nginx灰度发布功能详解

  6. es日志配置,只保存最近3天的日志

    Elasticsearch使用Log4j 2进行日志记录.可以使用log4j2.properties文件配置Log4j2. Elasticsearch公开三个属性 ${sys:es.logs.base ...

  7. 第五章:Admin管理后台 - 1:自定制Admin

    如果只是在admin中简单的展示及管理模型,那么在admin.py模块中使用admin.site.register将模型注册一下就好了: from django.contrib import admi ...

  8. 2_jQuery

    一. jQuery介绍 1.1 什么是jQuery jQuery, 顾名思义, 也就是JavaScript和查询(Query), 它就是辅助JavaScript开发的js类库 1.2 jQuery核心 ...

  9. ASCII(American Standard Code for Information Interchange,美国标准信息交换代码)

    ASCII(American Standard Code for Information Interchange,美国标准信息交换代码) ASCII简介 ASCII(American Standard ...

  10. 监控 HTTP 服务器的状态(测试返回码)shell脚本

    #!/bin/bash # 监控 HTTP 服务器的状态(测试返回码) # 设置变量,url为你需要检测的目标网站的网址(IP 或域名),比如百度 url=http://http://183.232. ...