好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

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

前言

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

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

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

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

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

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

准备工作

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

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

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

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

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

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

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

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

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

如何减少请求数?

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

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

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

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

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

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

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

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

而且如果你细心看的话,在这个页面中请求的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的项目:

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

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

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

按照一般的惯例,我们在项目中添加一个文件,来进行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  > 

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

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

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

总结

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

 

分类:  网络开发和设计

秋式网站日志分析器[IISLogViewer] V3版本发布

离上一个版本,过了好久好久了。

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,欢迎留言。

 

下载地址: http://www.cyqdata.com/download/article-detail-42518 。

 

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

 

分类:  IIS日志分析工具

标签:  IISLogViewer ,  秋式网站日志分析器 ,  点格网站日志分析器

秋式网站日志分析器[IISLogViewer] V3版本发布

离上一个版本,过了好久好久了。

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,欢迎留言。

 

下载地址: http://www.cyqdata.com/download/article-detail-42518 。

 

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

 

分类:  IIS日志分析工具

标签:  IISLogViewer ,  秋式网站日志分析器 ,  点格网站日志分析器

秋式网站日志分析器[IISLogViewer] V3版本发布

离上一个版本,过了好久好久了。

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,欢迎留言。

 

下载地址: http://www.cyqdata.com/download/article-detail-42518 。

 

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

 

分类:  IIS日志分析工具

标签:  IISLogViewer ,  秋式网站日志分析器 ,  点格网站日志分析器

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

 

 

分类:  软件设计

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于优化网站设计(一):减少请求数的详细内容...

  阅读:47次