最近学习Tomcat,源码过于繁杂,写出简单的内部实现有助于理解服务器的内部运行。
只是简单思想功能很不完备,望指导.码字不易,如果对你有帮助希望一键三连!!牛年发大财
1.要写服务器首先我们要了解什么是服务器.
- Web应用服务器:Web应用服务器能够运行服务器上的应用程序,并将结果返回给客户端浏览器;例如,Tomcat就是一种Web应用服务器;通常情况下,Web应用服务器兼具HTTP服务器的部分功能;
注意:Web应用需要HTTP服务器及Web应用服务器,因为不仅需要浏览信息,还需要运行应用程序;但是tomcat已兼具了http服务器的部分功能,所以运行web应用可以直接使用tomcat。
我的理解就是
简单来说服务器就是处理浏览器的请求并分配到对应的servlet进行处理的一种分配机制。利用不同servlet中对数据的处理来实现服务器和浏览器进行交互实现动态页面。
- 浏览器---------http协议---------服务器 http协议规定了发送数据的格式(报文格式) 浏览器中发送和读取的格式已固定 Web服务器需要能够接收和解析浏览器发来的符合http协议的请求报文,并调用对应的代码处理,再向浏览器发送符合http协议规范响应报文。
请求报文格式:
响应报文格式:
请求报文和响应报文就是浏览器和服务器进行数据交换的信息载体.
我把他比作信件。我觉得这个比喻再好不过了。
头文件就是信件的地址和邮政编码。通过邮政编码和地址来找到具体的人家来投递邮件,如果你不写地址和邮政编码那么就和你放报文不写请求行和状态行一样tomcat读取不到对应的信息
Tomcat就是做着同样的事情。就好比如果你写了一个不存在的地址和邮政编码,
那呢这封邮件一定不会发送出去得到的响应就是对应的404页面找不到。
报文实例
请求:
GET /mytest
HTTP/1.1
Host: www.cnblogs.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; zh-CN; rv:1.8.1) Gecko/20061010 Firefox/2.0 .......
响应:
HTTP/1.1 200 OK Content-Type:text/html;charset=utf-8
<html>
<body>
Hello http!
</body>
</html>
1.Myresquest文件
package http;
import java.io.*;
import java.lang.reflect.Method;
public class MyRequest {
private String method;
private String url;
public MyRequest(InputStream is){
//以因为要读报文的头文件第一行信息所以把字节输入流转换成字符输入流(因为字符流有可以读取一行的方法)
BufferedReader br = new BufferedReader(new InputStreamReader(is));
try {
String reqHead = br.readLine();
//浏览器发送求给服务器,读取第一行头文件,头文件是由空格分割的
String[] reqAll = reqHead.split(" ");
//获取到头文件的方法
method = reqAll[0];
//获取到头文件的访问服务
url = reqAll[1];
} catch (IOException e) {
e.printStackTrace();
}
}
//创建方法返回请求的方法字符串
public String getMethod() {
return method;
}
//创建方法返回访问的url字符
public String getUrl() {
return url;
}
}
2.MyResponse文件
package http;
import java.io.OutputStream;
import java.io.Writer;
public class MyResponse {
private OutputStream wirte;
//服务器像像迎头文件
public static final String resqHeader="HTTP/1.1 200 OK\r\n" +
"Content-Type:text/html;charset=utf-8\r\n" +
"\r\n";
//提供写出方法
public MyResponse(OutputStream os){
wirte = os;
}
//创建方法返回输出流
public OutputStream getWirte(){
return wirte;
}
}
3.MyServlet文件
package servlet;
import http.MyRequest;
import http.MyResponse;
//当一个类中有抽象方法时该类必须为为抽象类
public abstract class MyServlet {
public void service(MyRequest req, MyResponse resp){
//处理方法细分
if("GET".equals(req.getMethod())){
doGet(req,resp);
}else if("POST".equals(req.getMethod())){
doPost(req,resp);
}
}
//要让子类继承所以写成抽象方法
public abstract void doGet(MyRequest req, MyResponse resp);
public abstract void doPost(MyRequest req, MyResponse resp);
}
4.MyServer文件
package server;
import http.MyRequest;
import http.MyResponse;
import servlet.MyServlet;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class MyServer {
//服务器核心容器
static Map<String, MyServlet> myMapping = new HashMap<>();
//创建配置文件类
static Properties prop = new Properties();
/**
* 服务器初始化
* 读取配置文件 把映射关系加载到核心容器中(HashMap)
*
* 服务器启动
* ServerScoket 监听指定端口
* Socket 客户端发来的输入流(请求)和输出流(响应)
* 从核心容器中根据请求路径 找到对应的类执行
* 服务器允许多用户可多次访问
* 多线程
*
*/
public static void serverInit() {
try {
//加载配置文件
prop.load(MyServlet.class.getResourceAsStream("/myMapping.properties"));
//得到配置文件中的key集合
Set<Object> keySet = prop.keySet();
//循环key集合
for (Object key : keySet) {
//判断key集合中是否包含url
if (key.toString().contains("url")) {
//得到该行配置文件的value也就是对应的url地址
String url = prop.getProperty(key.toString());
//替换字符串得到key结尾是class的对象
String classKey = key.toString().replace("url", "class");
//得到key结尾是class文件对应的calue也就是就具体类的全类名(因为要用到类的反射所以是全类名)
String className = prop.getProperty(classKey);
try {
//通过反射创建类实例
MyServlet ms = (MyServlet) Class.forName(className).newInstance();
//把url和类对象放入到map中
myMapping.put(url, ms);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//开启服务器
public static void serverStart() {
try {
//创建socket对象设置端口号为8088
ServerSocket ss = new ServerSocket(8088);
System.out.println("服务器开启监听,8088端口已经准备好啦");
//设置为while循环可以使服务一直开启
while (true) {
//接收请求
Socket socket = ss.accept();
//创建新的进程
MyProcess myProcess = new MyProcess(socket);
//启动进程
myProcess.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//主方法调用程序运行
public static void main(String[] args) {
//调用服务器的初始化方法
MyServer.serverInit();
//调用服务器的开始方法
MyServer.serverStart();
}
}
5.MyProcess文件
package server;
import http.MyRequest;
import http.MyResponse;
import servlet.MyServlet;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class MyProcess extends Thread {
//创建socket对象
private Socket so;
//通过有参构造来实现socket赋值
public MyProcess(Socket socket){
this.so = socket;
}
@Override
public void run() {
//子线程做的事情
try {
MyRequest req = new MyRequest(so.getInputStream());
MyResponse resp = new MyResponse(so.getOutputStream());
//通过req方法获得url要访问地址
String urlstr = req.getUrl();
//在map中获取到对应的类
MyServlet myServlet = MyServer.myMapping.get(urlstr);
//判断类是否为空
if (myServlet != null) {
//不是空调用该类的service方法,该类继承自MyServlet所以有service方法
myServlet.service(req, resp);
} else {
//其他的直接返回错误
OutputStream wirte = resp.getWirte();
wirte.write(MyResponse.resqHeader.getBytes());
wirte.write("404 Not found!!".getBytes());
wirte.flush();
wirte.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.myMapping.properties配置文件
servlet.one.url=/login
servlet.one.class=servlet.LognServlet
servlet.two.url=/regist
servlet.two.class=servlet.RegistServlet
7.实现类LoginServlet文件
package servlet;
import http.MyRequest;
import http.MyResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
public class LognServlet extends MyServlet {
@Override
public void doGet(MyRequest req, MyResponse resp) {
OutputStream wirte = resp.getWirte();
try {
wirte.write(MyResponse.resqHeader.getBytes());
wirte.write("login success!!".getBytes());
wirte.flush();
wirte.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest req, MyResponse resp) {
doGet(req,resp);
}
}
8.实现类RegistServlet文件
package servlet;
import http.MyRequest;
import http.MyResponse;
import java.io.IOException;
import java.io.OutputStream;
public class RegistServlet extends MyServlet {
@Override
public void doGet(MyRequest req, MyResponse resp) {
OutputStream wirte = resp.getWirte();
try {
wirte.write(MyResponse.resqHeader.getBytes());
wirte.write("regist success".getBytes());
wirte.flush();
wirte.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest req, MyResponse resp) {
doGet(req,resp);
}
}
运行
先来分项目图分布图
在MySever中运行main方法,运行框程序显示如下
此刻打开历览器输入http://127.0.0.1:8088/loginu或者http://127.0.0.1:8088/regist页面显示如下
这两个访问地址对应的就是写的两个servlet如果你要添加新的servlet就继承MyServlet然后在配置文件中添加对应的配置文件就可以了
该应用下载网址.上我的资源里myWebService就是,也可以私信我要下载地址