上一篇博客《本身動手實現簡單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
快樂源於分享。異步
此博客乃做者原創, 轉載請註明出處