一、原理讲解
OSI七层模型
如何调动后四层协议
先仿写一个socket服务端和客户端接发数据的demo
/**
* 服务器端
*/
public class ServerA {
//main方法是主线程要对数据进行处理
public static void main(String[] args) throws IOException {
//监听端口号
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("ServerA started...");
while(true){
Socket socket = serverSocket.accept(); //阻塞监听
//子线程,去网卡拿数据
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
public static void handler(Socket socket) throws IOException {
//读取数据
InputStream inputStream = socket.getInputStream();
//将 01010这样的bit信息 转换为 字符数据
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String str = reader.readLine();
System.out.println(str);
}
/**
* 客户端
*/
public class ServerB {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8080);
System.out.println("ServerB started...");
Scanner scanner = new Scanner(System.in);
String input = scanner.next();
//获取输出流对象
OutputStream outputStream = socket.getOutputStream();
//将字符数据转化为bit信息
PrintWriter writer = new PrintWriter(outputStream);
writer.println(input);
writer.flush();
socket.close();
}
}
这样的设计有一个问题就是:服务器每次处理来自客户端的数据都必须新建一个线程,如果客户端B一直连接但不是发送数据,那么相应的子线程就会一直处于阻塞状态。
因为这是一个BIO,所以需要在服务端用while来防止单个客户端阻塞线程
我们应该以byte[]数组的方式收发数据而不是用bit数据流是因为byte[]数组有规整的长度(8bit)分割便于解析,而bit是无法分割的
二、仿写Tomcat
我们在仿写Tomcat的时候需要从Tomcat的运行思路看,总共分分为两个阶段:启动阶段和访问阶段
启动阶段
- 首先我们需要扫描servlet文件获取文件全路径名
- 再利用反射的方式获取@WebServlet()当中的地址信息
tips:
- Tomcat本身就是servlet容器
- 以下是servlet的概念
- key值是地址信息,value值是通过全路径名获取类对象,把以上放入到HashMap集合当中
主类HttpServlet中的主要方法包含doGet、doPost剩下不重要的还有doDelete,doHead,doOption,doPut,doTrace等等
访问阶段
- 利用socket获取http请求信息(请求信息就比如headers之类的)
- 创建HttpServletRequest接口,将http请求信息进行拆分,将请求信息和请求方式放入到实现类当中
- 通过请求地址判断有没有这个servlet对象
- 有的话,获取类对象,生成servlet对象——>判断请求方式,分为doGet()和doPost()请求,分别返回各自的方法数据
- 没有的话,按照路径查询有无静态资源——>如果有,返回页面;如果没有,返回404
三、主要代码
HttpServlet主类和Request、Response接口类
public abstract class HttpServlet {
public abstract void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException;
public abstract void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException;
public void service(HttpServletRequest request,HttpServletResponse response) throws IOException {
if("GET".equals(request.getMethod())){
doGet(request,response);
}else if("POST".equals(request.getMethod())){
doPost(request,response);
}
}
}
public interface HttpServletRequest {
public String getMethod();
public void setMethod(String method);
}
public interface HttpServletResponse {
public void GetWriter(String context) throws IOException;
}
配置类:
public class ServletConfig {
private String urlMapping; //url
private String classPath; //类的全路径名
public ServletConfig(String urlMapping,String classPath){
this.urlMapping = urlMapping;
this.classPath = classPath;
}
public String getUrlMapping() {
return urlMapping;
}
public void setUrlMapping(String urlMapping) {
this.urlMapping = urlMapping;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
@Override
public String toString() {
return "ServletConfig{" +
"urlMapping='" + urlMapping + '\'' +
", classPath='" + classPath + '\'' +
'}';
}
}
public class ServletConfigMapping {
//定义servlet容器
public static Map<String,Class<HttpServlet>> classMap = new HashMap<>();
//将每一个自定义servlet类的信息放入ServletConfig中
private static List<ServletConfig> configs = new ArrayList<>();
static {
//获取用户自定义的servlet全路径名
List<String> classPaths = SearchClassUtil.searchClass();
for (String classPath:classPaths) {
try {
getMessage(classPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//使用反射获取注解信息
public static void getMessage(String classPath) throws Exception {
Class clazz = Class.forName(classPath);
//获取注解信息
WebServlet webServlet = (WebServlet) clazz.getDeclaredAnnotation(WebServlet.class);
//需要将解析的信息加载到configs集合当中(保证url和类对象是相同的)
configs.add(new ServletConfig(webServlet.urlMapping(), classPath));
}
//将url和类对象放入到servlet容器
public static void initServlet() throws ClassNotFoundException {
for (ServletConfig config:configs) {
classMap.put(config.getUrlMapping(), (Class<HttpServlet>) Class.forName(config.getClassPath()));
}
}
}
MyTomcat
public class MyTomcat {
Request request = new Request();
//获取类对象生成servlet对象
public void dispatch(Request request,Response response) throws Exception {
Class<HttpServlet> servletClass = ServletConfigMapping.classMap.get(request.getUrl());
if (servletClass != null){
HttpServlet servlet = servletClass.newInstance(); //生成当前servlet对象
servlet.service(request,response);
}
}
//启动tomcat的主方法
public void startUp() throws Exception {
//加载servlet信息
ServletConfigMapping.initServlet(); //启动阶段
//定义ServerSocket8080端口
ServerSocket serverSocket = new ServerSocket(8080);
while (true){
Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(socket);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
//解析用户请求数据
public void handler(Socket socket) throws Exception {
InputStream inputStream = socket.getInputStream();
getInput(inputStream);//解析Http请求
//获取输出通道、获取输出流
Response response = new Response(socket.getOutputStream());
if (request.getUrl().equals("")){
//返回404
response.GetWriter(ResponseUtil.getResponseHeader404());
}else if (ServletConfigMapping.classMap.get(request.getUrl()) == null){
//访问的是静态资源
response.GetWriterHtml(request.getUrl());
}else {
//访问的是动态资源
dispatch(request,response);
}
}
//解析Http请求
public void getInput(InputStream inputStream) throws IOException {
int count = 0;
while(count == 0){
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String content = new String(bytes);
System.out.println(content);
if(content.equals("")){
System.out.println("你有一个空请求");
}else{
String firstLine = content.split("\\n")[0];
request.setMethod(firstLine.split("\\s")[0]);
request.setUrl(firstLine.split("\\s")[1]);
}
}
public static void main(String[] args) throws Exception {
MyTomcat myTomcat = new MyTomcat();
myTomcat.startUp();
}
}