java Socket + 自定義執行緒池 實現web伺服器 仿Servlet
阿新 • • 發佈:2019-12-31
前言
基於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
讚賞