zoukankan      html  css  js  c++  java
  • 高性能网站建设指南

    高性能网站建设指南---前端工程师技能精髓

    题记:无意间在公司图书馆看到这本书,感觉内容写得很不错,很细很有条理。虽然自己并非前端工程师,然而也需要对此有所了解,供以后在架构设计、系统优化时考虑这些因素,特在此将对该书进行摘录,供不时之需。也希望有更多的同行能够了解到这本书,进而提升自己所负责网站的展现速度,给与用户更好地访问体验。

          只有10-20%的最终用户响应时间花在了下载HTML文档上。其余的80-90%时间花在了下载页面中的所有组件上。

    HTTP概述
    1. 压缩
    2. 条件GET请求
    3. Expire
    4. Keep-Alive
    规则1、减少HTTP请求
    1. 图片地图:将多个图片合并成一个,而后通过css定位显示不同的位置
    2. CSS Sprites:同上
    3. 内联图片
    4. 合并脚本和样式表
    规则2、使用内容发布网络(CDN,Content Delivery Network)
    规则3、添加Expires头
    1. Expires头:在这一日期/时间之后,响应将被认为是无效的
    2. Max-Age: 设置相对有效时间Cache-Control:max-age=100
    3. 空缓存VS完整缓存:尽可能将页面内容放入大缓存中
    4. 不仅仅是图片:图片应该使用缓存,但这一最佳实践不应该仅限于图片
    5. 修订文件名:为能够获取最新的文件,最有效的解决方案是修改其所有链接,这样,全新的请求将从原始服务器下载最新的内容。
    规则4、压缩组件
    1. gzip:Accept-Encoding:gzip,deflate。gzip是目前最流行和最有效的压缩方法,是最理想的压缩方法
    2. 压缩内容:压缩脚本和样式表是非常值得的,同时还有XML和JSON在内的文本。图片和PDF不应该压缩,因为它们本来就已经被压缩了。
    3. 节省率:压缩通常能将响应的数据量减少将近70%
    4. gzip配置:不同的web服务器有不同的gzip配置方式,但大多支持gzip
    5. 代理缓存:Web服务器基于Accept-Encoding来检测是否对响应进行压缩。不管是否压缩过,浏览器都会基于响应中的其他HTTP头如Expires和Cache-Control来缓存响应。由于压缩的决定是基于Accept-Encoding请求头的,因此需要在服务器的Vary响应头中包含Accept-Encoding。
    6. 边缘情形:服务器和客户端的压缩对等性看似简单,但必须正确才行。无论是客户端还是服务器发生错误(发送压缩内容到不支持它的客户端、忘记将压缩内容声明为已经进行了gzip编码等),页面都会被破坏。错误并不会经常发生,但它们是必须考虑的边缘情形(Edge Case)。这种情况虽然可以通过浏览器白名单方式解决,但设置浏览器白名单的指令过于复杂,无法使用HTTP头进行编码。最佳做法是将User-Agent作为代理的另外一种判断标准添加到Vary头中Vary:Accept-Encoding,User-Agent
    规则5、将样式表放在顶部

          规则5对于加载页面所需的实际时间没有太多影响,它影响更多的是浏览器对这些组件顺序的反应。为避免当样式变化时重绘页面中的元素,浏览器会等待位于底部的样式表加载完成后才会呈现,这时浏览器会延迟任何可视化组件。实际上,用户感觉缓慢的页面反而是可视化组建加载得更快的页面。使用LINK标签将样式表放在文档的HEAD中可以解决该问题。

         在使用样式表时,页面逐步呈现会被阻止,直到所有的样式表下载完成。将样式表移到文档head中,这样就能首先下载它们而不会阻止页面呈现。

    规则6、将脚本放在底部
    1. 并行下载 对响应时间影响最大的是页面中组件的数量,如果一个Web页面平均地将其组件分别放在两个主机名下,整体响应时间将可以减少大约一半。但并行下载数量并不是越多越快,因为增加并行下载数量是有开销的,其优劣取决于带宽和cpu速度。
    2. 脚本阻塞下载 并行下载组件的优点是很明显的,然而,在下载脚本时并行下载实际上被禁用的。即使使用了不同的主机名,浏览器也不会启动其他的下载。其中一个原因是,脚本可能使用document.write来修改页面内容,因此浏览器会等待,已确保页面能够恰当地布局。另一个原因是为了保证脚本能够按照正确的顺序执行。

         在使用脚本时,对于所有位于脚本以下的内容,逐步呈现都被阻塞了。将脚本放在页面越靠下的地方,意味着越多的内容能逐步地呈现。

    规则7、避免css表达式
    规则8、使用外部JavaScript和Css
    1. 纯碎而言,内联快一些
    2. 页面查看 每个用户产生的页面查看越少,内联JavaScript和Css的论据越强势。另一方面,如果普通用户能够产生很多的页面查看,浏览器很可能将(具有长久的Expires头的)外部文件放在其缓存中。
    3. 组件重用  如果你的网站中的每一个页面都使用了相同的JavaScript和Css,使用外部文件可以提高这些组件的重用率。在这种情况下使用外部文件更加具有优势,因为当用户在页面间浏览时,JavaScript和Css组件已经位于浏览器的缓存中了。
    4. 动态内联  如果主页服务器知道一个组件是否在浏览器的缓存中,它可以在内联和使用外部文件之间做出最佳选择。尽管服务器不能查看浏览器缓存中有些什么,但可以用cookies做指示器。如果cookie不存在,就内联了JavaScript和Css。如果cookie出现了,则有可能外部组件位于浏览器的缓存中,并使用了外部文件。
    规则9、减少DNS查找

         Internet是通过IP地址来查找服务器的。由于IP地址很难记忆,通常使用包含主机名的URL来取代它,但当浏览器发送其请求时,IP地址仍然是必需的。这就是Domain Name System(DNS) 所处的角色。DNS也有开销,通常浏览器查找一个给定的主机名的IP地址要花费20-120毫秒。响应时间依赖于DNS解析器(通常由你的ISP提供)、它所承担的请求压力、你与它之间的距离和你的带宽速度。

    1. DNS缓存  DNS查找可以被缓存起来提高性能。这种缓存可以发生在由你的ISP或局域网中的一台特殊的缓存服务器上,但这里探讨的是发生在独立用户的计算机上的DNS缓存。在用户请求了一个主机名之后,DNS信息会留在操作系统的DNS缓存中,之后对于该主机名的请求将无需进行过多的DNS查找,至少短时间内不需要。·
    2. 影响DNS缓存的因素 首先,服务器可以表明记录可以被缓存多久。查找返回的DNS记录包含了一个存活时间(Time-to-live,TTL)值。该值告诉客户端可以对该记录缓存多久,尽管操作系统缓存会考虑TTL值,但浏览器通常忽略该值,并设置它自己的时间限制。另外浏览器对缓存的DNS记录的数量也有限制,TTL设置值通常是1天。
    3. 减少DNS查找  当客户端的DNS缓存为空(浏览器和操作系统都是)时,DNS查找的数量与Web页面中唯一主机名的数量相等。这包括页面URL、图片、脚本文件、样式表、Flash对象等的主机名。减少唯一主机名的数量就可以就可以减少DNS查找的数量。
    4. 通过使用Keep-Alive和较少的域名来减少DNS查找
    规则10、精简JavaScript

          JavaScript作为一门解释型语言,是构建Web页面的首选。当以快速原型为基准开发用户界面时,解释语言要优于其他语言。

    1. 精简 是从代码中移除不必要的字符以减少其大小,进而改善加载时间的实践。在代码被精简后,所有的注释以及不必要的空白字符(空格、换行和制表符)都将被移除。对于JavaScript而言,这可以改善响应时间效率,因为需要下载的文件大小减少了。精简JavaScript最流行的工具是JSMin。
    2. 混淆  是可以应用在源代码上的另外一种优化方式。和精简一样,它也会移除注释和空白,同时它还会改写代码。作为改写的一部分,函数和变量的名字将被转换为更短的字符串,这时的代码更加精炼。但是会带来三个弊端:可能引入错误、增加调试难度、需要对JavaScript关键字标记
    3. 内联脚本  内联的JavaScript块也应该精简
    规则11、避免重定向

         重定向用于将用户从一个URL重新路由到另一个URL,种类有很多,常用的是301和302。它是损伤性能的,可以采用Alias、mod_rewirte、DirectorySlash和直接连接代码来避免重定向。

    规则12、移除重复脚本
    1. 重复脚本--确有其事 开发一个网站需要极大数量的资源,除了核心团队要构建网站外,其他团队也会向页面贡献HTML代码。由于来自不同团队的很多人都要向页面中添加HTML,很容易想到相同的脚本可能会被添加多次
    2. 重复脚本伤害性能 引起不必要的HTTP请求和执行JavaScript所消耗的时间
    规则13、配置ETag

        实体标签(Entity Tag,ETag)是Web服务器和浏览器用于确认缓存组件的有效性的一种机制。减少呈现页面时所必需的HTTP请求的数量是加速用户体验的最佳方式。可以通过最大化浏览器缓存组件的能力来实现这一目标,但当网站被宿主在多于一台服务器上时,ETag头可能会阻碍缓存。

        ETag带来的问题 ETag的问题在于,通常使用组件的某些属性来构造它,这些属性对于特定的、寄宿了网站的服务器来说是唯一的。当浏览器从一台服务器上获取了原始组件,之后又向另外一台不同的服务器发起条件GET请求时,ETag是不会匹配的----而对于使用服务器集群来处理请求的网站来说,这是很常见的一种情况。默认情况下,对于拥有多台服务器的网站,Apache和IIS向ETag中嵌入的数据都会大大地降低有效性验证的成功率。

       解决该问题的两种方式:选择ETag的配置方式或者直接移除ETag

    规则14、使Ajax可缓存

        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
    都写完以后,我们需要对IHttpAsyncHandler接口进行实现:
    #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
    由于代码比较简单,我这里不做过多的讲解,逻辑顺序就是,通过BeginProcessRequest方法处理所有传入的异步执行对象,然后执行;执行完毕之后,通过IAsyncResult接口打印数据到前台去。
     
    上面的是服务端部分,接口写完后,如何调用呢?方法很简单,我直接贴出前台代码:
    <%@ 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;  435px;float:left;border:1px solid #B7D6BF;">
            <div style="height:30px;100%;float:left;background:url('images/navEx.gif') repeat-x;line-height:30px;text-align:center;"><strong>服务端接收数据</strong></div>
            <div style="float:left;100%;height:310px;border-bottom:1px solid #B7D6BF; overflow-x:auto;" id="msg"></div>
            <div style="float:left;100%;height:50px;text-align:right;line-height:50px;">
                绑定的本机端口:<input id="txtPort" type="text" style="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;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);
            }
        }
    }

    效果展示

    好了,我们开始运行程序,并且同时打开两个窗口,一个是客户端,一个是服务器端,看看效果吧:

    QQ截图20131024193037

    源码下载

    点击这里下载

  • 相关阅读:
    php,asp.net web开发B/S编程语言函数|参数|使用手册
    [share]深入探讨PHP中的内存管理问题
    Nginx环境下Php安装
    熟悉iWebSNS开发规约
    php判断浏览器和语言
    PHP实现动态生成饼状图、柱状图和折线图(转)
    cloudServer Amazon EC2 / AWS / SWS / yunjisuan / yunfuwu / yuncunchu
    read_humor_video
    pics
    JAVA XML / dom / jdom / dom4j / sax / stax / StAX Stream / StAX Event
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3386975.html
Copyright © 2011-2022 走看看