1、前言html
主流系統架構無非分兩種:C/S(客戶端/服務器)、B/S(瀏覽器/服務器)java
一、C/S架構web
優勢:安全編程
缺點:升級維護成本高,一旦要升級,全部終端要逐個升級瀏覽器
二、B/S架構安全
優勢:升級維護、部署簡單,只需維護服務端便可服務器
缺點:安全性有所欠缺多線程
當下B/S無疑是系統開發中主流模式,在B/S架構流行的前提下,就JavaEE規範下出現了各類各樣的Web框架,如Struts二、SpringMVC,而這些框架,無不基於Servlet規範,而Servlet容器中幾乎沒有不基於HTTP協議的實現(固然本文討論的不是HTTP協議),在開發中咱們到處使用框架,忽略底層細節,在部署時,打包扔進Tomcat、Jetty等web容器,簡單方便。那麼這些web容器的底層實現又是如何的呢?架構
今天本文就展現如何一步步實現一個簡單web服務器。併發
2、需求
一、實現一個web容器,並模擬登錄操做
二、可併發
三、線程安全
3、技術點
一、Socket編程
二、Java IO
三、線程池
四、ThreadLocal
五、多線程
六、NIO、BIO
4、單線程模式實現
一、寫一個HTML頁面,裏面有登錄須要的用戶名、密碼等輸入元素,值得一提的是,本例使用的是GET方式提交請求,已經足夠說明問題了,必須提到的是GET和POST獲取參數的方式是有區別的。
<html> <head> <meta charset="UTF-8"> <title>登陸頁</title> </head> <body> <form action="login" method="get"> <table> <tr> <td>用戶名:</td> <td><input type="text" name="username" ></td> </tr> <tr> <td>密碼:</td> <td><input type="text" name="password" ></td> </tr> <tr> <td><input type="reset" value="重置"></td> <td><input type="submit" value="登陸"></td> </tr> </table> </form> </body> </html>
二、有了HTML頁面,那麼如何把這個頁面讀到程序裏呢,這個時候就要用到IO了,請看下面的方法:
// 爲了方便main方法調用,並聲明爲static方法 private static 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(); }
三、那麼服務端怎麼與瀏覽器端交互,那麼須要Socket,請看下面例子:
(1)、先定義要返回的響應頭
// HTTP請求的返回頭 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"); }
(2)、啓動web服務
public static void main(String[] args) { try { // 監聽在8080端口 ServerSocket serverSocket = new ServerSocket(8080); while (true) { // 接收請求 Socket accept = serverSocket.accept(); // 獲取請求中的流 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(); } }
(3)、說明
上一步代碼,只讀一行數據 拿到用戶傳過來的參數,實際上是能夠拿到HTTP請求的全部信息的,以下:
GET / HTTP/1.1 Host: localhost:8080 Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: zh-CN,zh;q=0.8 Cookie: _ga=GA1.1.1755609982.1449039124; CNZZDATA1258600948=1953676425-1469624561-%7C1469624561; CNZZDATA1258740907=123042277-1469628131-%7C1469628131; userName=admin
5、執行效果
一、啓動程序,瀏覽器器敲下http://localhost:8080/,回車,便能看到以下界面
二、輸入用戶名:admin,密碼:admin,回車,提示登陸成功
三、輸入錯誤用戶名或密碼
6、後記
至此,一個單線程的web服務完成,其中存在以下問題
一、只能處理單個用戶請求,多用戶沒法同時使用
二、如何實現並行處理用戶請求
三、並行時線程如何安全
四、阻塞式的IO效率低,如何改進
請期待下一篇《本身動手實現簡單web容器二》
快樂源於分享。
此博客乃做者原創, 轉載請註明出處