聲明:本文大概需要30分鍾,如果只依據本文不看源碼就能寫出Web服務器就算學會了~如有錯誤歡迎指正~
首先我們要知道web服務器是什么?
一般指網站服務器,是指駐留於因特網上某種類型計算機的程序
服務器有什么作用:
1.放置網站文件,讓別人瀏覽
2.可以放置數據文件,供別人下載
服務器分類:
1.Apache(例如TomCat)
2.Nginx
3.IIS
Web服務器的工作原理,分四步:
1.連接過程
2.請求過程
3.應答過程
4.關閉連接
手擼web服務器就是根據web服務器的工作原理去手寫代碼以實現例如Tomcat的部分核心功能
根據這兩天的學習可以分為基礎和進階版本:
基礎就是簡單實現,靈活度不高
進階就是把部分固定功能的代碼封裝,再做一些動態的方法以供調用
下面我總結一下手擼Web服務器的業務邏輯

1 /** 2 * 極其簡易的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 15 public WebServer() { 16 try { 17 server = new ServerSocket(8080); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 public void start() { 24 try { 25 while(true) { 26 Socket socket = server.accept(); 27 OutputStream outputStream = socket.getOutputStream(); 28 outputStream.write("abcaaa".getBytes()); 29 outputStream.flush(); 30 socket.close(); 31 } 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 36 } 37 38 }
基礎實現:
一、創建服務器類即WebServer類(連接過程)
·1聲明ServerSocket類,代表服務器;ServerSocket作用是監聽特定端口,例如:8080,8086等;端口號總共用65535個
綁定端口方法:ServerSocket server = new ServerSocket(8080);
利用構造方法初始化ServerSocket;
端口可以傳入0,表示操作來為服務器分配一個任意可用的端口,也稱為匿名端口,但不推薦使用;
如果端口被占用會拋出BindException異常
解決辦法:win + r進入運行,輸入CMD進入命令行模式
輸入 netstat -ano查看所有被占用端口 找到想要關閉的端口對應的Listening后的值
輸入 taskkill -f -pid (Listening后的值)
·2創建開始方法start()(請求和應答過程)
(請求)調用ServerSocket的accept()方法監聽並接受套接字(socket)的連接,返回值是Socket對象
因為服務器是被動程序,需要等有請求的時候才會響應,所以accept()方法應該是持續運行的;所以要用到while(true)
(應答)接收到請求之后根據請求的不同應該回應不同的信息,此處基礎實現回應相同的信息
·調用socket的getOutputStream()返回這個套接字的輸出流(OutputStream)
·然后調用OutputStream的方法write(byte[] b)輸入想寫內容;因為我們輸入的是字符串,而write方法要求傳入字節數組,
所以調用字符串的getBytes()方法返回字符數組
·然后調用OutputStream的方法flush()刷新流並強制寫出所有緩沖的輸出字節
·3關閉連接
調用Socket的close()方法關閉連接
·4測試該基礎服務器能否成功運行
利用HttpWatch監聽該連接過程,查看請求和響應;如果響應的是你在write中寫的內容,該服務器即創建成功!
但是其中頁面一直處於加載過程,原因是你的響應不符合Http協議,瀏覽器一直在等待想要的內容(即Http的標准響應格式)
·5修改程序不當的響應方式
·這個時候就要修改write以符合Http的標准響應格式
·調用PrintStream對象,該對象繼承了FilterOutputStream,而FilterOutputStream繼承了OutputStream;
·該對象與其他輸出流不同的是永遠不會拋出IOException,並且會自動調用flush()方法(一般在執行print,println,write時自動執行),在需要寫入字符的時候推薦使用;因為IO流是基於裝飾者模式,所以使用該對象必須兩種類型(File或OutputStream)參數傳入一種,此處傳入的是OutputStream;而OutputStream可以通過Socket的getOutputStream()方法得到
·調用PrintStream的println()方法拼接出標准的Http協議響應格式(狀態行,響應頭,空行,響應內容)如果在調試過程中沒有響應,可以嘗試以下方法,查看方法調用順序是否有問題、重新啟動瀏覽器、換一個瀏覽器、重啟Eclipse
1 /** 2 * 修改符合HTTP協議的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 15 public WebServer() { 16 try { 17 server = new ServerSocket(8080); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 public void start() { 24 try { 25 while(true) { 26 Socket socket = server.accept(); 27 PrintStream ps = new PrintStream(socket.getOutputStream()); 28 ps.println("HTTP/1.1 200 OK"); 29 ps.println("Content-Type:text/html"); 30 String s = "server is running->->->->"; 31 ps.println("Content-Length:" + s.length()); 32 33 ps.println(""); 34 35 ps.write(s.getBytes()); 36 socket.close(); 37 } 38 } catch (IOException e) { 39 e.printStackTrace(); 40 } 41 42 } 43 44 }
這個時候瀏覽器上應該有write()里的內容:“server is running->->->->”;如果出錯了檢查是不是沒有加空行
進階實現:
具體的Web服務器結構
·cn.itlou----core 核心包: WebServer ,ClientHandler
|
---http 封裝Http協議相關內容:HttpRequest ,HttpResponse
|
---common 參數配置信息:ServletContext ,HttpContext
config配置文件:web.xml
一、基礎實現有許多許多的不足,單線程不能同時接收過多的請求,所以我們加入多線程技術
具體的服務器架構不用變,增加線程池對象的引用並初始化線程池;
ExecutorService threadPool = Executors.newFixedThreadPool(int a);線程池的創建方法,記得數字不要給的過高,可能電腦不行導致程序出錯;
在start()方法中加入線程的應用,調用execute(Runnable command)方法;傳入實現Runnable的對象ClientHandler
而該對象應該包含所有我們希望通過利用多線程提高性能的方法(應答,關閉連接)
我們把該對象命名為ClientHandler它實現了Runnable接口,重寫run方法並寫入應答,關閉連接的代碼;
·1聲明一個代表客戶端的對象Socket,並將該對象傳入ClientHandler構造方法;
·2提取響應代碼寫入run方法中
·3利用線程池執行寫好的ClientHandler類
注意:重寫時寫入網頁數據應該用PrintStream的write方法,而不是println()方法,使用println方法會輸出地址
1 /** 2 * 進階利用多線程的版本 3 * @author shaking 4 * 5 */ 6 public class WebServer { 7 8 public static void main(String[] args) { 9 WebServer webServer = new WebServer(); 10 webServer.start(); 11 } 12 13 private ServerSocket server; 14 private ExecutorService threadPool; 15 16 public WebServer() { 17 try { 18 server = new ServerSocket(8080); 19 threadPool = Executors.newFixedThreadPool(100); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 public void start() { 26 try { 27 while(true) { 28 Socket socket = server.accept(); 29 threadPool.execute(new ClientHandler(socket)); 30 } 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 35 } 36 37 }
1 /** 2 * 多線程部分代碼 3 * @author shaking 4 * 5 */ 6 public class ClientHandler implements Runnable{ 7 8 private Socket socket; 9 10 public ClientHandler(Socket socket) { 11 this.socket = socket; 12 } 13 14 public void run() { 15 try { 16 PrintStream ps = new PrintStream(socket.getOutputStream()); 17 ps.println("HTTP/1.1 200 OK"); 18 ps.println("Content-Type:text/html"); 19 String s = "server is running------->>>"; 20 ps.println("Content-Length:" + s.length()); 21 22 ps.println(""); 23 24 ps.write(s.getBytes()); 25 socket.close(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 30 } 31 32 }
這個時候瀏覽器上應該有write()里的內容:“server is running------->>>”;如果出錯了檢查是不是線程池加入過多線程數;
二、修改程序使其能輸出具體的網頁
這時候只需要修改響應部分的部分代碼;即修改ClientHandler類中run方法的部分內容
·1自制一個簡單網頁或者已有的網頁,例如index.html,把其放入WebContent文件夾下
思考一下在網頁中怎么輸出的字符串,網頁同理(傳入文件,寫出文件內容)
·2 -1)傳入文件,用File類,new File(); 在其中傳入一個String類型的pathName,然后使用BufferedInputStream字節緩沖流,使用該類需要傳入一個FileInputStream類型的對象,這里我們使用FileInputStream傳入我們的File對象即想要輸出的網頁,將字節數組byte[] bs = new byte[(int)file.length()],傳入BufferedInputStream的read(byte[])方法將文件讀入
-2)寫出文件內容,調用PrintStream的write方法,write要求傳入字符數組,調用PrintStream的write(byte[])方法傳入已經創建好的字符數組,關閉BufferedInputStream流
·3關閉socket連接
1 /** 2 * 輸出具體頁面的代碼 3 * @author shaking 4 * 5 */ 6 public class ClientHandler implements Runnable{ 7 8 private Socket socket; 9 10 public ClientHandler(Socket socket) { 11 this.socket = socket; 12 } 13 14 public void run() { 15 try { 16 PrintStream ps = new PrintStream(socket.getOutputStream()); 17 ps.println("HTTP/1.1 200 OK"); 18 ps.println("Content-Type:text/html"); 19 String pathName = "WebContent/index.html"; 20 File file = new File(pathName); 21 ps.println("Content-Length:" + file.length()); 22 23 ps.println(""); 24 25 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 26 27 byte[] bs = new byte[(int) file.length()]; 28 29 bis.read(bs); 30 ps.write(bs); 31 bis.close(); 32 socket.close(); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 37 } 38 39 }
三、我們這個服務器還是很低端,只能顯示一個固定的網頁,我們希望服務器能夠動態的響應輸入的所有網頁,有的就顯示,沒有就404
根據地址欄輸入的網址不同,請求行也會相應的改變
·1獲取請求行的部分內容,例如: GET /abc.html HTTP/1.1
這里我們使用字符輸入流BufferedReader,傳入新的對象InputStreamReader並在新的對象中傳入socket的輸入流;
調用BufferedReader的方法readline()讀取這一行數據,如果需要完整的HTTP請求可以寫死循環
我們利用字符串的split()方法切割請求行,以" "為目標切割成3份再用String類型數組接收,其中索引為1的數組就是我們想要的/abc.html
·2修改pathName的值使其可以動態的變化
·3設置index默認頁面和404錯誤頁面
判斷split切割后的地址,決定如何顯示頁面;如果為空顯示index,如果沒有對應的網頁顯示404
index需要判斷切割后的地址是否為空
404需要判斷切割后的地址對應的文件是否存在
注意:如果控制台報錯:FileNotFoundException:WebContent\favicon.ico在WebCont下放入一個后綴名為ico的圖片文件即可
1 /** 2 * 輸出具體頁面的代碼 3 * 包括默認首頁與404頁面 4 * @author shaking 5 * 6 */ 7 public class ClientHandler implements Runnable{ 8 9 private Socket socket; 10 11 public ClientHandler(Socket socket) { 12 this.socket = socket; 13 } 14 15 public void run() { 16 try { 17 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); 18 String line = reader.readLine(); 19 String[] s = line.split(" "); 20 String uri = s[1]; 21 22 if(uri.equals("/")) { 23 uri = "/index.html"; 24 } 25 26 PrintStream ps = new PrintStream(socket.getOutputStream()); 27 ps.println("HTTP/1.1 200 OK"); 28 ps.println("Content-Type:text/html"); 29 String pathName = "WebContent" + uri; 30 File file = new File(pathName); 31 32 if(!file.exists()) { 33 uri = "/404.html"; 34 file = new File("WebContent" + uri); 35 } 36 37 ps.println("Content-Length:" + file.length()); 38 39 ps.println(""); 40 41 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 42 43 byte[] bs = new byte[(int) file.length()]; 44 45 bis.read(bs); 46 ps.write(bs); 47 bis.close(); 48 socket.close(); 49 } catch (IOException e) { 50 e.printStackTrace(); 51 } 52 53 } 54 55 }
轉載請注明出處:http://www.cnblogs.com/shak1ng/
