文章目錄
有了以下的知識:
ASP.NET那點不為人知的事(一)
ASP.NET那點不為人知的事(二)
想必開發一個小型伺服器以不是問題了,功能補複雜,能夠響應用戶端瀏覽器的請求,並根據請求檔案的類型返迴響應的資訊,如能處理靜態頁面、圖片、樣式、指令碼、動態網頁面等。 回顧
由於用戶端和服務端的通訊是通過Socket通訊,且它們通訊的“語言”是基於Http1.1協議。根據這個線索,我們完全可以自己程式開發伺服器軟體,暫且叫他Melodies Server,當然這是一個很簡單的範例,和真正的伺服器還是有差距的,好,我們進入正題,首先需要瞭解以下幾個知識點:
- 用戶端和服務端是由Socket進行通訊,在伺服器端需要有監聽請求的通訊端,他綁定在某個連接埠號碼上,如果發現有請求過來,socket.Accept()產生一個通訊端和用戶端進行通訊。
- 用戶端發送的請求(報文)交給伺服器軟體分析,判斷是否為靜態頁面、圖片還是動態aspx檔案,若是靜態檔案能直接返回。
- 處理動態網頁面稍稍麻煩,需要反射建立頁面類(原因詳見ASP.NET那點不為人知的事(二))
開啟服務
public partial class WebServerForm : Form { public WebServerForm() { InitializeComponent(); TextBox.CheckForIllegalCrossThreadCalls = false; } private Socket socketWatch;//負責監聽瀏覽器串連請求的通訊端 private Thread threadWatch;//負責迴圈調用Socket.Accept 監聽線程 private void btnStartServer_Click(object sender, EventArgs e) { socketWatch=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); IPAddress address = IPAddress.Parse(txtIPAddress.Text.Trim()); IPEndPoint endPoint=new IPEndPoint(address,int.Parse(txtPort.Text.Trim())); socketWatch.Bind(endPoint); socketWatch.Listen(10); threadWatch = new Thread(WatchConnect); threadWatch.IsBackground = true; threadWatch.Start(); } private bool isWatch = true; //Dictionary<> void WatchConnect() { while (isWatch) { Socket socketConnection=socketWatch.Accept(); ShowMsg("瀏覽器:"+socketConnection.RemoteEndPoint.ToString()+",串連成功*********************"); ConnectionClient connectionClient = new ConnectionClient(socketConnection, ShowMsg); } } void ShowMsg(string msg) { txtLog.AppendText(msg+"\r\n"); } }
分析報文,處理請求
- 在非同步線程建立的與用戶端通訊的Socket,它的主要職責就是分析保文:
/// <summary> /// 與用戶端串連通訊類(包含一個與用戶端通訊的通訊端和通訊線程) /// </summary> public class ConnectionClient { private Socket socketMsg;//與用戶端通訊通訊端 private Thread threadMsg;//通訊線程 private DGShowMsg dgShowMsg;//負責向主表單文字框顯示訊息的委託 public ConnectionClient(Socket socket,DGShowMsg dgShowMsg) { this.socketMsg = socket; this.dgShowMsg = dgShowMsg; //負責啟動一個接受用戶端瀏覽器請求報文的線程 threadMsg = new Thread(ReceiveMsg); threadMsg.IsBackground = true; threadMsg.Start(); } private bool isRec = true; void ReceiveMsg() { while (isRec) { byte[] arrMsg=new byte[1024*1024*3]; //接受對應用戶端發送過來的請求報文 int length = socketMsg.Receive(arrMsg); string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, length); dgShowMsg(strMsg); //處理報文 string[] arrStr = strMsg.Replace("\r\n", "韘").Split('韘'); string[] firstRow=arrStr[0].Split(' '); string requestFile = firstRow[1]; ExcuteRequest(requestFile);//todo:長串連多少時間 } } private void ExcuteRequest(string requestFile) { //獲得被請求頁面的尾碼名 string fileExtension = System.IO.Path.GetExtension(requestFile); if (!string.IsNullOrEmpty(fileExtension)) { switch (fileExtension.ToLower()) { case ".html": case ".htm": case ".css": case ".js": ExcuteStaticPage(requestFile,fileExtension); break; case ".jpg": ExcuteImg(requestFile,fileExtension); break; case ".aspx": ExcuteDymPage(requestFile,fileExtension); break; } } }
- 針對不同的請求執行不同的操作,其中靜態頁面、css、js、圖片處理操作一樣,都是屬性靜態檔案,直接返回位元組流:
/// <summary> /// 處理靜態頁面,直接輸出 /// </summary> private void ExcuteStaticPage(string requestPath,string fileExtension) { StringBuilder sb=new StringBuilder(); //獲得請求檔案的檔案夾的實體路徑 string dataDir = AppDomain.CurrentDomain.BaseDirectory; if (dataDir.EndsWith(@"\bin\Debug\") || dataDir.EndsWith(@"\bin\Release\")) { dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName; } string phyPath = dataDir + requestPath; //讀取靜態頁面內容 string fileContent = System.IO.File.ReadAllText(phyPath); //獲得響應體位元組數組 byte[] fileArr = System.Text.Encoding.UTF8.GetBytes(fileContent); //獲得響應報文頭: string responseHeader = GetResponseHeader(fileArr.Length, fileExtension); byte[] arrHead = System.Text.Encoding.UTF8.GetBytes(responseHeader); //發送響應報文頭回瀏覽器 socketMsg.Send(arrHead); //發送響應報文體回瀏覽器 //todo:sleep 1分鐘會怎樣 socketMsg.Send(fileArr); } /// <summary> /// 處理圖片 /// </summary> /// <param name="requestPath"></param> /// <param name="extentionName"></param> private void ExcuteImg(string requestPath, string extentionName) { //獲得請求檔案的檔案夾的實體路徑 string dataDir = AppDomain.CurrentDomain.BaseDirectory; if (dataDir.EndsWith(@"\bin\Debug\") || dataDir.EndsWith(@"\bin\Release")) { dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName; } //獲得請求檔案的實體路徑(絕對路徑) string phyPath = dataDir + requestPath; int imgLength; byte[] fileArr; //讀取圖片內容 using (FileStream fs = new FileStream(phyPath, FileMode.Open)) { fileArr = new byte[fs.Length]; imgLength = fs.Read(fileArr, 0, fileArr.Length); //獲得響應報文頭 string responseHeader = GetResponseHeader(imgLength, extentionName); byte[] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader); socketMsg.Send(arrHeader); socketMsg.Send(fileArr, imgLength, SocketFlags.None); } } /// <summary> /// 得到回應標頭資訊 /// </summary> /// <param name="contentLength"></param> /// <param name="fileExtentionName"></param> /// <returns></returns> private string GetResponseHeader(int contentLength,string fileExtentionName) { StringBuilder sbHeader=new StringBuilder(); sbHeader.Append("HTTP/1.1 200 OK\r\n"); sbHeader.Append("Content-Length: "+contentLength+"\r\n"); sbHeader.Append("Content-Type:" + GetResponseHeadContentType(fileExtentionName) + ";charset=utf-8\r\n\r\n"); return sbHeader.ToString(); } /// <summary> /// 根據尾碼名擷取響應保文中的內容類型 /// </summary> /// <param name="fileExtentionName"></param> /// <returns></returns> private string GetResponseHeadContentType(string fileExtentionName) { switch (fileExtentionName.ToLower()) { case ".html": case ".htm": case ".aspx": return "text/html"; break; case ".css": return "text/plain"; break; case ".js": return "text/javascript"; break; case ".jpg": return "image/JPEG"; case ".gif": return "image/GIF"; break; default: return "text/html"; break; } }
- 同樣,針對動態網頁面反射建立其頁面類,注意記得讓其實現IHttpHandler介面
- 建立一個頁面類View
public class View:IHttpHandler { public string ProcessRequest() { string dataDir = AppDomain.CurrentDomain.BaseDirectory; //獲得模板實體路徑 if (dataDir.EndsWith(@"\bin\Debug\") || dataDir.EndsWith(@"\bin\Release")) { dataDir = System.IO.Directory.GetParent(dataDir).Parent.Parent.FullName; } string phyPath = dataDir + "/model.htm"; string modelContent=System.IO.File.ReadAllText(phyPath); modelContent = modelContent.Replace("@Title", "動態網頁面").Replace("@Content", "反射建立頁面類"); return modelContent; } }
- 反射View,調用其ProcessRequest方法執行服務端代碼
/// <summary> /// 反射建立動態網頁面對象 /// </summary> /// <param name="requestFile"></param> /// <param name="extentionName"></param> private void ExcuteDymPage(string requestFile, string extentionName) { string pageClassName = System.IO.Path.GetFileNameWithoutExtension(requestFile); string assemblyName = Assembly.GetExecutingAssembly().GetName().Name; //獲得頁面類全名稱 pageClassName = assemblyName + "." + pageClassName; //通過反射建立頁面類對象 object pageObj = Assembly.GetExecutingAssembly().CreateInstance(pageClassName); IHttpHandler page = pageObj as IHttpHandler; byte[] fileArr=null; if (page!=null) { string strHtml=page.ProcessRequest(); fileArr= System.Text.Encoding.UTF8.GetBytes(strHtml); } //獲得響應報文頭 string responseHeader = GetResponseHeader(fileArr.Length, extentionName); byte[] arrHeader = System.Text.Encoding.UTF8.GetBytes(responseHeader); socketMsg.Send(arrHeader); socketMsg.Send(fileArr); }
總結
至此,一個小型的伺服器軟體就構建好了,趕緊測試一下呵呵。