zoukankan      html  css  js  c++  java
  • 优化网站设计(一):减少请求数

    优化网站设计(一):减少请求数

    前言

    网站设计的优化是一个很大的话题,有一些通用的原则,也有针对不同开发平台的一些建议。这方面的研究一直没有停止过,我在不同的场合也分享过这样的话题。

    作为通用的原则,雅虎的工程师团队曾经给出过35个最佳实践。这个列表请参考

    Best Practices for Speeding Up Your Web Site  http://developer.yahoo.com/performance/rules.html 

    同时,他们还发布了一个相应的测试工具Yslow http://developer.yahoo.com/yslow/

    我强烈推荐所有的网站开发人员都应该学习这些最佳实践,并结合自己的实际项目情况进行应用。

    接下来的一段时间,我将结合ASP.NET这个开发平台,针对这些原则,通过一个系列文章的形式,做些讲解和演绎,以帮助大家更好地理解这些原则,并且更好地使用他们。

    准备工作

    为了跟随我进行后续的学习,你需要准备如下的开发环境和工具

    1. Google Chrome 或者firefox ,并且安装 Yslow这个扩展组件.请注意,这个组件是雅虎提供的,但目前没有针对IE的版本。
      1. https://chrome.google.com/webstore/detail/yslow/ninejjcohidippngpapiilnmkgllmakh
      2. https://addons.mozilla.org/en-US/firefox/addon/yslow/
      3. 你应该对这些浏览器的开发人员工具有所了解,你可以通过按下F12键调出这个工具。
    2. Visaul Studio 2010 SP1 或更高版本,推荐使用Visual Studio 2012
      1. http://www.microsoft.com/visualstudio/eng/downloads 
    3. 你需要对ASP.NET的开发基本流程和核心技术有相当的了解,本系列文章很难对基础知识做普及。

    本文要谈讨论的话题

    这一篇文章讨论的是第一个原则:应该尽可能加减少请求数。这个原则的说明请参考 http://developer.yahoo.com/performance/rules.html#num_http

    我们的网页在加载的时候,为了提供更加丰富的内容和效果,除了页面本身这个请求之外,总是需要加载其他一些资源的,例如我们常见的Javascript文件,css文件,图片,甚至还会有一些Flash组件等等。这本无可厚非,但如果过多的外部请求,会很直接地降低页面加载的速度。

    我们来看一个例子吧。例如我们经常访问的博客园的首页

    image

    这个网页的加载需要多少次请求呢?(如果不考虑缓存的话)

    image

    我们看到,请求数为55个。我们进一步通过Yslow来分析,可以得到综合的报表

    imageimageimage

    应该说博客园的设计已经比较注意很多细节了。他们得到了B级的评分。我们再来看看其他一些主要的门户的表现吧(从左至右,依次是新浪,搜狐,腾讯,淘宝),他们都只得到D级的评分。

    【备注】这些评分只是作为一个参考,不做任何的结论和推论。

    imageimageimageimage

    如何减少请求数?

    我们可以通过如下的几个方法来减少请求数:

    1. 合并外部资源文件(如javascript,css,图片文件)
      1. 图片的合并,是将多个图片合并为一个图片,然后采用css的一些设置(background-image,background-position) 来使用它们。这个很简单实用(但是效果也是显著的)。本文将不做演示。
      2. javascript和css文件的合并,这个可能大家不太经常注意到。本文的后续部分将对此进行演示。
    2. 使用Inline images 这种方式这个方式可能依赖于浏览器的实现,目前并不是所有的浏览器都支持。所以本文也不做演示。

    合并javascript文件和css文件

    对于这两种文件的合并而言,我们当然可以手工去做(copy,paste),把一个文件的内容粘贴在另外一个文件内容的底部即可。但这种方式有几个缺点:

    1. 破坏了原有文件的结构
    2. 不同页面需要的文件组合可能是不一样的,这种情况下会需要产生多个不同的文件,而且需要比较小心地维护它们
    3. 如果文件的内容发生变化,就需要重新再来一次

    所以,我并不是很推荐用这种手工合并的方法,事实上,我们有更好的工具来实现, 并且在ASP.NET的一些框架(例如ASP.NET MVC)里面已经有了内置的实现。

    我们先来看一个例子,下面是一个典型的ASP.NET MVC项目

    image

    我找了其中的一个用户注册页面,在IE中进行查看

    image

    我们看到默认情况下,完成这个页面其实要执行8个请求。但经过简单的处理(添加一行代码)之后,我们可以看到如下的效果

    image

    而且如果你细心看的话,在这个页面中请求的javascript文件似乎看起来经过了特殊的处理(路径比较特殊)。那么,这是如何实现的呢?

    原来,在MVC项目中,默认会有一个所谓的BundleConfig的类,它提供了一个方法RegisterBundles,如下所示

    using System.Web;
    using System.Web.Optimization;
    
    namespace MvcApplication1
    {
        public class BundleConfig
        {
            // For more information on Bundling, visit http://go.microsoft.com/fwlink/?LinkId=254725
            public static void RegisterBundles(BundleCollection bundles)
            {
                bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                            "~/Scripts/jquery-{version}.js"));
    
                bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                            "~/Scripts/jquery-ui-{version}.js"));
    
                bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                            "~/Scripts/jquery.unobtrusive*",
                            "~/Scripts/jquery.validate*"));
    
                // Use the development version of Modernizr to develop with and learn from. Then, when you're
                // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
                bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                            "~/Scripts/modernizr-*"));
    
                bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
    
                bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                            "~/Content/themes/base/jquery.ui.core.css",
                            "~/Content/themes/base/jquery.ui.resizable.css",
                            "~/Content/themes/base/jquery.ui.selectable.css",
                            "~/Content/themes/base/jquery.ui.accordion.css",
                            "~/Content/themes/base/jquery.ui.autocomplete.css",
                            "~/Content/themes/base/jquery.ui.button.css",
                            "~/Content/themes/base/jquery.ui.dialog.css",
                            "~/Content/themes/base/jquery.ui.slider.css",
                            "~/Content/themes/base/jquery.ui.tabs.css",
                            "~/Content/themes/base/jquery.ui.datepicker.css",
                            "~/Content/themes/base/jquery.ui.progressbar.css",
                            "~/Content/themes/base/jquery.ui.theme.css"));
    
            }
        }
    }

    这个方法会在Global.asax文件中调用

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Http;
    using System.Web.Mvc;
    using System.Web.Optimization;
    using System.Web.Routing;
    
    namespace MvcApplication1
    {
        // Note: For instructions on enabling IIS6 or IIS7 classic mode, 
        // visit http://go.microsoft.com/?LinkId=9394801
    
        public class MvcApplication : System.Web.HttpApplication
        {
            protected void Application_Start()
            {
                AreaRegistration.RegisterAllAreas();
    
                WebApiConfig.Register(GlobalConfiguration.Configuration);
                FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
                RouteConfig.RegisterRoutes(RouteTable.Routes);
                BundleConfig.RegisterBundles(BundleTable.Bundles);
                //BundleTable.EnableOptimizations = false;//启用这一行,则使用Bundle的机制进行文件打包
                AuthConfig.RegisterAuth();
            }
        }
    }

    在具体的页面中,如果需要用到上述的脚本组合,则可以使用下面这样的语法来调用

    @Scripts.Render("~/bundles/jqueryval")

    这就是所有的秘密。

    那么,这个技术是不是只能使用在MVC中,在我们另外一种开发框架(ASP.NET Web Forms)中是否也可以使用呢?

    答案是:可以的。而且这个技术,确实是最早就是用在ASP.NET Web Forms里面,只不过,因为这方面的文档较少,所以可能大家用的不多而已。下面是我作为演示用的一个简单的ASP.NET Web Forms的项目:

    image

    我们看到,在页面中,我们添加了三个脚本引用,这样的话,自然在打开页面的时候,需要单独请求这三个脚本文件。

    image

    我们是否可以将这三个文件合并成一个请求呢?请跟随我来进行如下的操作

    首先,添加一个组件,这是微软官方发布的System.Web.Optimization,顾名思义,这就是为了优化网络开发用的

    image

    按照一般的惯例,我们在项目中添加一个文件,来进行Bundle的注册

    using System.Web.Optimization;
    
    namespace WebApplication1
    {
        public class BundleConfig
        {
            public static void RegisterBundle(BundleCollection config)
            {
                config.Add(new ScriptBundle("~/default").Include("~/scripts/jquery-2.0.0.min.js", "~/scripts/knockout-2.2.1.js", "~/default.js"));
            }
        }
    }

    然后,我们修改Global.asax文件,添加如下的代码

    using System;
    using System.Web.Optimization;
    
    namespace WebApplication1
    {
        public class Global : System.Web.HttpApplication
        {
    
            protected void Application_Start(object sender, EventArgs e)
            {
                BundleConfig.RegisterBundle(BundleTable.Bundles);
                BundleTable.EnableOptimizations = true;
            }
    
        }
    }

    最后,我们在页面上也做相应的修改,如下所示(请注意粗体部分)

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1.Default" %>
    <%@ Import Namespace="System.Web.Optimization" %>
    
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
        <%= Scripts.Render("~/default") %>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
    </html>
    

    很不错,我们现在可以查看一下页面运行起来的效果

    image

    很明显,原先的三个请求,现在变成了一个请求。需要注意的是,如果我们去计算文件大小,这个合并之后的文件,体积会比之前三个文件体积总和略小一些。所以你可以理解为这里存在一定的压缩,但这个压缩比是不大的(尤其是原有的javascript文件本身就经过了压缩的情况下)。关于javascript文件或者css文件的压缩,后续会有专门的文章介绍。

    上面的例子,演示的是javascript文件的合并。其实,css文件的合并也是类似的做法,区别在于要使用StyleBundle : http://msdn.microsoft.com/en-us/library/system.web.optimization.stylebundle.aspx

    总结

    本文介绍了网站优化的第一个原则(尽量减少请求数),我带领大家分析了为什么需要考虑这个原则,以及具体如何实现(包括在MVC和Web Forms的做法)

     
    分类: 网络开发和设计
    离上一个版本,过了好久好久了。
    V1.0时,叫:CYQ.IISLogViewer。
     
    V2.0时,给了个中文名,叫:点格网站日志分析器V2.0

     
    升级到3.0了,给改了个名字,叫:秋式网站日志分析器V3.0
     
     
     

    本次版本升级要点:

     
    1:整体升级,避免线程冲突引发导致软件自动退出的问题。
    2:分析格式升级,再精准分析IIS日志。
     
    3:支持Linux下的IIS日志。
     
    4:增加IP分析。
     
    5:增加360搜索引擎的支持。
     
     

    下面请看截图说明:

     
     

    1:运行的界面,通过点击“单个文件”或“指文件夹”选择相应的日志文件或路径,加载后,系统这里会自动保存路径,方便下次启动时还原路径,然后点击汇总统计,可以看到汇总,如下图:

     
     
     

    2:切换到查看明细:点击一个日志文件,可以查看状态码,根据状态码分析是一项比较重要的手段,这里给出状态码的次数和说明,更详细的状态码介绍,可以点帮助菜单下的“状态码帮助”。

     
     
     

    3:双击状态码统计的任意一行的单元格,会出来状态码明细,这里可以根据明细再细分各搜索引擎的情况,进一分分析情况。

     
     
     
     

    4:在查看明细的“24小时”里,可以看到24小时段的访问情况,从这里可以看到搜索引擎的频繁,分析搜索引擎在哪个时段来的多一些,做一些优化。

     
     
     

    5:同样的,双击“24小时”统计里行里的单元格,也会出来“24小时明细”,可以更精准的看到每个请求的链接。

     
     
     
     

    6:本次版本新增加了“IP”的统计,通过这个统计,你可以看到搜索引擎的请求IP和访问次数和请求头等信息,同时,对于非搜索引擎,如果IP的访问数太高,可以分析是不是网站可能被攻击,或者被采集,应该做点事了。

     
     
     

    7:还是一样,双击单元格,会出来明细,从明细时在,可以看到每个IP具体访问的页面和时间,如果一个IP访问同一个页面多次,初步可以分析为被攻击了,如果一个IP每个页面都访问1次,初步可以分析这个IP正在采集你的文章信息。

     
     
     

    8:本版本做了Linux下的IIS日志的兼容处理,在默认情况下,会智能的根据某些特征分析对应的字段,如果你发现默认的智能字段对应不上,可以“勾Linux IIS 配置”,复制一行IIS日志到文本框里,然后点格式化,会出来对应的数字和值,然后修正右边的字段和对应数字,点保存即可。

     
     
     

    秋式网站日志分析器[IISLogViewer] V3版本的介绍就这里了,若遇到问题或Bug,欢迎留言。

     
     
    版权声明:本文原创发表于 博客园,作者为 路过秋天 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
     
    离上一个版本,过了好久好久了。
    V1.0时,叫:CYQ.IISLogViewer。
     
    V2.0时,给了个中文名,叫:点格网站日志分析器V2.0

     
    升级到3.0了,给改了个名字,叫:秋式网站日志分析器V3.0
     
     
     

    本次版本升级要点:

     
    1:整体升级,避免线程冲突引发导致软件自动退出的问题。
    2:分析格式升级,再精准分析IIS日志。
     
    3:支持Linux下的IIS日志。
     
    4:增加IP分析。
     
    5:增加360搜索引擎的支持。
     
     

    下面请看截图说明:

     
     

    1:运行的界面,通过点击“单个文件”或“指文件夹”选择相应的日志文件或路径,加载后,系统这里会自动保存路径,方便下次启动时还原路径,然后点击汇总统计,可以看到汇总,如下图:

     
     
     

    2:切换到查看明细:点击一个日志文件,可以查看状态码,根据状态码分析是一项比较重要的手段,这里给出状态码的次数和说明,更详细的状态码介绍,可以点帮助菜单下的“状态码帮助”。

     
     
     

    3:双击状态码统计的任意一行的单元格,会出来状态码明细,这里可以根据明细再细分各搜索引擎的情况,进一分分析情况。

     
     
     
     

    4:在查看明细的“24小时”里,可以看到24小时段的访问情况,从这里可以看到搜索引擎的频繁,分析搜索引擎在哪个时段来的多一些,做一些优化。

     
     
     

    5:同样的,双击“24小时”统计里行里的单元格,也会出来“24小时明细”,可以更精准的看到每个请求的链接。

     
     
     
     

    6:本次版本新增加了“IP”的统计,通过这个统计,你可以看到搜索引擎的请求IP和访问次数和请求头等信息,同时,对于非搜索引擎,如果IP的访问数太高,可以分析是不是网站可能被攻击,或者被采集,应该做点事了。

     
     
     

    7:还是一样,双击单元格,会出来明细,从明细时在,可以看到每个IP具体访问的页面和时间,如果一个IP访问同一个页面多次,初步可以分析为被攻击了,如果一个IP每个页面都访问1次,初步可以分析这个IP正在采集你的文章信息。

     
     
     

    8:本版本做了Linux下的IIS日志的兼容处理,在默认情况下,会智能的根据某些特征分析对应的字段,如果你发现默认的智能字段对应不上,可以“勾Linux IIS 配置”,复制一行IIS日志到文本框里,然后点格式化,会出来对应的数字和值,然后修正右边的字段和对应数字,点保存即可。

     
     
     

    秋式网站日志分析器[IISLogViewer] V3版本的介绍就这里了,若遇到问题或Bug,欢迎留言。

     
     
    版权声明:本文原创发表于 博客园,作者为 路过秋天 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
     
    离上一个版本,过了好久好久了。
    V1.0时,叫:CYQ.IISLogViewer。
     
    V2.0时,给了个中文名,叫:点格网站日志分析器V2.0

     
    升级到3.0了,给改了个名字,叫:秋式网站日志分析器V3.0
     
     
     

    本次版本升级要点:

     
    1:整体升级,避免线程冲突引发导致软件自动退出的问题。
    2:分析格式升级,再精准分析IIS日志。
     
    3:支持Linux下的IIS日志。
     
    4:增加IP分析。
     
    5:增加360搜索引擎的支持。
     
     

    下面请看截图说明:

     
     

    1:运行的界面,通过点击“单个文件”或“指文件夹”选择相应的日志文件或路径,加载后,系统这里会自动保存路径,方便下次启动时还原路径,然后点击汇总统计,可以看到汇总,如下图:

     
     
     

    2:切换到查看明细:点击一个日志文件,可以查看状态码,根据状态码分析是一项比较重要的手段,这里给出状态码的次数和说明,更详细的状态码介绍,可以点帮助菜单下的“状态码帮助”。

     
     
     

    3:双击状态码统计的任意一行的单元格,会出来状态码明细,这里可以根据明细再细分各搜索引擎的情况,进一分分析情况。

     
     
     
     

    4:在查看明细的“24小时”里,可以看到24小时段的访问情况,从这里可以看到搜索引擎的频繁,分析搜索引擎在哪个时段来的多一些,做一些优化。

     
     
     

    5:同样的,双击“24小时”统计里行里的单元格,也会出来“24小时明细”,可以更精准的看到每个请求的链接。

     
     
     
     

    6:本次版本新增加了“IP”的统计,通过这个统计,你可以看到搜索引擎的请求IP和访问次数和请求头等信息,同时,对于非搜索引擎,如果IP的访问数太高,可以分析是不是网站可能被攻击,或者被采集,应该做点事了。

     
     
     

    7:还是一样,双击单元格,会出来明细,从明细时在,可以看到每个IP具体访问的页面和时间,如果一个IP访问同一个页面多次,初步可以分析为被攻击了,如果一个IP每个页面都访问1次,初步可以分析这个IP正在采集你的文章信息。

     
     
     

    8:本版本做了Linux下的IIS日志的兼容处理,在默认情况下,会智能的根据某些特征分析对应的字段,如果你发现默认的智能字段对应不上,可以“勾Linux IIS 配置”,复制一行IIS日志到文本框里,然后点格式化,会出来对应的数字和值,然后修正右边的字段和对应数字,点保存即可。

     
     
     

    秋式网站日志分析器[IISLogViewer] V3版本的介绍就这里了,若遇到问题或Bug,欢迎留言。

     
     
    版权声明:本文原创发表于 博客园,作者为 路过秋天 本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
     

    ZMQ和MessagePack的简单使用

      近段日子在做一个比较复杂的项目,其中用到了开源软件ZMQ和MessagePack。ZMQ对底层网络通信进行了封装,是一个消息处理队列库,使用起来非常方便。MessagePack是一个基于二进制的对象序列化类库,具有跨语言的特性,同样非常容易使用。在我做的项目中,消息类通过MessagePack进行压包,然后写入ZMQ的消息结构体,通过ZMQ传递,最后接收者利用MessagePack进行解包,从而分析命令。由于我英语水平实在不高,所以我并没有通过阅读它们的说明文档来对它们进行了解,而仅仅是通过它们的示例代码进行探索。虽然因此遇到了一些不解问题,但这种方式却为我节省了很多时间。不过,对于英语好的人,还是应该通过阅读说明文档来去了解它们。

      为了说明如何使用它们,在这里构造一个使用场景:有N个Client,一个Server,M个Agent,Client使用ZMQ的请求-响应模式和Server通信,Server收到Client的命令后,通过ZMQ的发布-订阅模式与各个Agent进行通信。下面的代码封装并使用了ZMQ和MessagePack,为了简便,我把类的定义和实现都写在了头文件。

      1.对ZMQ的简单封装:

    复制代码
      1 #include"Msgpack.h"
      2 #include<zmq.h>
      3 #include<string>
      4 #include<cassert>
      5 #include<iostream>
      6 
      7 namespace Tool
      8 {
      9     //网络工具类
     10     class Network
     11     {
     12     public:
     13 
     14         // 功能 :构造函数。
     15         // 参数 :无。
     16         // 返回 :无。
     17         Network() : m_socket(NULL) { }
     18 
     19         // 功能 :初始化socket。
     20         // 参数 :zmqType表示ZMQ的模式,address表示socket绑定或连接地址。
     21         // 返回 :true表示初始化成功,false表示失败。
     22         bool Init(int zmqType,const std::string& address)
     23         {
     24             try
     25             {
     26                 m_socket = zmq_socket(Context,zmqType);
     27                 return SetSocket(zmqType,address);
     28             }
     29             catch(...)
     30             {
     31                 std::cout << "Network初始化失败。" << std::endl;
     32                 return false;
     33             }
     34         }
     35 
     36         // 功能 :发送消息。
     37         // 参数 :指向Msgpack的指针,isRelease如果为true表示发送消息后即刻释放资源。
     38         // 返回 :true表示发送成功,false表示发送失败。
     39         bool SendMessage(Msgpack *msgpack,bool isRelease = true) const
     40         {
     41             try
     42             {
     43                 zmq_msg_t msg;
     44                 zmq_msg_init(&msg);
     45                 if(isRelease)
     46                 {
     47                     zmq_msg_init_data(&msg,msgpack->GetSbuf().data(),msgpack->GetSbuf().size(),Tool::Network::Release,msgpack);
     48                 }
     49                 else
     50                 {
     51                     zmq_msg_init_data(&msg,msgpack->GetSbuf().data(),msgpack->GetSbuf().size(),0,0);
     52                 }
     53                 zmq_msg_send(&msg,m_socket,0);
     54                 return true;
     55             }
     56             catch(...)
     57             {
     58                 std::cout << "Network发送失败。" << std::endl;
     59                 return false;
     60             }
     61         }
     62 
     63         // 功能 :接收消息。
     64         // 参数 :无。
     65         // 返回 :指向消息的指针。
     66         zmq_msg_t* ReceiveMessage() const
     67         {
     68             zmq_msg_t *reply = NULL;
     69             try
     70             {
     71                 reply = new zmq_msg_t();
     72                 zmq_msg_init(reply);
     73                 zmq_msg_recv(reply,m_socket,0);
     74                 return reply;
     75             }
     76             catch(...)
     77             {
     78                 if( reply != NULL )
     79                 {
     80                     delete reply;
     81                 }
     82                 return NULL;
     83             }
     84         }
     85 
     86         // 功能 :关闭消息。
     87         // 参数 :指向消息的指针。
     88         // 返回 :无。
     89         void CloseMsg(zmq_msg_t* msg)
     90         {
     91             try
     92             {
     93                 zmq_msg_close(msg);
     94                 msg = NULL;
     95             }
     96             catch(...)
     97             {
     98                 msg = NULL;
     99             }
    100         }
    101 
    102         // 功能 :析构函数。
    103         // 参数 :无。
    104         // 返回 :无。
    105         ~Network()
    106         {
    107             if( m_socket != NULL )
    108             {
    109                 zmq_close(m_socket);
    110                 m_socket = NULL;
    111             }
    112         }
    113 
    114     private:
    115 
    116         //通信socket
    117         void *m_socket;
    118 
    119         //网络环境
    120         static void *Context;
    121 
    122     private:
    123 
    124         // 功能 :设置socket。
    125         // 参数 :zmqType表示ZMQ的模式,address表示socket绑定或连接地址。
    126         // 返回 :true表示设置成功,false表示设置失败。
    127         bool SetSocket(int zmqType,const std::string& address)
    128         {
    129             int result = -1;
    130             switch(zmqType)
    131             {
    132             case ZMQ_REP:
    133             case ZMQ_PUB:
    134                 result = zmq_bind(m_socket,address.c_str());
    135                 break;
    136             case ZMQ_REQ:
    137                 result = zmq_connect(m_socket,address.c_str());
    138                 break;
    139             case ZMQ_SUB:
    140                 result = zmq_connect(m_socket,address.c_str());
    141                 assert(result == 0);
    142                 result = zmq_setsockopt(m_socket,ZMQ_SUBSCRIBE,"",0);
    143                 break;
    144             default:
    145                 return false;
    146             }
    147             assert( result == 0 );
    148             return true;
    149         }
    150 
    151         // 功能 :发送完消息后,释放消息资源。
    152         // 参数 :function为函数地址,hint指向要释放资源的对象。
    153         // 返回 :无。
    154         static void Release(void *function, void *hint)
    155         {
    156             Msgpack *msgpack = (Msgpack*)hint;
    157             if( msgpack != NULL )
    158             {
    159                 delete msgpack;
    160                 msgpack = NULL;
    161             }
    162         }
    163     };
    164 
    165     //整个程序共用一个context
    166     void *Tool::Network::Context = zmq_ctx_new();
    167 };
    复制代码

      说明:

      (1)由zmq_ctx_new创建出来的Context,整个应用程序共用一个就可以了,具体的通信是由zmq_socket创建的socket来完成的。上述代码中没有去释放Context指向的资源。

      (2)在zmq_msg_init_data函数的参数中,需要传入一个释放资源的函数地址,在ZMQ发送完消息后就调用这个函数来释放资源。如果没有传入这个参数,而且传入的信息是临时变量,那么接收方很有可能接收不到信息,甚至抛出异常。如果不传入这个参数,那么就要记得由自己去释放资源了。

      2.对MessagePack的简单封装:

    复制代码
      1 #include"BaseMessage.h"
      2 #include"ClientMessage.h"
      3 #include"ServerMessage.h"
      4 #include<zmq.h>
      5 #include<msgpack.hpp>
      6 
      7 namespace Tool
      8 {
      9     using namespace Message;
     10 
     11     //压包/解包工具类
     12     class Msgpack
     13     {
     14     public:
     15 
     16         // 功能 :构造函数。
     17         // 参数 :无。
     18         // 返回 :无。
     19         Msgpack(void) { }
     20 
     21         // 功能 :析构函数。
     22         // 参数 :无。
     23         // 返回 :无。
     24         ~Msgpack(void) { }
     25 
     26         // 功能 :压包数据。
     27         // 参数 :要压包的数据。
     28         // 返回 :true表示压包成功。
     29         template<typename T>
     30         bool Pack(const T& t)
     31         {
     32             try
     33             {
     34                 Release();
     35                 msgpack::pack(m_sbuf,t);
     36                 return true;
     37             }
     38             catch(...)
     39             {
     40                 std::cout << "Msgpack压包数据失败。" << std::endl;
     41                 return false;
     42             }
     43         }
     44 
     45         // 功能 :解包数据。
     46         // 参数 :zmq消息体。
     47         // 返回 :返回指向基类消息的指针。
     48         BaseMessage* Unpack(zmq_msg_t& msg)
     49         {
     50             try
     51             {
     52                 int size = zmq_msg_size(&msg);
     53                 if( size > 0 )
     54                 {
     55                     Release();
     56                     m_sbuf.write((char*)zmq_msg_data(&msg),size);
     57                     size_t offset = 0;
     58                     msgpack::zone z;
     59                     msgpack::object obj;
     60                     msgpack::unpack(m_sbuf.data(),m_sbuf.size(),&offset,&z,&obj);
     61                     return GetMessage(obj);
     62                 }
     63             }
     64             catch(...)
     65             {
     66                 //吃掉异常
     67             }
     68             return NULL;
     69         }
     70 
     71         // 功能 :获取压包/解包工具。
     72         // 参数 :无。
     73         // 返回 :压包/解包工具。
     74         inline msgpack::sbuffer& GetSbuf()
     75         {
     76             return m_sbuf;
     77         }
     78 
     79     private:
     80 
     81         //压包/解包工具
     82         msgpack::sbuffer m_sbuf;
     83 
     84     private:
     85 
     86         // 功能 :释放上一次的数据资源。
     87         // 参数 :无。
     88         // 返回 :无。
     89         void Release()
     90         {
     91             m_sbuf.clear();
     92             m_sbuf.release();
     93         }
     94         
     95         // 功能 :获取消息。
     96         // 参数 :用于转换的msgpack::object。
     97         // 返回 :指向消息基类的指针。
     98         BaseMessage* GetMessage(const msgpack::object& obj)
     99         {
    100             BaseMessage bmessage;
    101             obj.convert(&bmessage);
    102             switch(bmessage.Type)
    103             {
    104             case 1024:
    105                 return Convert<ClientMessage>(obj);
    106             case 2048:
    107                 return Convert<ServerMessage>(obj);
    108             default:
    109                 return NULL;
    110             }
    111         }
    112 
    113         // 功能 :将压包后的数据转换为具体的类。
    114         // 参数 :用于转换的msgpack::object。
    115         // 返回 :指向T的指针。
    116         template<typename T>
    117         T* Convert(const msgpack::object& obj)
    118         {
    119             T *t = new T();
    120             obj.convert(t);
    121             return t;
    122         }
    123     };
    124 };
    复制代码

      说明:

      压包时将zmq_msg_t消息体压包到msgpack::sbuffer,然后就可以关闭这个消息体了。要将解包后的数据转换成具体的某一个类,需要知道这个类是什么类,这里有三种方法:

      (1)可以先发送一个消息告知接收者即将收到什么消息,然后接收者将消息解包后转换成对应的类。这种方式需要额外的一次通信,不建议使用。

      (2)所有的消息都继承自一个基类,这个基类存储有消息类型的字段。解包后,先将数据转换为基类,然后根据类型再转换为具体的派生类。这种方式需要多转换一次,上面的代码也正是采用这种方式。

      (3)压包时先压包一个消息类,然后再压包一个标识这个消息是什么类型的标识类,即压包两次。解包时,先解包标识类,得知消息类的具体类型,然后再解包消息类,即解包两次,转换两次。与(2)相比,除了要做更多的压包、解包工作外,这里还需要对解包的偏移量进行计算,否则容易出错。

      3.使用到的消息类:

    复制代码
    namespace Message
    {
        //消息基类
        class  BaseMessage
        {
        public:
    
            MSGPACK_DEFINE(Type);
    
            //消息类型
            int Type;
    
            //默认构造函数
            BaseMessage()
            {
                Type = 0;
            }
        };
    
        //来自客户端的消息
        class ClientMessage : public BaseMessage
        {
        public:
    
            MSGPACK_DEFINE(Type,Information);
    
            //信息
            std::string Information;
    
            //默认构造函数
            ClientMessage()
            {
                Type = 1024;
            }
        };
    
        //来自服务端的消息
        class ServerMessage : public BaseMessage
        {
        public:
    
            MSGPACK_DEFINE(Type,Information);
    
            //信息
            std::vector<std::string> Information;
    
            //默认构造函数
            ServerMessage()
            {
                Type = 2048;
            }
        };
    }; 
    复制代码

      说明:

      (1)MSPACK_DEFINE标识了一个类的哪些成员可以进行压包/解包。派生类中的MSGPACK_DEFINE还需要写上基类的成员,否则无法使用对MessagePack封装说明的第二个方法。

      (2)C++版本的MessagePack压/解包的数据成员,只能是一个类、结构或者联合体,不能使用指针(包括boost库的智能指针)、数组,枚举值也不适用。因此,BaseMessage使用int值来标识派生类属于哪个类型。C#版本的MessagePack可以对枚举值进行压包。

      4.Client的示例代码:

    复制代码
     1 int _tmain(int argc, _TCHAR* argv[])
     2 {
     3     Network network;
     4     bool result = network.Init(ZMQ_REQ,"tcp://192.168.10.179:8888");
     5     if(result)
     6     {
     7         ClientMessage cmessage;
     8         cmessage.Information = "I come form Client.";
     9 
    10         Msgpack msgpack;
    11         result = msgpack.Pack<ClientMessage>(cmessage);
    12         if(result)
    13         {
    14             result = network.SendMessageW(&msgpack,false);
    15             if(result)
    16             {
    17                 zmq_msg_t *msg = network.ReceiveMessage();
    18                 if( msg != NULL )
    19                 {
    20                     BaseMessage *bmessage = msgpack.Unpack(*msg);
    21                     network.CloseMsg(msg);
    22                     if( bmessage != NULL && bmessage->Type == 2048 )
    23                     {
    24                         ServerMessage *smessage = static_cast<ServerMessage*>(bmessage);
    25                         if( smessage != NULL && smessage->Information.size() > 0 )
    26                         {
    27                             std::cout << smessage->Information[0] << std::endl;
    28                         }
    29                         delete smessage;
    30                         smessage = NULL;
    31                         bmessage = NULL;
    32                     }
    33                 }
    34             }
    35         }
    36     }
    37 
    38     system("pause");
    39     return 0;
    40 }
    复制代码

      5.Server的示例代码:

    复制代码
     1 int _tmain(int argc, _TCHAR* argv[])
     2 {
     3     Network responder;
     4     bool result = responder.Init(ZMQ_REP,"tcp://192.168.10.179:8888");
     5     if(result)
     6     {
     7         Network publisher;
     8         result = publisher.Init(ZMQ_PUB,"tcp://192.168.10.179:9999");
     9         if(result)
    10         {
    11             Msgpack msgpack;
    12             while(true)
    13             {
    14                 zmq_msg_t *msg = responder.ReceiveMessage();
    15                 BaseMessage *bmessage = msgpack.Unpack(*msg);
    16                 responder.CloseMsg(msg);
    17 
    18                 ServerMessage smessage;
    19                 smessage.Information.push_back("I come from Server.");
    20                 msgpack.Pack<ServerMessage>(smessage);
    21                 result = responder.SendMessageW(&msgpack,false);
    22 
    23                 if( result )
    24                 {
    25                     if( bmessage != NULL && bmessage->Type == 1024 )
    26                     {
    27                         ClientMessage *cmessage = static_cast<ClientMessage*>(bmessage);
    28                         if( cmessage != NULL )
    29                         {
    30                             std::cout << cmessage->Information << std::endl;
    31                             for( int counter = 0 ; counter < 100 ; counter++ )
    32                             {
    33                                 publisher.SendMessageW(&msgpack,false);
    34                             }
    35                         }
    36                         delete cmessage;
    37                         cmessage = NULL;
    38                         bmessage = NULL;
    39                     }
    40                 }
    41             }
    42         }
    43     }
    44 
    45     return 0;
    46 }
    复制代码

      6.Agent的示例代码:

    复制代码
    int _tmain(int argc, _TCHAR* argv[])
    {
        Network network;
        bool result = network.Init(ZMQ_SUB,"tcp://192.168.10.179:9999");
        if(result)
        {
            zmq_msg_t *msg = network.ReceiveMessage();
            if( msg != NULL )
            {
                Msgpack msgpack;
                BaseMessage *bmessage = msgpack.Unpack(*msg);
                network.CloseMsg(msg);
                if( bmessage->Type == 2048 )
                {
                    ServerMessage *smessage = static_cast<ServerMessage*>(bmessage);
                    if( smessage->Information.size() > 0 )
                    {
                        std::cout << smessage->Information[0] << std::endl;
                    }
                    delete smessage;
                    smessage = NULL;
                    bmessage = NULL;
                }
            }
        }
    
        system("pause");
        return 0;
    }
    复制代码

      7.启动这三个程序,Client将要发送的消息压包后发给Server,Server接收到消息后反馈一个信息给Client,然后循环发布消息给Agent,Agent不需要回复Server。最后着重说明两点:

      (1)ZMQ创建的socket发送数据和接收数据要处在同一条线程。Server接收到Client的数据后,不能通过开一条线程来给Client反馈信息,必须要在接收数据的线程中反馈信息。

      (2)ZMQ并不要求发送者和接收者有一定的启动顺序,但在Server中如果只发布一次消息,那么Agent很有可能收不到信息。不管是Agent先启动,还是Server先启动,Agent都有可能收不到信息。在Server的代码中,通过循环发布一百次,来让Agent收到信息。至于实际应用中,可以结合请求-响应模式来保证订阅消息者都收到了发布者的消息。

     参考资料:

    ZMQ:http://zguide.zeromq.org/page:all

    MessagePack:http://wiki.msgpack.org/pages/viewpage.action?pageId=1081387#QuickStartforC%2B%2B-ImplementationStatus

     
     
    分类: 软件设计
  • 相关阅读:
    warning: LF will be replaced by CRLF in ***. The file will have its original line endings in your working directory.
    GitHub出现Permissiondenied (publickey).
    浏览器编辑web页面的方法
    用scikit-learn进行LDA降维
    支持向量机原理(二) 线性支持向量机的软间隔最大化模型
    奇异值分解(SVD)原理与在降维中的应用
    scikit-learn K近邻法类库使用小结
    矩阵分解在协同过滤推荐算法中的应用
    精确率与召回率,RoC曲线与PR曲线
    异常点检测算法小结
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3051439.html
Copyright © 2011-2022 走看看