Java | Socket + 自定义线程池 实现Web服务器(仿Servlet)

1,593 阅读4分钟

前言

基于java Scoket的TCP协议 简单实现http web服务器,使用自定义线程池去处理每一个请求,用浏览器当作客户端,达到javaWeb中类似于访问Servlet的效果。 (对http协议和Servlet要有一定了解)

执行效果

  • http服务器端

客户端 (login.html): 在这里插入图片描述 登陆后: 测试账号:zjl 123456 登陆结果

思路

  • 浏览器端:是一个html的表单,输入姓名密码后点击登陆即可,访问服务器地址为localhost:8080/login,然后会自动连接服务器并发送http协议的请求消息。
  • 服务器:服务器每次接收到一个请求后,交给自定义线程池去处理,会把接收到的请求进行解析,在请求消息中解析出客户端欲请求的资源(这里是/login),请求方式(get或者post),和请求参数,然后封装到Request实体中。然后就要根据请求资源来获得对应的Servlet了,请求资源和Servlet的全路径类名的映射关系放在web.xml这个文件,所以利用dom4j解析web.xml文件即可得到Servlet的全路径类名,再利用反射即可创建出此servlet的实例。有了Servlet的实例后调用Servlet实例的service方法并把封装后的Request对象和Reponse对象作为参数传入即可,这样就执行了Servlet的service方法,再用reponse调用print方法即可向客户端发送响应消息。

思路图形化

代码目录结构

在这里插入图片描述

主要代码为下面几个

web.xml配置

  • 自定义xml文件,主要配置请求url和负责处理这个请求的Servlet实体类的映射关系,每新增一个Servlet都需要在这里配置
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>demo.ServletImpl.LoginServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
</web-app>

入口startServer代码为

  • 相当于服务器负责接收每一个来自客户端的请求,然后放到线程池去处理这次请求
public class startServer {

    private static RequestThreadPool<ServerThread> requestThreadPool = new RequestThreadPool<>();

    public static void main(String[] args) throws Exception {
        ServerSocket server   = new ServerSocket(8080);
        System.out.println("http服务器启动成功....");

        //多线程处理每个请求
        while(true){
            Socket client = server.accept();  //阻塞式等待接收一个请求
           // new ServerThread(client).start();   旧版
            requestThreadPool.execute(new ServerThread(client));
        }
    }

    /**
     *    服务器处理浏览器请求线程
     */
    static class ServerThread extends Thread{

        private Request request;  //请求
        private Response reponse;   //响应
        private Socket client;

        //初始化request,reponse
        public ServerThread(Socket client) {
            try {
                this.client = client;
                request = new Request(client.getInputStream());
                reponse = new Response(client.getOutputStream());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            try {
                System.out.println(client.getRemoteSocketAddress()+" 发出请求");

                //浏览器会默认请求网站图标资源,我们这里忽略掉这个请求
                if (request.getUrl().equals("/favicon.ico"))
                    return;

                //1-根据请求的url获得Servlet
                Servlet servlet  = ServletFactory.getServlet(request.getUrl());

                //请求资源不存在404
                if (servlet == null){
                    reponse.setCode(404);
                    reponse.print("");
                }

                //2-执行Servlet
                if (servlet != null){
                    servlet.service(request,reponse);
                }


            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                try {
                    client.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

自定义线程池RequestThreadPool代码

  • 所谓的自定义线程池,不过就是里面有一个放着待处理请求(这里的任务具体指一次请求)的集合队列, 然后事先创建好n个线程去消费集合任务队列,外部每次调用execute方法都会把待处理任务添加到任务队列,然后随机唤醒一个消费线程去处理任务
package demo3.util;

import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 *      自定义处理请求的线程池
 *
 *
 *
 */
public class RequestThreadPool<Job extends Runnable>  {
    //任务列表 (线程池)
    private final LinkedList<Job> jobsList = new LinkedList<>();

    //工作线程队列
    private final List<MyWorker> workerList = Collections.synchronizedList(new ArrayList<MyWorker>());

    //默认工作者线程数量
    private static final int DEFAULT_WORKER_NUMBERS = 5;

    //工作者编号生成序号
    private AtomicLong threadNum = new AtomicLong();


    // 构造方法
    public RequestThreadPool(){
        initWorkerThreadByNum(DEFAULT_WORKER_NUMBERS);

    }
    public RequestThreadPool(int workerNum){
        initWorkerThreadByNum(workerNum);
    }

    public void initWorkerThreadByNum(int workerNum){
        for (int i = 0; i < workerNum; i++) {
            MyWorker worker =  new MyWorker();
            workerList.add(worker);

            //工作线程开始消费任务
            new Thread (worker, "ThreadPool-Worker-"+ threadNum.incrementAndGet()).start();
        }
    }


    //把任务交给线程池,之后工作线程回去消费它
    public void execute(Job job) {
            if (job != null){
                synchronized (jobsList){
                    jobsList.addLast(job);
                    System.out.println("剩余待处理请求个数:"+RequestThreadPool.this.getJobsize());
                    jobsList.notify();  //随机唤醒在此jobsList锁上等待的工作者线程
                }
            }
    }

    //关闭所有的工作者线程
    public void shutdown() {
        for (MyWorker e : workerList) {
            e.shutdown();
        }
    }
    
    //获取剩余任务个数
    public int getJobsize() {
        return jobsList.size();
    }

    
    /**
     *      工作线程,消费任务
     */
    private class MyWorker implements Runnable{
        //是否工作
        private volatile boolean isRunning = true;

        @Override
        public void run() {
            while(isRunning){
                Job job = null;

                //同步获取任务
                synchronized(jobsList){
                    //如果任务列表为空就等待
                    while(jobsList.isEmpty()){
                        try {
                            jobsList.wait();
                        } catch (InterruptedException e) {
                            //感知到被中断就退出
                            return;
                        }
                    }
                    //获取任务
                    job = jobsList.removeFirst();
                }

                //执行任务
                if (job != null){
                    System.out.println("正在处理请求");
                    job.run();
                    System.out.println("处理完成,剩余待处理请求个数:"+RequestThreadPool.this.getJobsize());

                }

            }
        }

        //关闭线程
        public  void shutdown(){
            isRunning = false;
        }
    }
}

ServletFactory

  • 下面web.xml文件的路径xmlpath需根据自己电脑环境设置否则会找不到该文件
  • 用于根据请求url获取对应Servlet实体类
/**
 *   Servlet工厂
 *
 *          根据url和xml文件创建Servlet
 *
 *
 */
public class ServletFactory {

    //Servlet上下文环境
    private static ServletContext context = new ServletContext();

    //web.xml文件路径
    private static String xmlpath = "http服务器/src/demo/web.xml";

    private ServletFactory(){}

    /**
     *      读取web.xml文件把servlet和url的关系进行配置存储
     */
    static {
        try {
            //1-获得doucument
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(new File(xmlpath));

            //2-获得根元素      <web-app>
            Element rootElement = document.getRootElement();

            //3-获得所有子元素
            List<Element> elements = rootElement.elements();

            //4-遍历处理所有子元素
            for (Element e : elements) {
                if ("servlet-mapping".equals(e.getName())) {
                    Element servlet_name = e.element("servlet-name");
                    Element url_pattern = e.element("url-pattern");
                    context.getUrl_map().put(url_pattern.getText(),servlet_name.getText());
                }
                else if ("servlet".equals(e.getName())) {
                    Element servlet_name = e.element("servlet-name");
                    Element servlet_class = e.element("servlet-class");
                    context.getServlet_map().put(servlet_name.getText(),servlet_class.getText());
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }


    /**
     *  获得Servlet
     */
    public static synchronized Servlet getServlet(String url) throws Exception {
        String servletClass = context.getServlet_map().get(context.getUrl_map().get(url));

        if (servletClass != null)
            return (Servlet)Class.forName(servletClass).newInstance();
        else
            return null;
    }
}

ServletContext上下文环境

  • 存储所有的请求和Servlet映射关系
/**
 *   Servlet的上下文环境
 */
public class ServletContext {

    //Servlet别名和Servlet全路径类名的映射关系
    private  Map<String,String>  servlet_map;

    //url和 Servlet别名的映射关系
    private  Map<String,String> url_map;

    public ServletContext() {
        servlet_map = new HashMap<>();
        url_map = new HashMap<>();
    }

    public Map<String, String> getServlet_map() {
        return servlet_map;
    }


    public Map<String, String> getUrl_map() {
        return url_map;
    }
}

Servlet抽象类代码

  • Servlet顶层接口,用户需自定义继承它实现自己的Servlet
/**
 *   Servlet抽象类
 */
public abstract class Servlet {

    public void service(Request request,Response reponse) throws Exception {
        this.doGet(request,reponse);
        this.doPost(request,reponse);
    }


    public abstract void doGet(Request request,Response reponse) throws Exception;
    public abstract void doPost(Request request,Response reponse) throws Exception;

}

LoginServlet代码

  • 自定义Servlet,负责处理登陆请求
import demo.domain.Request;
import demo.domain.Response;
import demo.domain.Servlet;
public class LoginServlet extends Servlet {
    @Override
    public void doGet(Request request, Response reponse) throws Exception {

        String name = request.getParameter("name");
        String password = request.getParameter("password");

        if (name!= null && password !=null && name.equals("zjl") && password.equals("123456"))
            reponse.print("登陆成功!");
        else
            reponse.print("登陆失败!");
    }

    @Override
    public void doPost(Request request, Response reponse) throws Exception {
        doGet(request,reponse);
    }
}

  • 完成上述之后,现在你可以实现自己自定义的Servlet去处理每一个请求,只要去继承Servlet抽象类即可,然后再在web.xml中配置一下Setvlet的属性即可.

完整代码地址

链接:pan.baidu.com/s/1zaJy3wK-… 密码:ovgq

赞赏

                                                                                            

如果觉得文章有用,你可鼓励下作者 如果浪费你时间了,在这里先跟你抱歉