本身動手實現簡單web容器二

    上一篇博客《本身動手實現簡單web容器一》,留下了一些問題,今天咱們來解決並行訪問的問題,即多個用戶同時訪問,咱們的web服務器能給出響應,而不至於阻塞住。現代計算機愈來愈好,CPU核心數也愈來愈多,爲了更高效的利用CPU,Java提供多線程的編程方式,下面咱們就用多線程的方式解決這個問題。html

    一、寫一個HandleRequestThread類,實現Runnable接口,重寫run方法,便實現了一個線程類。代碼以下:java

public class HandleRequestThread implements Runnable {

	private static final StringBuilder responseContent = new StringBuilder();

	static {
		responseContent.append("HTTP/1.1 200 OK");
		responseContent.append("Content-Type: text/html; charset=utf-8");
		responseContent.append("Content-Length:%s");
		responseContent.append("Date:%s");
		responseContent.append("Server:This is a simulation web container");
	}

	private Socket accept;

	public HandleRequestThread(Socket accept) {
		this.accept = accept;
	}

	@Override
	public void run() {
		try {
			// 獲取請求中的流
			BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
			// 只讀取第一行數據
			String request = br.readLine();
			String result = "";
			if (!"".equals(request) && null != request) {
				String urlAndPrams = request.split(" ")[1];
				if (urlAndPrams.indexOf("/login") != -1) {
					// 獲取用戶傳過來的用戶名密碼
					String[] params = urlAndPrams.split("\\?")[1].split("&");
					String username = params[0].split("=").length < 2 ? null : params[0].split("=")[1];
					String password = params[1].split("=").length < 2 ? null : params[1].split("=")[1];
					if ("admin".equals(username) && "admin".equals(password)) {
						result = "login success";
					} else {
						result = "username or password error";
					}
				} else {
					// 調用前1面定義的獲取登陸頁方法,獲取已經寫好的登陸頁
					result = getLoginPage();
				}
			} else {
				// 調用前面定義的獲取登陸頁方法,獲取已經寫好的登陸頁
				result = getLoginPage();
			}
			// 拿到該請求對應Socket的輸出流,準備向瀏覽器寫數據了
			PrintWriter printWriter = new PrintWriter(accept.getOutputStream());
			// 把文件長度、日期填回Response字符串中
			printWriter.println(String.format(responseContent.toString(), result.length(), new Date()));
			// 必須有個換行,換行以後才能向瀏覽器端寫要傳回的數據(HTTP協議)
			printWriter.println();
			// 寫頁面數據
			printWriter.println(result);
			printWriter.flush();
			printWriter.close();
			accept.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	private String getLoginPage() {
		// 用於存儲從文件讀出的文件
		StringBuilder sb = new StringBuilder();
		try {
			// 聲明一個BufferedReader準備讀文件
			BufferedReader htmlReader = new BufferedReader(new InputStreamReader(
					new FileInputStream(new File(System.getProperty("user.dir") + "/webapp/login.html"))));
			String html = null;
			// 按行讀取,直到最後一行結束
			while ((html = htmlReader.readLine()) != null) {
				sb.append(html);
			}
			// 關閉
			htmlReader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		// 以字符串形式返回讀到的HTML頁面
		return sb.toString();
	}
}

    二、啓動web 服務web

public class WebServer {
	public static void main(String[] args) {
		try {
			// 監聽在8080端口
			ServerSocket serverSocket = new ServerSocket(8080);
			while (true) {
				Socket accept = serverSocket.accept();
				// 接收到客戶端請求,新起一個線程來處理該請求
				new Thread(new HandleRequestThread(accept)).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

    以上代碼已經能夠實現多用戶並行訪問了,視乎完美解決了問題,假設如今有1000個用戶同時訪問,那麼意味着要建立1000個線程,那麼CPU和內存資源確定吃緊,那麼有什麼辦法優化呢?答案是線程池,預先建立一批線程在池子裏,請求來了就處理,線程數不夠,請求排隊(固然線程池的實現是很複雜的,不是本文討論的重點)。編程

    廢話很少說,上代碼。瀏覽器

public class WebServer {
	public static void main(String[] args) {
		try {
			// 建立一個定長爲10的線程池
			ExecutorService pool = Executors.newFixedThreadPool(10);
			// 監聽在8080端口
			ServerSocket serverSocket = new ServerSocket(8080);
			while (true) {
				Socket accept = serverSocket.accept();
				// 接收到客戶端請求,新起一個線程來處理該請求
				pool.execute(new HandleRequestThread(accept));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

    用線程池,不只能夠節省線程建立和銷燬的開銷,還能夠穩定線程的數量,不至於撐爆CPU和內存,這裏不得不提一下Executors,Executors提供四種線程池建立的靜態方法:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor,這個不是本文討論的重點,咱們只說咱們用到的定長線程池,分析一下jdk源碼服務器

public static ExecutorService newFixedThreadPool(int nThreads) {
   return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}

ThreadPoolExecutor是JDK默認線程池的實現,這裏corePoolSize和maximumPoolSize相等,keepAliveTime表示不保存空閒線程,這個參數能夠 根據請求的密集程度來調整,TimeUnit線程存活時間單位,BlockingQueue線程池滿,選用的隊列,這裏咱們是不限長隊列,LinkedBlockingQueue是FIFO隊列。具體線程池的用法將在後面的文章中詳細討論。多線程

後記app

    至此,咱們用僞異步的方式,對咱們的web服務器進行了改造,實現了請求的並行,可是這個方式並不是最高效,那麼還有什麼高效的方式呢?敬請期待《本身動手實現簡單web容器三》webapp

快樂源於分享。異步

此博客乃做者原創, 轉載請註明出處

相關文章
相關標籤/搜索