题记:无意间在公司图书馆看到这本书,感觉内容写得很不错,很细很有条理。虽然自己并非前端工程师,然而也需要对此有所了解,供以后在架构设计、系统优化时考虑这些因素,特在此将对该书进行摘录,供不时之需。也希望有更多的同行能够了解到这本书,进而提升自己所负责网站的展现速度,给与用户更好地访问体验。
规则5对于加载页面所需的实际时间没有太多影响,它影响更多的是浏览器对这些组件顺序的反应。为避免当样式变化时重绘页面中的元素,浏览器会等待位于底部的样式表加载完成后才会呈现,这时浏览器会延迟任何可视化组件。实际上,用户感觉缓慢的页面反而是可视化组建加载得更快的页面。使用LINK标签将样式表放在文档的HEAD中可以解决该问题。
在使用样式表时,页面逐步呈现会被阻止,直到所有的样式表下载完成。将样式表移到文档head中,这样就能首先下载它们而不会阻止页面呈现。
在使用脚本时,对于所有位于脚本以下的内容,逐步呈现都被阻塞了。将脚本放在页面越靠下的地方,意味着越多的内容能逐步地呈现。
Internet是通过IP地址来查找服务器的。由于IP地址很难记忆,通常使用包含主机名的URL来取代它,但当浏览器发送其请求时,IP地址仍然是必需的。这就是Domain Name System(DNS) 所处的角色。DNS也有开销,通常浏览器查找一个给定的主机名的IP地址要花费20-120毫秒。响应时间依赖于DNS解析器(通常由你的ISP提供)、它所承担的请求压力、你与它之间的距离和你的带宽速度。
JavaScript作为一门解释型语言,是构建Web页面的首选。当以快速原型为基准开发用户界面时,解释语言要优于其他语言。
重定向用于将用户从一个URL重新路由到另一个URL,种类有很多,常用的是301和302。它是损伤性能的,可以采用Alias、mod_rewirte、DirectorySlash和直接连接代码来避免重定向。
实体标签(Entity Tag,ETag)是Web服务器和浏览器用于确认缓存组件的有效性的一种机制。减少呈现页面时所必需的HTTP请求的数量是加速用户体验的最佳方式。可以通过最大化浏览器缓存组件的能力来实现这一目标,但当网站被宿主在多于一台服务器上时,ETag头可能会阻碍缓存。
ETag带来的问题 ETag的问题在于,通常使用组件的某些属性来构造它,这些属性对于特定的、寄宿了网站的服务器来说是唯一的。当浏览器从一台服务器上获取了原始组件,之后又向另外一台不同的服务器发起条件GET请求时,ETag是不会匹配的----而对于使用服务器集群来处理请求的网站来说,这是很常见的一种情况。默认情况下,对于拥有多台服务器的网站,Apache和IIS向ETag中嵌入的数据都会大大地降低有效性验证的成功率。
解决该问题的两种方式:选择ETag的配置方式或者直接移除ETag
Ajax表示异步JavaScript和XML(Asynchronous JavaScript and XML),尽管今天除了XML有很多其他选择,最著名的是JSON。Ajax的目的是为了突破Web本质的开始--停止交互方式。向用户显示一个白屏然后重绘整个页面不是一种后的用户体验。而Ajax在UI和Web服务器之间插入了一层。这个Ajax层位于客户端,与Web服务器进行交互以获取请求的信息,并与表现层交互,仅更新哪些必要的组件。它将Web体验从“浏览页面”转变为“与应用程序进行交互”。
Ajax的一个明显优点是向用户提供了及时反馈,因为它异步地从后端Web服务器请求信息。但Ajax并不保证用户就不会一边玩弄自己的手指一边等着“异步JavaScript和XML”返回响应,记住“异步”并没有暗示“即时”,这一点很重要。用户是否需要等待的关键因素在于Ajax请求是被动的还是主动的。被动请求是为了将来使用而预先发起的。主动请求是基于用户当前的操作而发起的。
改善Ajax请求的最重要的方式就是使响应可缓存,前面第4、9、10、11、13原则也适用于此。
确保Ajax请求遵守性能指导,尤其应具有长久的Expires头。
很难把UDP和Asp.net扯到一起,但是由于最近项目中需要通过网页发送控制指令到中间件,再由中间件发送到下位机的需求。所以就研究了一下是否可以通过asp.net操控UDP Socket实现数据的收发,结果证明是可以的。
服务端代码逻辑
在这个实现过程中,其核心组件就是IHttpAsyncHandler接口,相信大家都明白IHttpHandler是来干什么用的,那么IHttpAsyncHandler就是其异步版本,可以实现操作的异步进行。并且由于这个接口提供了事件完成回调机制,所以能够非常方便的将执行结果打印到前台来。这个接口包含了两个比较有用的方法,一个是BeginProcessRequest,代表异步操作的开始,另外一个是EndProcessRequest,代表异步操作的结束。一般在使用的时候,我们需要结合IAsyncResult接口一起使用,以便能够非常方便的实现完成回调的通知。
下面主要来讲解一下代码逻辑。
首先,需要继承自IHttpAsyncHandler接口,这里暂时先不提供实现:
#region 异步执行请求
public class AsyncSocketHandler : IHttpAsyncHandler,IRequiresSessionState
{
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
//开始请求
}
public void EndProcessRequest(IAsyncResult result)
{
//请求结束
}
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{ }
}
#endregion
然后,我们最好能够引用IAsyncResult接口并提供其实现,以便于回传通知:
#region 返回异步执行结果
public class myAsyncResult : IAsyncResult
{
public HttpContext contex;
public AsyncCallback cb;
public object extraData;
public bool isCompleted = false;
public myAsyncResult(HttpContext contex, AsyncCallback cb, object extraData)
{
this.contex = contex;
this.cb = cb;
this.extraData = extraData;
}
public void send(string resultStr)
{
this.contex.Response.Write(resultStr);
}
public object AsyncState
{
get { return null; }
}
public System.Threading.WaitHandle AsyncWaitHandle
{
get { return null; }
}
public bool CompletedSynchronously
{
//在网络连接或者流读取中,这里需要设置为True,否则前台是不能显示接收数据的。
get { return true; }
}
public bool IsCompleted
{
get { return isCompleted; }
}
}
#endregion
上面的实现很简单,其实就是一个带参构造,然后利用Send方法向前台打印数据。
最后就是我们真正要运行在异步进程中的方法:
#region 异步执行对象
public static class myAsyncFunction
{
private static string resultResponse = string.Empty; //获取客户端数据
private static UdpClient udpClient;
private static IPEndPoint remoteEndPoint;
private static myAsyncResult asyncResult; //通知回传对象
// 把一个异步的请求对象传入以便于供操作
public static void Init(myAsyncResult result,int port)
{
asyncResult = result;
if (udpClient == null)
{
try
{
udpClient = new UdpClient(port);
}
catch (System.Net.Sockets.SocketException ex)
{
udpClient = null;
throw new Exception(ex.Message);
}
}
}
//接收客户端数据并将数据打印到前台
public static void AcceptClients()
{
byte[] bufferResult = udpClient.Receive(ref remoteEndPoint);
resultResponse = System.Text.Encoding.ASCII.GetString(bufferResult);
WriteLog("ReceivingClient: " + resultResponse);
asyncResult.send(resultResponse);
}
//数据接收完毕,服务器端回送消息给客户端
public static void ResponseClients()
{
string message = "Server has received your message.";
byte[] msgBytes = Encoding.Default.GetBytes(message);
IPEndPoint sEndPoint = new IPEndPoint(remoteEndPoint.Address, 10003);
UdpClient sClient = new UdpClient();
sClient.Connect(sEndPoint);
sClient.Send(msgBytes,msgBytes.Length);
}
//写日志
public static void WriteLog(string content)
{
try
{
string timeStamp = DateTime.Now.ToString("yyyyMMdd");
string filePath = "C:\\TestLog." + timeStamp + ".txt";
string contentAll = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + ":" + content + Environment.NewLine;
byte[] b = Encoding.Default.GetBytes(contentAll);
using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
{
fs.Write(b, 0, b.Length);
fs.Flush();
}
}
catch (Exception ex) { }
}
}
#endregion
#region 异步执行请求
public class AsyncSocketHandler : IHttpAsyncHandler,IRequiresSessionState
{
private static object obj = new object();
private static object objEx = new object();
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
lock (obj)
{
int port = Int32.Parse(context.Request.QueryString["port"].ToString());
myAsyncResult asyncResult = new myAsyncResult(context, cb, extraData); //实例
myAsyncFunction.Init(asyncResult, port); //接收所有传入的异步对象
myAsyncFunction.AcceptClients(); //处理所有传入的异步对象
asyncResult.isCompleted = true;
return asyncResult;
}
}
public void EndProcessRequest(IAsyncResult result)
{
myAsyncFunction.ResponseClients(); //服务端发送客户端的应答
}
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{ }
}
#endregion
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Server.aspx.cs" Inherits="SocketViaWeb.Server" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>UDP服务器端</title>
<style type="text/css">
body
{
font-size:12px;
}
</style>
<script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#btnListen").bind("click", function () {
setInterval(TriggerAjax, 1000);
//TriggerAjax();
});
});
var TriggerAjax = function () {
var port = $("#txtPort").val();
$.ajax({
type: "GET",
url: "AsyncSocketHandler.ashx?port=" + port,
dataType:"text",
success: function (result) {
//if (result != "") {
var myDate = new Date();
$("#msg").append(myDate.toLocaleString() + " : " + result).append("<br>");
//}
},
error: function (result) {
debugger;
}
});
}
</script>
</head>
<body>
<div style="height: 390px; width: 435px;float:left;border:1px solid #B7D6BF;">
<div style="height:30px;width:100%;float:left;background:url('images/navEx.gif') repeat-x;line-height:30px;text-align:center;"><strong>服务端接收数据</strong></div>
<div style="float:left;width:100%;height:310px;border-bottom:1px solid #B7D6BF; overflow-x:auto;" id="msg"></div>
<div style="float:left;width:100%;height:50px;text-align:right;line-height:50px;">
绑定的本机端口:<input id="txtPort" type="text" style="width:40px;border:none;border-bottom:1px solid black;" value="10004" />
<input id="btnListen" type="button" value="监听" style="border:none;background:url('images/btn.gif') no-repeat;width:50px;height:20px;color:White;" /></div>
</div>
</body>
</html>
在前台,我是通过一个轮询来每隔1秒钟检测一次UDP Socket连接,如果当前有数据传来,那么就会显示到页面上。这里就是服务端的所有部分。
客户端代码逻辑
至于客户端,我就不细说了,和正常的UDP socket发送的写法一样:
using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
namespace SocketViaWeb
{
public partial class Client : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
port = Int32.Parse(txtPort.Value);
endPoint = new IPEndPoint(IPAddress.Parse("192.168.0.100"), port);
if (client == null)
{
client = new UdpClient();
}
}
private int port;
private static UdpClient client;
private static IPEndPoint endPoint;
protected void btnListen_Click(object sender, EventArgs e)
{
SendContentViaUDP(txtMsg.Text);
}
private void SendContentViaUDP(string content)
{
client.Connect(endPoint);
byte[] byteToSend = Encoding.Default.GetBytes(content);
client.Send(byteToSend, byteToSend.Length);
}
}
}
效果展示
好了,我们开始运行程序,并且同时打开两个窗口,一个是客户端,一个是服务器端,看看效果吧:
源码下载
点击这里下载