1000行代码手写服务器

Posted 半月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1000行代码手写服务器相关的知识,希望对你有一定的参考价值。

1000行代码手写服务器

开发技术&开发环境和工具

使用技术

基于Java IO,多线程,Socket网络编程,XML解析,只引入Junit,dom4j(解析xml),熟悉javaweb基本应用。

环境参数

  • Java环境 JDK1.8 maven maven3.6
  • 开发工具 IDEA

环境搭建

pom.xml文件配置

只需在xml导入dom4j jar包

<dependencies>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>

手写服务器 整体架构,编写XML文件

配置解析XML文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app >
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.feng.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/log</url-pattern>
    </servlet-mapping>

</web-app>

POJO类

package com.feng.server;

public class Entity {  //servlet-name或每一个servlet-name所对应得实体类
    private String name;
    private String clazz;

    public Entity() {
    }

    public Entity(String name, String clazz) {
        this.name = name;
        this.clazz = clazz;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getClazz() {
        return clazz;
    }

    public void setClazz(String clazz) {
        this.clazz = clazz;
    }
}

Mapping类

package com.feng.server;

import java.util.ArrayList;
import java.util.List;

public class Mapping { //映射关系,多个路径可以访问资源
    private String name; //servlet-name
    private List<String> urlPattern;//url-pattern

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getUrlPattern() {
        return urlPattern;
    }

    public void setUrlPattern(List<String> urlPattern) {
        this.urlPattern = urlPattern;
    }

    public Mapping() {
        this.urlPattern = new ArrayList<String>();
    }

    public Mapping(String name, List<String> urlPattern) {
        this.name = name;
        this.urlPattern = urlPattern;
    }
}

配置dom4j

package com.feng.server;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class WebDom4j { //用于解析xml
    private List<Entity> entityList; //用于储存实体类,每一个实体类一个servlet-name对应一个servlet-class
    private List<Mapping> mappingList;//用于储存映射类,每一个servlet-name对应一个url-pattern
public List<Entity> getEntityList() {
    return entityList;
}

public void setEntityList(List<Entity> entityList) {
    this.entityList = entityList;
}

public List<Mapping> getMappingList() {
    return mappingList;
}

public void setMappingList(List<Mapping> mappingList) {
    this.mappingList = mappingList;
}

//构造方法
public WebDom4j() {
    entityList = new ArrayList<Entity>();
    mappingList = new ArrayList<Mapping>();
}

//获取Document对象的方法
public Document getDocument(){

    try {
        //创建SAXReader对象
        SAXReader reader = new SAXReader();
        //调用read方法
        return reader.read(new File("web/WEB-INF/web.xml"));
    } catch (DocumentException e) {
        e.printStackTrace();
    }
    return null;
}
//获取元素
public void parse(Document doc) {
    //1获取根元素
    Element root = doc.getRootElement(); //web-app
    //2获取servlet子元素
    for (Iterator<Element> ite = root.elementIterator("servlet"); ite.hasNext();) {
        Element subElement = ite.next(); //得到每一个servlet
        //创建一个实体类
        Entity ent = new Entity(); //用于储存servlet-name与servlet-class
        for (Iterator<Element> subIte = subElement.elementIterator(); subIte.hasNext();) {
            Element ele = subIte.next(); //servlet-name与servlet-class都有可能
            if ("servlet-name".equals(ele.getName())) {
                ent.setName(ele.getText());
            } else if ("servlet-class".equals(ele.getName())) {
                ent.setClazz(ele.getText());
            }
        }
        entityList.add(ent);//放到集合中
    }
    //测试
   /* for (Entity entity : entityList) {
        System.out.println(entity.getName() + "\\t" + entity.getClazz());
    }*/

    //解析servlet-mapping

    for (Iterator<Element> ite = root.elementIterator("servlet-mapping"); ite.hasNext();){
        Element subEle = ite.next();//得到每一个servlet-mapping
        //创建一个mapping对象
        Mapping map = new Mapping();
        //解析mapping下面每一个子元素
        for (Iterator<Element> subIte = subEle.elementIterator(); subIte.hasNext();){
            Element ele = subIte.next();//可能是name,也可能是url
            if ("servlet-name".equals(ele.getName())) {
                map.setName(ele.getText());
            } else if ("url-pattern".equals(ele.getName())) {
                //获取集合对象,调用集合对象的添加方法,添加元素
                map.getUrlPattern().add(ele.getText());
            }
        }
        mappingList.add(map);

    }
    //测试
    /*for (Mapping m : mappingList) {
        System.out.println(m.getName());
        for (String s : m.getUrlPattern()) {
            System.out.println(s);
        }
    }*/
}

//用于测试
    public static void main(String[] args) {
        WebDom4j web = new WebDom4j();
        web.parse(web.getDocument());
    }
}

servlet和servlet-mapping解析结果

更新编写XML

先更新编写才得出上面的结果

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.feng.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/log</url-pattern>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>register</servlet-name>
        <servlet-class>com.feng.servlet.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>register</servlet-name>
        <url-pattern>/reg</url-pattern>
        <url-pattern>/regis</url-pattern>
        <url-pattern>/register</url-pattern>
    </servlet-mapping>
   
</web-app>

编写servletContext Servlet 上下文,容器用于存储映射关系

package com.feng.server;

import java.util.HashMap;
import java.util.Map;

/**
*ServletContext 上下文 就是一个容器

*/
public class ServletContext { //Entity与Mapping之间的映射关系
     private Map<String, String> servlet;//key是servlet-name(实体类中的name),值servlet-class,实体类中的值
     private Map<String, String> mapping;//key是url-pattern(Map中的每一个元素),值servlet-name,实体类中的name

 public Map<String, String> getServlet() {
     return servlet;
 }

 public void setServlet(Map<String, String> servlet) {
     this.servlet = servlet;
 }

 public Map<String, String> getMapping() {
     return mapping;
 }

 public void setMapping(Map<String, String> mapping) {
     this.mapping = mapping;
 }

 public ServletContext(){
     servlet = new HashMap<String, String>();
     mapping = new HashMap<String, String>();
 }
}

编写WebAPP 初始化程序数据

package com.feng.server;

import com.feng.servlet.Servlet;

import java.util.List;
import java.util.Map;

public class WebApp {  //应用程序
    private static ServletContext context;
    static {
        context = new ServletContext();
        //分别获取对应关系的Map集合
        Map<String, String> servlet = context.getServlet();
        Map<String, String> mapping = context.getMapping();
        //创建解析XML文件对象
        WebDom4j web = new WebDom4j();
        //解析xml
        web.parse(web.getDocument());
        //获取解析xml之后的List集合
        List<Entity> entityList = web.getEntityList();
        List<Mapping> mappingList = web.getMappingList();

        //将List集合储存到map集合中
        for (Entity entity : entityList) {
            servlet.put(entity.getName(),entity.getClazz());
        }
        System.out.println(servlet);
        for (Mapping map : mappingList) {
            //遍历url-pattern的集合
            List<String> urlPattern = map.getUrlPattern();
            for (String s : urlPattern) {
                mapping.put(s,map.getName());
            }
        }
        System.out.println(mapping);
    }

   
    public static void main(String[] args) {
       WebDom4j web = new WebDom4j();
        web.parse(web.getDocument());
    }
}

获取解析xml集合的结果

创建不同的Servlet对象

/*
     * 根据url创建不同的Servlet对象
     * */
    public static Servlet getServlet(String url){
        if (url ==null||url.trim().equals("")){
            return null;
        }
        try {
            //如果url正确
            //根据url的key获取servlet-name的值
            String servletName = context.getMapping().get(url);
            //根据servlet-name找到对应得servlet-class
            String servletClass = context.getServlet().get(servletName);
            //得到的是一个完整的包+类名的字符串

            //使用反射创建servlet对象
            Class<?> clazz = Class.forName(servletClass);
            Servlet servlet = (Servlet) clazz.newInstance();
            return servlet;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

在WebApp类调用getServlet方法得到如下结果:

编写服务(启动或关闭服务)

public class Server { //服务器,用于启动或停止服务
    private ServerSocket server;

    public static void main(String[] args) {
        Server server = new Server();//创建服务器
        server.start();
    }

    private void start() {
        this.start(8888);
    }

    public void start(int port){
        try {
            server = new ServerSocket(port);
            this.receive();//调用接受请求的方式
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void receive() {

        try {
          
                //1监听
                Socket client = server.accept();
                  
            //获取用户的请求
            InputStream is = client.getInputStream();
            byte[] buf = new byte[20480];
            int len = is.read(buf);
            System.out.println(new String(buf, 0, len));
          
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public void stop(){

    }
}

在浏览器访问localhost:8888,控制台会出现如下结果:

编写login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登入</title>
</head>
<body>
    <form action="http://localhost:8888/log" method="post">
        <p>用户名:<input type="text" name="username" id="username"></p>
        <p>密码:<input type="password" name="pwd" id="password"></p>
        <p>
            爱好:<input type="checkbox" name="hobby" value="ball"/>足球
                 <input type="checkbox" name="hobby" value="read"/>读书
                <input type="checkbox" name="hobby" value="paint"/>画画
        </p>
        <p><input type="submit" value="登入"></p>
    </form>
</body>
</html>

封装Request


import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.PrivateKey;
import java.util.*;

public class Request { //请求

    //输入流
    private InputStream is;
    private String requestInfo; //请求字符串,请求方式,请求路径,参数,协议,协议版本,请求正文。。
    private String method;//请求的方式
    private String url;//请求的路径

    public String getUrl() {
        return url;
    }

    /*
    * 输入框的name为key,值为value
    * */
    private Map<String, List<String>> parameterMapValues;//参数
    private static final String CRLF="\\r\\n";//换行
    private static final String BLANK =" ";//空格
    //构造方法,初始化属性
    public Request(){
        parameterMapValues=new HashMap<String, List<String>>();
        method="";
        url="";
        requestInfo="";
    }
    public Request(InputStream is){
        this();//调用本类无参构造方法
        this.is=is;

        try {
            byte[] buf = new byte[20480];
            int len = this.is.read(buf);
            requestInfo = new String(buf, 0, len);
        } catch (IOException e) {
            return;
        }
        //调用本类中的分解信息的方法
        this.parseRequestInfo();
    }
    //分解请求信息的方法
    /**
    * 请求方式
    * 请求路径
    * 请求的参数
    * */
    public void parseRequestInfo() {
        String paraString="";//用于存储请求参数
        //获取参数的第一行
        String firstLine = requestInfo.substring(0, requestInfo.indexOf(CRLF)).trim();//从0开始到第一个换行的位置
        //分解出请求方式
        int index = firstLine.indexOf("/");
        this.method = firstLine.substring(0, index).trim();
        //分解url ,get可能包含的参数,也可能不包含的参数post
        String urlString = firstLine.substring(index, firstLine.indexOf("HTTP/")).trim();
        //判断请求方式是GET或POST
        if ("get".equalsIgnoreCase(this.method)) {   //包含请求参数
            if (urlString.contains("?")){
                String[] urlArray = urlString.split("\\\\?");
                this.url=urlArray[0];
                paraString=urlArray[1];
            }else {
                this.url=urlString;
            }
        }else {//post不包含请求参数
            this.url=urlString;
            paraString=requestInfo.substring(requestInfo.lastIndexOf(CRLF)).trim();
         }
        if (paraString.equals("")){
            return;
        }
        //请求参数
        System.out.println(paraString);
       
    }
    //用于测试
    public void show(){
        System.out.println(this.url);
        System.out.println(this.method);
    }

  
}

成功封装后启动服务,控制台输出如下:

储存参数,处理中文

/**
   *username = admin
    * pwd = 123
    * hobby =ball
    * hobby = paint
    * */
   public void parseParam(String prarString){
       String[] token = prarString.split("&");
       for (int i=0;i<token.length;i++){
           String keyValues = token[i];
           String[] keyValue = keyValues.split("=");//把=分割掉,得到K和V
           if (keyValue.length==1){   //username=
               keyValue = Arrays.copyOf(keyValue, 2);
               keyValue[1] = null;
           }
           //将表单中的元素的name与name对应的值储存到Map集合
           String key = keyValue[0].trim();
           String value = keyValue[1]==null?null:decode(keyValue[1].trim(),"utf-8");
           //放到集合中存储
           if (!parameterMapValues.containsKey(key)) {  //键不存在就创建
               parameterMapValues.put(key,new ArrayList<String>());
           }
           List<String> values = parameterMapValues.get(key);
           values.add(value); //创建一个集合添加值
       }

   }
    //根剧表单元素的name获取多个值
    public String [] getParamterValues(String name) {
       //根据key获取value
        List<String> values = parameterMapValues.get(name);
        if (values == null ){
            return null;
        } else {
            return values.toArray(new String[0] );
        }
    }
    public String getParamter(String name){
       //调用本类中根据name获取单个值的方法
        String[] values = this.getParamterValues(name);
        if (values ==null){
            return null;
        } else {
            return values[0];
        }
    }
     //处理中文,因浏览器对中文进行了编码,进行解码
        private String decode(String value,String code){
            try {
                return URLDecoder.decode(value,code);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }
   

    public static void main(String[] args) {
        Request req = new Request();
        //调用分解参数的方法
        req.parseParam("username=中文加密&pwd=123&hobby=ball&hobby=read");
        System.out.println(req.parameterMapValues);

        //调用获取多个值的方法
        String[] str = req.getParamterValues("hobby");
        for (String string : str) {
            System.out.println(string);
        }
        //调用单个获取值的方法
        System.out.println(req.getParamter("pwd"));
    }

备注:中文参数加密(在百度上随便搜索,截取wd后面的参数)例如:苹果举例

https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E8%8B%B9%E6%9E%9C&fenlei=256&rsv_pq=b162262500064c7f&rsv_t=3f02zT3Tvav%2FKayD%2BwKuGWxcDuG3ZnH0aQoblznxlDr2LxisTdsiuUCkLbE&rqlang=cn&rsv_enter=1&rsv_dl=tb&rsv_sug3=13&rsv_sug1=17&rsv_sug7=101&rsv_sug2=0&rsv_btype=i&inputT=13852&rsv_sug4=43101

%E8%8B%B9%E6%9E%9C(这个就是加密参数)

封装之后结果如下:

加到Server类中,测试响应

  //封装请求信息
            Request req = new Request(client.getInputStream());
           
            /**
             * 做出响应
             * */
            StringBuilder sb = new StringBuilder();
            sb.append("HTTP/1.1").append(" ").append(200).append(" ").append("OK").append("\\r\\n");
            sb.append("Content-Type:text/html;charset=utf-8").append("\\r\\n");
            //内容
            String str = "<html><head><title>响应结果</title></head><body>成功</body></html>";
            sb.append("Content-Length:"+str.getBytes("utf-8").length).append("\\r\\n");
            sb.append("\\r\\n");
            sb.append(str);

            //通过输出流发送出去
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
            bw.write(sb.toString());
            bw.flush();
            bw.close();

模拟响应结果,测试浏览器是否在登入之后出现成功状态

编写Response

package com.feng.server;
import com.feng.util.IOCloseUtil;
import java.io.*;

public class Response { //响应
    private StringBuilder headInfo;  //响应头
    private StringBuilder content; //响应内容
    private int length; //响应内容的长度

    //流
    private BufferedWriter bw;

    //两个常量,换行和空格
    private static final String CRLF="\\r\\n";  //换行
    private static final String BLANK=" ";  //空格

    //构造方法
    public Response() {
        headInfo= new StringBuilder();
        content = new StringBuilder();
    }
    //带构造方法

    public Response(OutputStream os) {
        this();//调用本类的无参构造方法
        try {
            bw = new BufferedWriter(new OutputStreamWriter(os,"utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    //构造正文部分
    public Response print(String info){
        content.append(info);
        try {
            length+= info.getBytes("utf-8").length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return this;
    }
    public Response printLn(String info){
        content.append(info).append(CRLF);
        try {
            length+=(info+CRLF).getBytes("utf-8").length;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return this;
    }

    //构造响应头
    private void createHeadInfo(int code){
        headInfo.append("HTTP/1.1").append(BLANK).append(code).append(BLANK);
        switch (code){
            case 200:
                headInfo.append("OK");
                break;
            case 500:
                headInfo.append("SERVER ERROR");
            default:
                headInfo.append("NOT FOUND");
                break;
        }
        headInfo.append(CRLF);
        headInfo.append("Content-Type:text/html;charset=utf-8").append(CRLF);
        headInfo.append("Content-Length:"+length).append(CRLF);
        headInfo.append(CRLF);
    }

    /**
     * 推送到客户机的浏览器
     * */
    public void pushToClient(int code){
        if (headInfo==null){
            code=500;
        }
        //调用本类中的构造响应头
        this.createHeadInfo(code);
        try {
            bw.write(headInfo.toString());
            bw.write(content.toString());
            bw.flush();
            this.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void close() {
        IOCloseUtil.closeAll(bw);
    }
}

然后再把Server里面做出响应那段代码注释掉

package com.feng.server;

import com.feng.servlet.Servlet;
import com.feng.util.IOCloseUtil;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server { //服务器,用于启动或停止服务
    private ServerSocket server;
  
    public static void main(String[] args) {
        Server server = new Server();//创建服务器
        server.start();
    }

    private void start() {
        this.start(8888);
    }

    public void start(int port){
        try {
            server = new ServerSocket(port);
            this.receive();//调用接受请求的方式
        } catch (IOException e) {
             e.printStackTrace();
        }
    }

    private void receive() {

        try {
            //1监听
            Socket client = server.accept();
            //创建线程类
            Dispatcher dis = new Dispatcher(client);
            //创建代理线程
            new Thread(dis).start();
            
          
           //封装请求信息
            Request req = new Request(client.getInputStream());
            //req.show();
            /**
             * 做出响应
             * */
            /*StringBuilder sb = new StringBuilder();
            sb.append("HTTP/1.1").append(" ").append(200).append(" ").append("OK").append("\\r\\n");
            sb.append("Content-Type:text/html;charset=utf-8").append("\\r\\n");
            //内容
            String str = "<html><head><title>响应结果</title></head><body>成功</body></html>";
            sb.append("Content-Length:"+str.getBytes("utf-8").length).append("\\r\\n");
            sb.append("\\r\\n");
            sb.append(str);

            //通过输出流发送出去
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
            bw.write(sb.toString());
            bw.flush();
            bw.close();*/
            Response rep = new Response(client.getOutputStream());
            Servlet servlet = WebApp.getServlet(req.getUrl());
            int code=200;
            if (servlet ==null){
                code=404;
            }
            //调用Servlet中的服务方法
            try {
                servlet.service(req,rep);
            } catch (Exception e) {
                e.printStackTrace();
            }
            rep.pushToClient(code);
            IOCloseUtil.closeAll(client);  //单线程已被删除
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public void stop(){

    }
}

封装Response

编写servlet

public abstract class Servlet { //是所有请求servlet的父类
    public void service(Request req, Response rep) throws Exception {
        this.deGet(req,rep);
        this.doPost(req,rep);
    }

    public abstract void deGet(Request req, Response rep) throws Exception;
    public abstract void doPost(Request req, Response rep) throws Exception;

}

编写LoginServlet

public class LoginServlet extends Servlet {
    @Override
    public void deGet(Request req, Response rep) throws Exception {
        //获取请求参数
        String name = req.getParamter("username");
        String pwd = req.getParamter("pwd");

        if (this.login(name,pwd)) {
            //调用响应中的构建内容的方法
            rep.printLn(name+"登入成功");
        }else {
            rep.printLn(name+"登录失败,对不起,账号和密码不准确");
        }
    }
    private boolean login(String name,String pwd){
        if ("admin".equals(name)&&"123".equals(pwd)) {
            return true;
        }
        return false;
    }

    @Override
    public void doPost(Request req, Response rep) throws Exception {

    }
}

在Server创建Response对象,用Servlet调用service方法,请求和响应之后并推送到客服端,用户名和密码正确的话如图所示:

如果用户名和密码不匹配的话:

封装分发器,实现多线程(Dispatcher)

package com.feng.server;

import com.feng.servlet.Servlet;
import com.feng.util.IOCloseUtil;

import java.io.IOException;
import java.net.Socket;

/**
 * 一个请求与响应就是一个Dispatcher
 *
 * @author asus
 * */
public class Dispatcher implements Runnable {
    private Request req;
    private Response rep;
    private Socket client;
    private int code =200;//状态码

    //构造方法初始化属性

    public Dispatcher(Socket client) {
        this.client = client;
        try {
            req=new Request(this.client.getInputStream());
            rep=new Response(this.client.getOutputStream());
        } catch (IOException e) {
            code=500;
            return;
        }

    }

    @Override
    public void run() {
        //根据不同的url创建指定的servlet对象
        Servlet servlet = WebApp.getServlet(req.getUrl());
        if (servlet==null){
            this.code=404;
        }else {
            //调用响应的servlet中service方法
            try {
                servlet.service(req,rep);
            } catch (Exception e) {
                this.code=500;
            }
        }
        //将有响应的结果推送到客户端的浏览器
        rep.pushToClient(code);
        IOCloseUtil.closeAll(client);
    }
}

实现多线程

这是修改后的服务器主线程类

public class Server { //服务器,用于启动或停止服务
    private ServerSocket server;
    private boolean isShutDown=false; //默认没有出错

    public static void main(String[] args) {
        Server server = new Server();//创建服务器
        server.start();
    }

    private void start() {
        this.start(8888);
    }

    public void start(int port){
        try {
            server = new ServerSocket(port);
            this.receive();//调用接受请求的方式
        } catch (IOException e) {
            isShutDown=true;
        }
    }

    private void receive() {

        try {
            while (!isShutDown){
                //1监听
                Socket client = server.accept();
                //创建线程类
                Dispatcher dis = new Dispatcher(client);
                //创建代理线程
                new Thread(dis).start();
            }
           
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    public void stop(){
	isShutDown=true;
	IOCloseUtil.closeAll(server);
    }
}

但是这样还没有成功,控制台还会出现bug,虽然服务器没有停止,可能会出现潜在的风险,控制台出现空指针异常:

修改空指针异常

为了更好查看bug,我在Dispatcher类中的run方法里面添加了一个测试路径

System.out.println(req.getUrl());    //测试

控制台会出现一个新增的类:

缺少favicon映射类,在xml里面配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.feng.servlet.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/log</url-pattern>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>register</servlet-name>
        <servlet-class>com.feng.servlet.RegisterServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>register</servlet-name>
        <url-pattern>/reg</url-pattern>
        <url-pattern>/regis</url-pattern>
        <url-pattern>/register</url-pattern>
    </servlet-mapping>
    <servlet>
        <servlet-name>favicon</servlet-name>
        <servlet-class>com.feng.servlet.FaviconServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>favicon</servlet-name>
        <url-pattern>/favicon.ico</url-pattern>
    </servlet-mapping>

</web-app>

添加FaviconServlet

package com.feng.servlet;

import com.feng.server.Request;
import com.feng.server.Response;

public class FaviconServlet extends Servlet {
    @Override
    public void deGet(Request req, Response rep) throws Exception {

    }

    @Override
    public void doPost(Request req, Response rep) throws Exception {

    }
}

最后运行成功,控制台没有出现bug

避坑bug经验

return reader.read(new File("web/WEB-INF/web.xml"));

假如要配置正确的路径格式,那么先从根目录下开始写相对路径,当路径格式配置有问题时,例如WEB-INF看错了可能写成WEB_INF,控制台马上会出现如下错误:

以上是关于1000行代码手写服务器的主要内容,如果未能解决你的问题,请参考以下文章

手写数字识别——基于全连接层和MNIST数据集

前端面试题之手写promise

手写RPC框架-重写服务治理和引入netty,开启1000个线程看看执行调用情况

300行代码手写SpringMVC

程序员面试被要求手写代码,手写代码真的行的通吗?

Tensorflow快餐教程 - 30行代码搞定手写识别