我們到底能走多遠系列(8)
扯淡:
我們到底能走多遠?是想提醒自己不要停止學習和博客的更新,這個系列會一直和web開發有關,我的目標是寫完100個。慢慢來,階段性目標15個吧。哈哈
學得好不如做的好,做得好不如扯得好啊!
主題:
在上一輪學習中:Servlet-我們到底能走多遠系列(7) 發現要想理解web中的servlet,還是需要深入學習servlet 容器:tomcat(因為它是開源的嘛!)
為了不迷失在tomcat茫茫的源碼中,我先學習下最外圍的一些知識,然后先把《How Tomcat Works 》第一章看了。有人評論說這本書就是代碼太多,我到很喜歡這個寫作風格,程序員嘛,代碼就是我們的文章!
HTTP(Hypertext Transfer Protocol):
想要自己看http消息可以利用抓包工具比如wireshark來抓取http消息。(wireshark是一個很不錯的抓包工具,界面也蠻漂亮的,功能強大。)
官方下載:http://www.wireshark.org/download.html
訪問網站時抓到的包:包括了request 和 response(沒有包括body部分)
詳細的http協議的學習可以查看:http://www.cnblogs.com/tankxiao/archive/2012/02/13/2342672.html
其中關於method的get還是post可以查看:http://www.cnblogs.com/killbug/archive/2012/08/05/2624286.html
一個簡單的webServer:
知道了http消息的格式,就可以來學習下《How Tomcat Works 》第一章提供的代碼了
分成3部分:
HttpServer :負責監聽服務器的端口,將監聽到的消息交給Request處理,然后實例特定的Response返回除去。
Request :負責把http消息中的Uri取出來。
Response :根據Request取得的Uri,到到響應的文件中讀取內容,把內容返回出去。
上面的整個流程基本完成了響應 - 返回的效果。
HttpServer:
package code.tomcat.simpleWebServer; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class HttpServer { // 項目放置的文件夾 public static final String WEB_ROOT = System.getProperty("user.id") + File.separator + "webroot"; // SHUTDOWN命令 private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; // 判斷是否Shutdown標示符,控制程序運行 private boolean isShutdown = false; // 主程序 public static void main(String[] args) { HttpServer httpSever = new HttpServer(); // 不能叫wait,你懂的 httpSever.await(); } private void await(){ ServerSocket serverSocket = null; int port = 8080; try { // 用ServerSocket等待端口消息 serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1);//異常后,把java虛擬機都關掉 } while( ! isShutdown ){ Socket socket = null; InputStream input = null; OutputStream output = null; try { // 建立socket socket = serverSocket.accept(); // socket的輸入 input = socket.getInputStream(); // socket的輸出 output = socket.getOutputStream(); Request request = new Request(input); request.parse();//Request來解析出uri Response response = new Response(output); response.setRequest(request);//把Request放進去,是要用解析出來的uri response.sendStaticResource();//把產生的返回內容發送出去 // 關閉socket socket.close(); isShutdown = request.getUri().equals(SHUTDOWN_COMMAND); } catch (IOException e) { e.printStackTrace(); continue; } } } }
Request :
package code.tomcat.simpleWebServer; import java.io.IOException; import java.io.InputStream; public class Request { private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public void parse() { StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { // 把InputStream讀出來,放進byte[2048]大小的buffer中 i = input.read(buffer); } catch (IOException e) { e.printStackTrace(); i = -1; } // 傳化成StringBuffer for (int j = 0; j < i; j++) { request.append((char) buffer[j]); } // 然后再以String的形式傳經解析方法中去 uri = parseUri(request.toString()); } // 真正解析uri出來的方法,相當於是解析request的第一行:GET /killbug/ HTTP/1.1 想要取出“/killbug/”這段,即兩個空格之間那一段 private String parseUri(String requestString) { int index1, index2; // 第一個空格位置 index1 = requestString.indexOf(' '); if (index1 != -1) { // 第二個空格位置 index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) // 根據兩個空格的位置截斷出需要的一段 return requestString.substring(index1 + 1, index2); } return null; } public String getUri() { return uri; } }
Response :
package code.tomcat.simpleWebServer; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; public class Response { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; //文件輸入流 FileInputStream fis = null; // request.getUri()相當於:“/killbug/”,把路徑和文件起來定位到具體的文件,可以是項目中的一個html文件 File file = new File(HttpServer.WEB_ROOT, request.getUri()); if (file.exists()) { fis = new FileInputStream(file); int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch != -1) { // 寫進socket的output中去,就成了返回值了 output.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } }else { // 找不到文件的情況相當於404啦 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } } }
Tomcat外圍的一些知識:
1,tomcat的目錄結構:
window的(linux下目錄結構大同小異):
各文件夾各自的分管作用:
bin: 存放各種平台下啟動和關閉Tomcat的腳本文件.其中有個檔是catalina.bat,打開這個windos配置文件,在非注釋行加入JDK路徑,例如 : SET JAVA_HOME=C:\j2sdk1.4.2_06 保存后,就配置好tomcat環境了. startup.bat是windows下啟動tomcat的文件,shutdown.bat是關閉tomcat的文件.
work : Tomcat把各種由jsp生成的servlet文件放在這個目錄下.
logs : 存放Tomcat的日志文件
conf : Tomcat的各種配置文件,最重要的是 server.xml
lib: 在server/webapps目錄中,存放Tomcat自帶的兩個APP-admin和manager應用,使用來管理Tomcat-web服務用的.在server/lib目錄中,存放tomcat服務器所需要的各,web應用不能訪問種jar
temp:存放Tomcat運行時的臨時文件;,
2,tomcat連接數:
在tomcat配置文件server.xml中的<Connector ... />配置中,和連接數相關的參數有:
minProcessors:最小空閑連接線程數,用於提高系統處理性能,默認值為10
maxProcessors:最大連接線程數,即:並發處理的最大請求數,默認值為75
acceptCount:允許的最大連接數,應大於等於maxProcessors,默認值為100
enableLookups:是否反查域名,取值為:true或false。為了提高處理能力,應設置為false
connectionTimeout:網絡連接超時,單位:毫秒。設置為0表示永不超時,這樣設置有隱患的。通常可設置為30000毫秒。
其中和最大連接數相關的參數為maxProcessors和acceptCount。如果要加大並發連接數,應同時加大這兩個參數。
web server允許的最大連接數還受制於操作系統的內核參數設置,通常Windows是2000個左右,Linux是1000個左右。Unix中如何設置這些參數,請參閱Unix常用監控和管理命令
tomcat4中的配置示例:
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
port="8080" minProcessors="10" maxProcessors="1024"
enableLookups="false" redirectPort="8443"
acceptCount="1024" debug="0" connectionTimeout="30000" />
對於其他端口的偵聽配置,以此類推。
3,如何加大tomcat可以使用的內存
tomcat默認可以使用的內存為128MB,在較大型的應用項目中,這點內存是不夠的,需要調大。
Unix下,在文件{tomcat_home}/bin/catalina.sh的前面,增加如下設置:
JAVA_OPTS='-Xms【初始化內存大小】 -Xmx【可以使用的最大內存】'
需要把這個兩個參數值調大。例如:
JAVA_OPTS='-Xms256m -Xmx512m'
表示初始化內存為256MB,可以使用的最大內存為512MB
4,關於虛擬主機
tomcat服務器中在 $CATALINA_HOME/conf/service.xml中配置設置<host>的name屬性來配置不同主機名對應的站點:
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
</Host>
<!-虛擬主機配置-->
<Host name="http://www.cnblogs.com/" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<Context path="" docBase="G:\dev\apache-tomcat-6.0.33\webapps\killbug\" debug="1" reloadable="true" allowLinking="true"></Context>
</Host>
</Engine>
service.xml中<host>標簽用於配置站點,一個<host>建立一個WEB站點,可以使用多個<host>配置多個站點,但同一個<Engine>中<host>的name不能相同,name屬性對應該站點的主機名稱,appBase="webappa"設置了一個路徑,該路徑將作為嵌套在<host>中<context>的屬性docBase的基准路徑,docBase可以是絕對路徑也可以是相對於appBase的相對路徑,如:docBase="Jquery\",當tomcat接受到一個請求,會取出主機名來跟<host>的name值來對應,來訪問對應的context中docBase配置的站點,若沒有找到對應的host,則會訪問在<Engine>中defaultHost配置的默認站點,將defaultHost值設置為<Engine>中某個<host>的name值,則該host就會作為該Engine引擎的默認站點。
當配置多個<host>時就不能使用IP來作為主機名來訪問tomcat,因為Host的name值對應同一個IP,這時就不知道訪問哪一個站點。
在建立基於主機名的虛擬主機時,除了在service.xml中配置主機名與站點的映射外,還要配置主機名與IP地址的映射,這樣在網絡中才會找到該WEB服務器。配置主機名與IP地址的映射有兩種方式:
1.通過DNS(域名解析服務器)系統來配置
2.通過在客戶端本地Hosts文件中配置。
Hosts文件可用於小型的企業內部網,而DNS用於大型網絡服務(如:Internet上對外提供服務)。客戶機會首先在本地Hosts文件中找主機名與IP的映射,若沒有找到才會去DNS系統中去查找。
下面介紹在hosts文件中配置。
在C:\Windows\System32\drivers\etc下找到Hosts文件,用EditPlus打開該文件會看到默認有127.0.0.1 localhost,這時為什么我們訪問http://localhost:8080/,下面我們在新的一行寫127.0.0.1 www.cnblogs.com ,就建立起來127.0.0.1與www.cnblogs.com主機名的映射。
我們如上面圖所示配置Service.xml,就完成了虛擬主機的配置。啟動tomcat,訪問http://www.cnblogs.com:8080/killbug就可以訪問killbug站點了。
總結:tomcat 作為容器需要監聽http消息,調用響應servlet,做出回應。這個基本的流程似乎只是tomcat的冰山一角,讓我們繼續前行。
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
共勉