目錄
一個完整的web服務器,不僅需要滿足用戶端對於圖片、文檔等資源的需求;還能夠對於用戶端的動態請求,返回指定程序生成的數據。支持動態請求處理是web服務器的必要組成部分,現有比較成熟的前端動態技術有CGI,ASP/ASP.net, PHP,原生javascript實現的Ajax技術以及基於HTML5的webSocket通訊,它們每一項都涉及很多相關知識,不過歸結到核心都是前后端的數據交互,特別是對於后端來說並沒有太大區別。作為動態通訊,實現就不僅僅涉及后端的數據處理,前端也要負責相當的數據操作,因此本篇將分前端web網頁和后端服務器兩部分來實現整個過程,其中前端通過Ajax技術,后端基於C#來實現。
(1).基於Ajax的前端實現
Ajax全稱異步JavaScript和XML,是一種創建交互式網頁應用的網頁開發技術,通過在后台與服務器進行少量數據交換,Ajax 可以使網頁實現異步更新。Ajax的實現包含三個步驟:
1.創建XmlHttpRequest對象
2.綁定事件回調,判定事件回調,設置首部,發送請求(確定URL, 參數,異步方式等)
3.等待數據返回,回調函數執行。
其中XmlHttpRequest對象是Ajax實現的基礎,幾乎所有的現代瀏覽器都內建XMLHttpRequest對象,不過為了滿足老版本瀏覽器如IE5,IE6的兼容性需求,對象創建的代碼具體如下:
//創建XMLhttpRequest對象並返回 GetAjaxReq: function(){ var s = ["XMLHttpRequest", "ActiveXObject('Microsoft.XMLHTTP')"], req; if(!"1"[0]){ s[0] = location.protocol === "file:"? "!":s[0]; } for(var i=0, axo; axo = s[i++]; ){ try{
if(req = eval("new " + axo)) //eval 執行內部程序,並返回 return req; }catch(e){} }}
上述過程實現了XmlHttpRequest對象的創建,下一步就是Ajax通訊的實現了,不過在進行對象操作之前,要先熟悉XmlHttpReques對象的方法和屬性,知己知彼,方能百戰不殆,這里提供從相關參考資料上查找的信息,希望對大家有幫助。
XmlHttpRequest屬性:
XmlHttpRequest方法:
了解了上述屬性方法,就可以比較清晰的了解ajax實現過程,包含http請求的創建,http頭的添加,異步模式設置,回調函數確定以及最后的前端數據發送,當然為了方便整個Ajax流程代碼的復用,這里編寫一個簡單的框架$,將ajax的實現等都放在IIFE中,如下:
//前端ajax實現主體,其中obj為用戶提交的相關參數
ajax:function(obj){ var HttpReq = $.GetAjaxReq(); //獲得XmlHttpRequest對象 $.mix(obj, $.ajaxSettings, false); //非覆寫模式下多對象合並,用於發送對象初始化 var ele = $.GetElement(obj.object); HttpReq.open(obj.type, obj.url, obj.async); //創建一個新的HTTP請求 HttpReq.setRequestHeader('If-Modified-Since', '0');//添加http頭 If-Modified-Since:0 if(!obj.async) //判斷是否為異步模式,同步模式是timeout賦值會出錯 HttpReq.timeout = obj.timeout; HttpReq.onreadystatechange = function(){ //Ajax執行后回調函數 $.ajaxCallback(ele, obj, HttpReq); }; //因為IE中將數組直接發送會不識別(火狐會將數組直接轉成字符串發送),考慮到兼容性, 如果是數組,就轉成字符串發送 if({}.toString.apply(obj.data) === '[object Array]'){ HttpReq.send(obj.data.join(' ')); }else{ HttpReq.send(obj.data); } },
因此回調函數的實現就比較簡單,主要包含響應狀態的判斷,http連接狀態的判斷以及相關數據的獲取處理。ajaxCallback:function(ele, obj, req){ if(req.readyState == 4){ //XmlHttpRequest狀態變化判斷 if(req.status == 200){ //Http狀態判斷
//判斷是否為函數,進行后續處理 if(Object.prototype.toString.call(obj.success) === '[object Function]'){ obj.success(ele, req.responseText); } } else{ alert("AJAX Update Data Failed!"); } } },實現了上述Ajax框架后,在html文檔里添加通過調用Ajax接口,就可以完成一次Ajax通訊。
function UploadText(){ var t_ele = document.getElementById("TEXT"); $.ajax({ object: '#trans_f', type:'POST', //ajax提交的方法 url :'html/AspApply.asp', async: true, //異步提交 timeout: 5000, data: t_ele.value, success:function(ele, msg){ ele.innerHTML = msg; } }); } function RefreshText(){ $.ajax({ object: '#frame', type:'GET', url :'html/AspGet.asp', async: true, timeout: 5000, success:function(ele, msg){ ele.innerHTML = msg; } }); } RefreshText();到此為止,Ajax通訊的前端部分實現已經全部完成,詳細實現請參考博文后的鏈接文檔。下面就進入本篇的正題,Web服務器對前端動態數據的處理及數據回復的實現。
(2).Web服務器后端處理
Web服務器動態通訊的設計主要包括以下三個方面:
1. 前端提交數據的獲得(一個或多個數據包的讀取)。
2. 數據分析,處理和保存。
3. 后端動作及相關數據回復反饋。
對於一個完整的Web服務器並不在意數據來自AJAX、CGI、ASP還是Websocket,它只會對后端已經實現的處理架構進行正確反饋,如果沒有實現,需要返回指定的錯誤代碼,用於告知客戶端。在進行服務器實現之前,我們要明白一點:前端的提交的數據雖然在傳輸過程上可能會分片或者加密等,但提交到后端服務器處理時,一定是完全透明且確定的,這就決定了我們可以通過分析后端服務器接收到的數據,來進行解析處理。這也是后端處理的基礎,如果看過之前關於嵌入式在線升級實現的文章,那么本篇的解析部分也類似,我們需要在原有的靜態服務器處理的基礎上添加動態處理部分,完成整個服務器的實現。下面我就以上面編寫的前端通訊數據包為例,闡述如何數據的分析和處理。
Ajax-GET方法提交的數據如下:
通過分析發現,Get的實現與靜態資源的獲取過程類似,因此我們的處理也與靜態資源的獲取差不多,檢索方法匹配,返回指定的數據。不過為了和靜態處理區分,我們定義專門的函數dynamic_process來進行動態數據處理。
private static void dynamic_process(HTTPServer.HttpProcess HttpProcess, string str) { string send_str = ""; if (HttpProcess.updata_status == false) { //解析ajax_methon處理方法 example: /html/AspGet.asp -> /html/aspget.asp -> aspget int PEnd = HttpProcess.hpr.url.IndexOf("."); int pStart = HttpProcess.hpr.url.LastIndexOf("/"); HttpProcess.hpr.dynamic_method = HttpProcess.hpr.url.Substring(pStart + 1, PEnd - pStart - 1);
//...... }
//......
//get模式為false,直接執行 if (HttpProcess.updata_status == false) { switch (HttpProcess.hpr.dynamic_method) { case "aspapply": send_str = create_correct_head(HttpProcess, System.Text.Encoding.UTF8.GetBytes(str).Length); send_str += str; break; case "aspget": send_str = create_correct_head(HttpProcess, HTTPServer.setup.Length); send_str += HTTPServer.setup; break; default: break; }
//處理后數據返回 byte[] send_byte = new byte[send_str.Length]; send_byte = Encoding.UTF8.GetBytes(send_str); HttpProcess.bs = send_byte; } }
對於get方法,我們將指定請求的數據處理完后以UTF-8的格式回復,前端異步等待后就可以接收到正確的數據。對於POST方法的處理,就是服務器動態通訊實現的核心,與在線升級時的實現類似,重點檢索的有
1.首字段
包含url,http方法,以及對應的dynamic方法
2.Content-Length
提交的數據長度
3.Data
提交的數據
不過考慮到TCP包分片的情況,還要在http層添加循環,實現多數據包的接收處理,才能滿足動態通訊的需求。
1. 多tcp分片的接收.
//考慮到Post提交時長度較長時tcp層分片發送,需要等待所有包發送完在處理 do { //獲得當前Socket連接傳輸的數據,並轉換為utf-8格式 length = CurrentSocket.Receive(recvBytes, recvBytes.Length, 0); recvStr = Encoding.UTF8.GetString(recvBytes, 0, length); Console.WriteLine(recvStr); //http引擎處理,返回獲得數據 http_engine(recvStr, length, hpc); }while(hpc.updata_status);
2.tcp首個數據包處理, 解析Ajax方法,解析獲得接收數據長度,接收數據長度統計,接收狀態判斷,發送數據生成等
//ajax動態數據處理 private static void dynamic_process(HTTPServer.HttpProcess HttpProcess, string str) { string send_str = ""; if (HttpProcess.updata_status == false) { //解析ajax_methon處理方法 int PEnd = HttpProcess.hpr.url.IndexOf("."); int pStart = HttpProcess.hpr.url.LastIndexOf("/"); HttpProcess.hpr.dynamic_method = HttpProcess.hpr.url.Substring(pStart + 1, PEnd - pStart - 1); if (HttpProcess.hpr.http_method == "post") { //解析Content-Length 獲得接收數據正文總長度 PEnd = str.IndexOf("Content-Length:"); str = str.Substring(PEnd); PEnd = str.IndexOf("\r\n"); string str_len = str.Substring(16, PEnd - 16); //獲得接收數據總長度! HttpProcess.total_len = int.Parse(str_len); PEnd = str.IndexOf("\r\n\r\n"); HttpProcess.updata_status = true; //右移4位,獲得正文首字符位置 \r\n\r\n str = str.Substring(PEnd + 4);
//統計接收數據長度 //對於可能的中文輸入,直接用str.length獲得的長度與Content-Length不符合 //因此取長度用System.Text.Encoding.UTF8.GetBytes(str).Length;中英文獲得的長度都正確 HttpProcess.recv_len += System.Text.Encoding.UTF8.GetBytes(str).Length; } } else { HttpProcess.recv_len += System.Text.Encoding.UTF8.GetBytes(str).Length; }
//接收數據長度和實際長度相同時,表示接收完畢,可以處理 if (HttpProcess.recv_len == HttpProcess.total_len) { HttpProcess.updata_status = false; } if (HttpProcess.updata_status == false) { switch (HttpProcess.hpr.dynamic_method) { case "aspapply":
//Post方法,將獲得數據返回給客戶端 send_str = create_correct_head(HttpProcess, System.Text.Encoding.UTF8.GetBytes(str).Length); send_str += str; break; case "aspget": send_str = create_correct_head(HttpProcess, HTTPServer.setup.Length); //get方法,將服務器內部數據提交返回 send_str += HTTPServer.setup; break; default: break; } byte[] send_byte = new byte[send_str.Length]; send_byte = Encoding.UTF8.GetBytes(send_str); Console.WriteLine(send_str); HttpProcess.bs = send_byte; } }
實驗效果,直接刷新網頁:
Apply提交數據后:
到此為止,一個簡單的基於C#的web服務器全部功能就實現了,它包含靜態網頁,圖片以及js,css文件的讀取和基於Ajax的動態通訊實現,如果有一定的socket通訊知識基礎以及對前后端知識的了解,整個實現過程並不復雜,不過相對於正常工作的服務器,本例中的實現只能說是簡單的框架,需要完善很多細節和功能。
具體代碼參考:http://files.cnblogs.com/files/zc110747/webserver-2.zip
相關資料參考:
1. 司徒正美 《JavaScript 框架設計》第13章 數據交互模塊
2. xingoo 【前端開發系列】—— 別說你不會Ajax