java Socket + 自定義線程池 實現web服務器 仿Servlet

前言

基於java Scoket的TCP協議 簡單實現http web服務器,使用自定義線程池去處理每個請求,用瀏覽器看成客戶端,達到javaWeb中相似於訪問Servlet的效果。 (對http協議和Servlet要有必定了解)html

執行效果

  • http服務器端

客戶端 (login.html): java

在這裏插入圖片描述
登錄後: 測試帳號: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-… 密碼:ovgqweb

讚揚

                                                                                            

若是以爲文章有用,你可鼓勵下做者
若是浪費你時間了,在這裏先跟你抱歉
相關文章
相關標籤/搜索