TCP/IP协议学习(八) 基于C# Socket的Web服务器---动态通讯实现
目录
一个完整的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通讯知识基础以及对前后端知识的了解,整个实现过程并不复杂,不过相对于正常工作的服务器,本例中的实现只能说是简单的框架,需要完善很多细节和功能。
具体代码参考:https://files.cnblogs.com/files/zc110747/webserver-2.zip
相关资料参考:
1. 司徒正美 《JavaScript 框架设计》第13章 数据交互模块
2. xingoo 【前端开发系列】—— 别说你不会Ajax