网站中Cache的使用
Tag 构架, Cache, 网站, MVC, on by view 5736

现在的许都网站都使用MVC构架,为了应对高并发,人们通常使用基于内存存取的Cache系统作为后端减压的应对方式。在网站的后端,主要涉及业务逻辑处理和数据库查询,而数据查询许多时候在高并发的状况下成为了性能的瓶颈。本文要讨论的是Cache如何使用才能够更加合理,更加有效。

网站的MVC构架通常情况如下
mvc_structure.png
数据库(Database)处于最后端,其次是Model层,Model层是对数据库直接操作的一种封装,然后是Controller层,Controller层是专门处理业务逻辑的,获取数据是直接调用Model层中封装的各种方法,最外层是View层,负责显示数据到页面,Controller层处理完毕的的数据传到View层,由其渲染为静态页发送给用户浏览器。

构想一,将Cache用于Model层中
cache_in_model.png
Cache用于Model层是将查询到的数据放置到Cache中,从而避免下次同样的数据需要再次去数据库执行SQL语句查询,而是直接从Cache中更快的读取数据(理论上某些SQL查询非常耗时,这样Cache效果更明显)。

// 通过id获取文章-cached
func GetArticle(id int) (Article, error) {
	var err error
	var art Article

	cache := config.GetCache("GetArticle.id." + fmt.Sprintf("%d", id))
	if cache != nil { // check cache
		json.Unmarshal([]byte(cache.(string)), &art)
		return art, nil
	} else {
		o := orm.NewOrm()
		o.Using("default")
		art = Article{Id: id}
		err = o.Read(&art, "id")

		data, _ := utils.JsonEncode(art)
		config.SetCache("GetArticle.id."+fmt.Sprintf("%d", id), data, 600)
	}

	return art, err
}

构想二,将Cache用于Controller层,甚至View层。
cache_in_controller.png
若是将Cache用于Controller层,可以同时减轻业务逻辑处理和数据库查询。如果将Cache用于View层,甚至可以减轻模版的渲染工作,这样一来有点类似于全站静态化的感觉了,但是我并不觉得这是一个好方法,或许对于个别网站是可行的,但是对于大多数相对复杂的网站,这是完全不科学的方法。例如,一个论坛网站,某个页面有帖子正文,有回复,若是将这种页面放入Cache势必会导致不得不降低Cache过期时间,而最终的结果将会是Cache命中率极低,还不如不用Cache。

对于我来说,我更偏向于在Model层使用Cache,因为这样会更加灵活能够定向的缓存该缓存的东西,而不是像方案二中外层的缓存使得数据块不易分离。


windows live writer与wordpress通讯协议(xmlrpc)分析
Tag windows live writer, wordpress, xmlrpc, 通讯, on by view 5737

最近一直在完善自己的博客,wordpress博客不可不谓之博客系统中最完善的一个,我的博客系统也一直以wordpress为样本,向wordpress学习。相信用过wordpress的人都用过windows live writer作为自己博客的客户端。windows live writer原本是微软为live博客开发的客户端,但是现在live博客已经关停,因此windows live writer也停止了更新。作为windows系统下最佳的博客离线客户端,即便它已经停止更新,它也将会是一款经典的软件,如同xp系统一样。

最近两天的时间,我将自己的博客系统实现了与windows live writer通讯,苦于找不到有用的指导资料,折腾得很辛苦。

调试工具:
服务端:本地搭建wordpress,我的博客blog
客户端:windows live writer,chrome浏览器的Postman应用扩展

调试过程:

  1. 进入本地wordpress首页,找到xmlrpc协议入口

    <link title="RSD" rel="EditURI" type="application/rsd+xml" href="http://127.0.0.1/xmlrpc.php">

    之后的所有请求都是从这个入口上实现的。

  2. 协议检查。windows live writer连接后的第一件事请是获取到首页上的接口(title为RSD的link标签),第二件事情便是向http://127.0.0.1/xmlrpc.php发送get请求,获取到的内容大致如下:

    <?xml version="1.0" encoding="UTF-8"?><rsd version="1.0" xmlns="http://archipelago.phrasewise.com/rsd">
      <service>
        <engineName>WordPress</engineName>
        <engineLink>http://wordpress.org/</engineLink>
        <homePageLink>http://127.0.0.1</homePageLink>
        <apis>
          <api name="WordPress" blogID="1" preferred="true" apiLink="http://127.0.0.1/xmlrpc" />
        </apis>
      </service>
    </rsd>
  3. 登录,发送blogger.getUsersBlogs请求【以下请求的主体均在http request的body内,并且皆为post请求】

    <?xml version="1.0" encoding="utf-8"?>
    <methodCall>
     <methodName>blogger.getUsersBlogs</methodName>
     <params>
      <param>
       <value>
        <string>ffffffabffffffce6dffffff93ffffffac29ffffffc9fffffff826ffffffdeffffffc9ffffffe43c0b763036ffffffa0fffffff3ffffffa963377716</string>
       </value>
      </param>
      <param>
       <value>
        <!-- 用户名 -->
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <!-- 密码 -->
        <string>lijun</string>
       </value>
      </param>
     </params>
    </methodCall>

    响应:

    <?xml version="1.0" encoding="UTF-8"?>
    <methodResponse>
      <params>
        <param>
          <value>
            <array>
              <data>
                <value>
                  <struct>
                    <!-- 1 -->
                    <member><name>isAdmin</name><value><boolean>1</boolean></value></member>
                    <!-- homepage -->
                    <member><name>url</name><value><string>%s</string></value></member>
                    <!-- userid -->
                    <member><name>blogid</name><value><string>%d</string></value></member>
                    <!-- blogname -->
                    <member><name>blogName</name><value><string>%s</string></value></member>
                    <!-- xmlrpc url address -->
                    <member><name>xmlrpc</name><value><string>%s</string></value></member>
                  </struct>
                </value>
              </data>
            </array>
          </value>
        </param>
      </params>
    </methodResponse>

    上面的%d%s等按照自己的博客替换成相应的返回参数,遵循printf函数的格式化规则。

  4. 新建文章metaWeblog.newPost

    <?xml version="1.0" encoding="utf-8"?>
    <methodCall>
     <methodName>metaWeblog.newPost</methodName>
     <params>
      <param>
       <value>
        <string>1</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <struct>
         <member>
          <name>title</name>
          <value>
           <string>test tag</string>
          </value>
         </member>
         <member>
          <name>description</name>
          <value>
           <string>&lt;p&gt;test tag content&lt;/p&gt;</string>
          </value>
         </member>
         <member>
          <name>mt_text_more</name>
          <value>
           <string />
          </value>
         </member>
         <member>
          <name>wp_slug</name>
          <value>
           <string />
          </value>
         </member>
         <member>
          <name>mt_basename</name>
          <value>
           <string />
          </value>
         </member>
         <member>
          <name>wp_password</name>
          <value>
           <string />
          </value>
         </member>
         <member>
          <name>categories</name>
          <value>
           <array>
            <data>
             <value>
              <string>ceshi</string>
             </value>
             <value>
              <string>tag</string>
             </value>
            </data>
           </array>
          </value>
         </member>
         <member>
          <name>mt_excerpt</name>
          <value>
           <string />
          </value>
         </member>
        </struct>
       </value>
      </param>
      <param>
       <value>
        <boolean>1</boolean>
       </value>
      </param>
     </params>
    </methodCall>

    响应:

    <?xml version="1.0" encoding="UTF-8"?>
    <methodResponse>
      <params>
        <param>
          <value>
          <!-- post id -->
          <string>%d</string>
          </value>
        </param>
      </params>
    </methodResponse>
  5. 新建分类标签请求wp.newCategory

    <?xml version="1.0" encoding="utf-8"?>
    <methodCall>
     <methodName>wp.newCategory</methodName>
     <params>
      <param>
       <value>
        <string>1</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <struct>
         <member>
          <name>name</name>
          <value>
           <string>测试</string>
          </value>
         </member>
         <member>
          <name>parent_id</name>
          <value>
           <int>0</int>
          </value>
         </member>
        </struct>
       </value>
      </param>
     </params>
    </methodCall>

     响应:

    <?xml version="1.0" encoding="UTF-8"?>
    <methodResponse>
      <params>
        <param>
          <value>
          <!-- catalog id -->
          <int>%d</int>
          </value>
        </param>
      </params>
    </methodResponse>
  6. 新建媒体文件metaWeblog.newMediaObject【图片上传】

    <?xml version="1.0" encoding="utf-8"?>
    <methodCall>
     <methodName>metaWeblog.newMediaObject</methodName>
     <params>
      <param>
       <value>
        <string>1</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <string>lijun</string>
       </value>
      </param>
      <param>
       <value>
        <struct>
         <member>
          <name>name</name>
          <value>
           <string>loadfailed.png</string>
          </value>
         </member>
         <member>
          <name>type</name>
          <value>
           <string>image/png</string>
          </value>
         </member>
         <member>
          <name>bits</name>
          <value>
           <!-- base64转码的图片文件 -->
           <base64>iVBORw0KGgoAAAANSUhEUgAAAkJggg==</base64>
          </value>
         </member>
        </struct>
       </value>
      </param>
     </params>
    </methodCall>

     响应:

    <?xml version="1.0" encoding="UTF-8"?>
    <methodResponse>
      <params>
        <param>
          <value>
          <struct>
            <!-- file id -->
            <member><name>id</name><value><string>%d</string></value></member>
            <!-- file name -->
            <member><name>file</name><value><string>%s</string></value></member>
            <!-- file url -->
            <member>
              <name>url</name>
              <value><string>%s</string></value>
            </member>
            <!-- file type -->
            <member><name>type</name><value><string>%s</string></value></member>
          </struct>
          </value>
        </param>
      </params>
    </methodResponse>


协议的内容太多,如有需要还建议自己使用相关的工具进行捕捉请求,以及使用Postman捕捉响应,然后自己分析请求与响应的内容,并在自己的系统中实现它。响应部分的其他内容可以参考我的博客系统源码http://github.com/duguying/blog ,请求部分我已经打包可以直接下载


写在网站变迁之后
Tag 网站, 备案, wordpress, go, 博客, on by view 5810

最近几天一直忙于这学期的课程设计作业,无法抽空写点什么。其实这次博客变动之后早就想写点东西记录一下本次博客搬迁的过程。

本次博客搬家是从原先的位于英国的Hostinger空间搬迁至国内的阿里云服务器,搬迁的原因主要有两个:第一个原因是国内访问国外的服务器线路极其的不稳定,经常被和谐或者是访问速度很慢;第二个原因是我已经决定并开始使用go语言重构了博客系统,弃用原来的基于php的wordpress博客系统,这一次用go语言重构博客也是一个挑战。

博客变迁记载

  • 2011-2012年左右,博客是基于php的wordpress系统,曾放置于“浦东信息港”那个免费空间提供商,相比于国内的免费空间这一家还算不错(不排除有我不知道的更好的),他提供香港机房版免备案空间,同时也提供单个MySQL数据库(不是整个MySQL服务器),可是有两个缺点:访问速度慢;不支持curl服务(这意味着不能在线安装wordpress的各种插件)。

  • 2013年,我申请了域名 duguying.net ,使用修改版的wordpress将网站放置于百度云,并且绑定了域名,可是不久之后百度云开始检查备案,未备案的网站一律停止域名解析,再到后来百度云升级了,可是升级后的系统却是非常的不方便使用,在到后来百度云开始大张旗鼓的收费了,感觉再也不爱百度云了。

  • 2014年上半年,将网站迁移至Hostinger,之前百度云上面的数据有部分已经丢失了,可是好景不长,Hostinger免费又是国外的免备案服务器,估计注册的人不少,那么各种言论也不少,相信触动了某的逆鳞的言论也是不少,因此就会偶尔遭遇和谐,对于这种各网站公用ip的空间来说,一个网站被和谐意味着相同ip的所有网站空间全部被和谐,空间提供商是老外,别人可不在意什么和谐。另外,Hostinger经常会出现CPU过载,然后你的网站就会自动跳转到一个警告页面,对于这个表示无语。最后,网速的确很差。

  • 2014年下半年,手痒了,想搞一个云服务器,把网站全部迁移至拥有独立造作系统、拥有root账户的云服务器或者是VPS。本人一开始是表示不喜欢备案的,因为我并不认为我的言论会导致什么坏的事情所以觉得自己没必要被审查,然后早就听说备案特别复杂,是一件很费神的事情。于是,我对比了国内外VPS/云服务器的价格之后,决定先在DigitalOcean购买一个月的VPS试用一下。买下之后却遇到了一系列的问题(将帐号锁定禁止开通主机),各方谷歌之后发现甚至有人大骂DigitalOcean野蛮终止自己的服务器导致大量的客户丢失(链接),硬着头皮看完英文,我想糟了该不会是被老外坑了吧,最后还是决定通过沟通的方式看能不能解决,于是我给客服发送用我那蹩脚的英文写的电子邮件,邮件中描述了我所遇到的状况并表示希望能够解除锁定,等待了半日的时间,终于收到了回复的电邮,帐号也解锁了。于是,立即创建了一个虚拟主机,ssh登陆之,最后发现ssh竟然卡出翔……我在putty上敲了一行的命令,没反应,大概过了十来秒屏幕突然一下子又出现了刚才敲的一行命令,按删除键没反应,再按又没反应,十来秒后一下子删除了N个字符,删多了。这样的用户体验,谁能忍!毕竟是国外的服务器……

  • 2014年7月左右,DigitalOcean那卡出翔的用户体验让我放弃了继续试用linode,听说linode可以选择日本的数据中心,网速还不错,据说大神byvoid的个人博客就是放在linode,可是又据说这个日本的数据中心容易被和谐。于是,恰逢遇到阿里云促销“0元半年体验”,申请成功后发现这个是不包括网络带宽的,没办法,一咬牙就花了127大洋买了6个月的带宽。ssh登录,速度很不错,速度上感觉与本地虚拟机相似比较爽,没办法了,买了国内的云服务器,乖乖备案吧。

  • 2014年8月,既然买了一个独立的云服务器那么就得用到实处,我决定使用go语言重构博客了,第一周看了《Go语言编程》那本书的大部分,第二周开始基于Beego框架构建博客系统,第三周博客后台完成大部分,开始构思前端界面并且开始准备备案的事情。终于要备案了,这是我第一次备案,备案使用的是阿里云系统,阿里云的客服处理事情的速度还是比较快的,提交初审后过了两小时就有客服妹纸打电话说明相关信息并且告诉我我起得网站名称不行,需要改一下更容易通过,于是就按照客服妹纸的建议改为“大俊的个人网站”(我提交的名称是“大俊哥之家”),并且客服告诉我我的域名持有人名称与申请备案人名称不一致,当时域名是在oray注册的,注册信息填写英文名的时候填写的是Rex Lee,因此显示为Rex Lee,客服建议我讲域名转到阿里云。后来我发现阿里云和万网居然合并了(万网被阿里云收购),记得我上次登录万网时候那时还没有合并,域名价格都是特贵的,所以当初才选择了相对便宜的Oray,可是现在一看,域名居然白菜价,转入39,注册49,续费55,我果断决定转入了,可是,有另外一件麻烦事情,当初Oray帐号注册时候没有实名认证,Oray规定只有实名认证的用户才能够转出域名,没办法,当初注册帐号填写的是企业账户,我现在是无法实名认证的,最终决定注册一个小号,将域名在帐号间转移(花了我10块大洋),然后将小号实名认证,Oray的客服效率真心查啊,帐号实名认证花了4天,这还是我在他官方bbs上催他们才有这效率的,终于,实名认证成功了,也成功获取了域名转移密码,申请转入万网成功,然后又等了将近5个工作日,终于成功转过来了,在原域名商那儿还有5个月的时间也一同转过来了,这是我没想到的。最后发现,国际域名.net根本就不支持中文持有人信息Orz...不过我还是决定将Rex Lee改为中文名的汉语拼音。域名转入的事情告一段落了,与此同时,我申请了阿里云备案的幕布,自己用他的幕布拍照,传照片以及各种资料,然后快递资料。接下来便是漫长的等待了,据说20个工作日可以得到备案结果,还好没用20天,备案号终于发下来了。早已经准备好的网站系统终于可以启动了,我霸气的在putty中敲下service nginx start启动了前端服务器。网站开通。

折腾了这么久,网站总算是通了,我想以后我会继续续费,将这个网站继续下去,go语言才刚刚开始,我也是刚刚开始学习go语言,后面的路还很长,我看好golang。也会坚持写博客。


Go语言新手应该知道的几点
Tag golang, 初学者, 包导入, 变量命名, on by view 5964

1. 变量与函数命名

在go语言中变量与函数的命名是有着特殊的限制的,比如,一个函数functionname与函数FunctionName是有着不同的特点的。函数名第一个字母大写表示此函数可以被其他文件中的代码所调用,而函数名第一个字母小写的函数却只能在当前文件中被调用。初学者往往会发现自己正确的导入了包,但是却无法调用包里面的函数,这时候请看一下自己定义的函数名的首字母的大小写情况。因此,建议Go语言变量命名一律使用骆驼命名法(FunctionName/functionName),而不是蛇形命名法(function_name)。

2. 包导入import

(1) 相对路径

import "./model" //当前文件同一目录的model目录,但是不建议这种方式来import

(2) 绝对路径

import "shorturl/model" //加载gopath/src/shorturl/model模块

(3) 点操作

import( . "fmt" ) 
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world")可以省略的写成Println("hello world")

(4) 别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字

import( f "fmt" ) //别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")

(5) _操作

import ( "database/sql" 
         _ "github.com/ziutek/mymysql/godrv" 
        )
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数

Vim常用操作
Tag Vim, 动画演示, 常用操作, on by view 3758

1. 单词间跳转,w词首,e词尾

word_jump.gif

2. 行首^、行尾$

home_end.gif

3. 撤销u、重做ctrl+r

undo_redo.gif

4. 查找/keyword

find.gif

5. 复制、粘贴(v选区,y复制,p粘贴)

copy_paste.gif

6. 块操作,注释块(ctrl+v进入块操作模式,I块插入模式,编辑,Esc填充)

block_operate.gif

以上是一些常用的基本操作,当然Vim的操作还有很多,要不断的学习和练习才能够更加熟练。


Windows上的软件包管理器chocolatey
Tag chocolatey, windows, 软件包管理, on by view 7759

软件包管理器相信用过非Windows的人都知道。例如Linux上的apt-get,yum;MacOS上的brew。对于开发者来说,包管理器确实是个好东西,前面所说的包管理器可以安装二进制文件,部分也可以源码编译安装。Windows上的自带的软件管理(添加删除程序)一向被人们称为垃圾。下面就给大家介绍一个第三方实现的包管理器chocolatey。

chocolatey官网http://chocolatey.org/ ,安装方法:在cmd中执行下面命令(依赖于.NET Framework 4和Powershell)

C:>@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin

之后就可以在cmd中使用一下命令执行安装、卸载、查找软件包了

:: 安装notepad2软件
choco install notepad2
:: 或
cinst notepad2
:: 卸载notepad2软件
choco uninstall notepad2
:: 或
cuninst notepad2
:: 查找软件
choco search sublime

与Linux、MacOS上的命令很相似了,不过这个还是无法达到Linux、MacOS上的apt-get,yum,brew那么强悍,因为它只能够安装二进制软件包,并不能做编译安装之类的工作。chocolatey并不够强悍,不过也可以让人们在Windows系统上稍微体验一下命令行安装的感觉了。


bmp文件格式分析
Tag bmp, C语言, 二进制, 文件读取, 格式, on by view 6043

bmp格式图片文件由4部分组成:文件头(BitmapFileHeader),信息头(BitmapInfoHeader),颜色表(RGBQuad),像素矩阵(RGB)[即数据区]。

文件头(BitmapFileHeader)

typedef struct _BITMAPFILEHEADER
{
	BYTE2  bfType;  //类型标识0x4d42
	BYTE4  bfSize;  //文件大小(字节)
	BYTE2  bfReserved1;  //保留1(0)
	BYTE2  bfReserved2;  //保留2(0)
	BYTE4  bfOffBits;  //数据区偏移(字节)
} BitmapFileHeader;

信息头(BitmapInfoHeader)

typedef struct _BITMAPINFOHEADER{ 
	BYTE4  biSize;  //本结构的大小,根据不同的操作系统而不同,在Windows中,此字段的值总为0x28字节=40字节
	BYTE4  biWidth;  //图片宽度(px)
	BYTE4  biHeight;  //图片高度(px)
	BYTE2  biPlanes;  //目标设备的级别(1)
	BYTE2  biBitCount;  //每个像素所需的位数,必须是1(双色),4(16色),8(256色)或24(真彩色)之一
	BYTE4  biCompression;  //是否压缩(0不压缩)
	BYTE4  biSizeImage;  //图像数据区大小
	BYTE4  biXPelsPerMeter;  //水平分辨率,像素每米
	BYTE4  biYPelsPerMeter;  //垂直分辨率,像素每米
	BYTE4  biClrUsed;  //BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为0x100=256
	BYTE4  biClrImportant;  //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,
                                //当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
} BitmapInfoHeader;

颜色表(RGBQuad)

typedef struct _RGBQUAD {
	BYTE1  rgbBlue;  //蓝色分量
	BYTE1  rgbGreen;  //绿色分量
	BYTE1  rgbRed;  //红色分量
	BYTE1  rgbReserved;  //保留(0),非0则为alpha通道
} RGBQuad;

像素

typedef struct _RGBPx
{
	BYTE1  R;  //红色分量
	BYTE1  G;  //绿色分量
	BYTE1  B;  //蓝色分量
} RGBPx;

image_matrix.png

当图像颜色为24位时,即为全彩色RGB模式,无颜色表,所有颜色皆为重要色,因此biBitCount=24时文件中没有RGBQuad区域;若biBitCount为其他值,则图像为索引模式,颜色表中包含图像所用到的颜色,RGBQuad区域为RGBQuad结构体数组。文件中文件头(BitmapFileHeader),信息头(BitmapInfoHeader),颜色表(RGBQuad)三个区域之后是像素矩阵区域。

像素矩阵区中对于24位全色彩文件是RGBQuad区域为RGBQuad结构体数组,对于索引模式图像会将颜色表按照先后顺序编号,例如4位颜色,16色,将会编号0~15号颜色,如此0000b(1)表示第1种颜色,0001b(2),0010b(3),0011b(4),0100b(5)……故一字节可以表示两个像素,如0xa2=10100010b即为11号和2号色。

关于像素矩阵中还有一个问题需要注意,那就是“4字节边界”。像素矩阵的存储是遵循4字节边界存储的,也就是说每一行像素的数据字节大小必须是4的倍数,可是实际上有些图片存储后并不能保证每行的字节数是4的倍数;如一幅1366*768的24位图像,这种图像一像素24位即3字节,则每行1366*3=4098字节,而4098%4=2,余2并不是4的倍数,此时应当在最后2字节后面补充2字节(0x0)。故,在读取时应当注意每行像素后面是否有空边界。

最后,读取的像素还要进行倒置才能够正确的显示,因为原像素矩阵是上下颠倒,左右颠倒的。

paint_picture_reverse_demo.png

最后附上我自己实现的零依赖的bmp解析项目,直接读取二进制bmp文件解析,并带有基础图像变换函数https://github.com/organc/image 。


变形金刚4中的中国元素
Tag 中国元素, 变形金刚, on by view 3384

考完试和同学一起去放松了一下,看了一部3D电影——《变形金刚4绝迹重生》。第一次看3D电影,感觉还不错,只是戴两层眼镜有点不舒服,哈哈。电影的故事情节一般,各种特效的确很强大,自然少不了美国大片的打斗场景,大气磅礴的恢宏场面从一开始贯穿全片,电影中的各种建筑物自然是少不了被摧残,只是这次摧毁的不仅仅是美国的建筑物,也有中国的建筑。

网上查了一下,《变形金刚4》是派拉蒙影业、中国电影频道以及加赋公司联合制作的。既然是中国人参与制作的电影,中国的戏份自然是少不了。因此,电影中的中国元素也是众多。在这之前值得一提的是,电影中的植入广告倒是挺多,不过还好植入广告并不那么让人反感,场景刚切换到中国,李冰冰就拿着一瓶怡宝矿泉水(怪不得最近买的怡宝矿泉水上面印有变形金刚4的广告),还有那个喝伊利牛奶的美国科学家,以及写着别人厂家名字的几个大烟囱。主角们来到中国后,首先就是在北京,于是便少不了鸟巢这些标志建筑,飞船在空中飞行的时候还有万里长城的背景,后来场景切换到了香港,主角们首先是在比较古老破败的街道巷子中活动,有比较土的电梯,中国功夫,以及多一个人就超重的电梯,更有麻将以及各种熟悉的中国式的普通家庭布局;噢,对啦,还有逃亡时购买路人的摩托车时候的本地方言对话。穿过狭隘的过道时遇到的几个白发老奶奶,尽管被挡道很着急,却没有做出什么不礼貌的举动。后来的场景中还有各种中国现代化的建筑,当然这些场景都成了机器人战斗的战场。

电影全片气势恢宏,较之以前的几部又多出了恐龙机器人,还有更强悍的幕后人物,变形金刚和塞伯坦星球的创造者(尽管只出现在最后的镜头),那是一个屠杀有机生物星球并将其变为塞伯坦星球然后创造出机器人的神秘人物。那些恐龙形态的变形金刚貌似就是外星人在远古时代屠杀了恐龙之后创造的变形金刚。

现在的美国大片中出现中国元素已经是屡见不鲜的事情了,从当年的《2012》中的诺亚方舟在中国建成,到《木乃伊3龙帝之墓》,以及现在的《变形金刚4》。一方面是中国电影的积极参与,另一方面也体现出了中国逐渐受到世界的重视,逐渐的矗立于世界强国之列。


利用开源和免费资源搭建CI环境
Tag CI, 测试, on by view 7426

前段时间偶然接触到了持续集成(Continuous Integration)这一概念,并对它做了进一步的了解;说是偶然接触到是有原因的,起初我在github上的某些项目里面发现了这些东西

image.png

吸引我的注意力的正是这些绿色的徽章,点入build那个徽章一看,连接到了一个叫做Travis CI的网站,里面有类似控制台输出的信息

image1.png

这个的确是控制台输出,看里面的内容知道是通过git clone将这个项目下载到某台linux服务器,然后sudo权限执行了一些shell脚本,再后来就是./configure、make、make install之类的,这整个流程就是linux用户通过代码安装某个软件的过程。当然,部分项目里面并没有make install,而是make test,这是执行单元测试啊。

经过进一步了解后,发现Travis CI就是一个专门提供在线编译以及运行单元测试等工作的一个云平台(或者直白点说就是服务器)。阅读了一下午的Travis CI英文文档后,自己尝试着利用Travis CI为自己的项目也创建一个这样的编译测试环境。

首先,Travis  CI是可以直接与Github连接,读取项目信息的。然后只要你的项目的根目录下面有一个名为.travis.yml的文件Travis CI即可识别你的项目,并且从这个文件里面读取它所要执行的步骤。当然.travis.yml这个配置文件必须遵守文档中所定义的各种约定,在这之前.travis.yml文件的内容也必须遵守.yml文件的语法格式约定(我有一次就是因为没注意到其中的语法错误导致排错很久)。

  • yml文件语法应当注意的内容

    例如:

    env:
    global:
       # The next declration is the encrypted COVERITY_SCAN_TOKEN, created
       #   via the "travis encrypt" command using the project repo's public key
       - secure: "q5Wb1ChR5r/52zmkssky4IIM2C3b/ItLjfGRuqPgAsU6pgnrJ7GvHC44A/jvba4rTElxWKXhTyWvskdwSzgCvBs0GIRXHxSYjtcD9IzIMLX7zHfczP2ekH2xGONXhZR1AWN6/YHh1QZ2SwdrlbQv9jybZ9NxWvk9v2IPhdWr5MM="
    language: c
    compiler: 
        - "gcc"
        - "clang"
    script:
        - "chmod 777 ./coverity.sh"
        - "./coverity.sh"

           

    这其中有部分语法类似于key: value,但是要注意:后面必须有一个空格符,另外,语法还有与python类似的对齐规则,没对齐或者是伪对齐(空格与Tab符对齐)都会导致语法错误。最终的结果是Travis CI报错。

  • compiler项是设置系统编译器,上例中是使用gcc和clang编译器,因此会有两次编译,一次是gcc一次clang编译,当然多个值就会有多个编译,众多的编译构成编译矩阵,可以测试某个项目在不同的环境下面的编译结果。script项是命令Travis CI执行的具体命令,上例中我将命令全部写入到一个shell文件里面去了,script这一项的内容是执行这个shell脚本。详细的配置介绍可以自己查看Travis CI官网上的文档。

利用Travis CI你可以做些什么?首先,检测代码在语法上的正确性这是最基本的,简单来说就是检测你的项目是否能够编译通过,是否能够在不同个环境下编译通过。其次,你可以利用一些单元测试对项目进行更进一步的检测,这些根据你的测试代码可以检测出一些逻辑上的错误。

其实Travis CI是可以有其他的一些功能的,比如程序发布。例如,我现在需要将编译后的可执行文件发布到项目的官方网站上供别人下载;我可以使用curl命令将可执行文件通过我事先准备好的上传接口上传到我的网站,这样一来,你每次更新代码Travis CI会自动编译完成后直接发送到你指定的网站上,然后你的程序发布可以自动的保持最新版本。

事实上类似于Travis CI的网站还比较多,比如drone.io等,当然还有一些收费的CI服务网站,这儿就不提了。最后值得一提的是虽然大部分的免费CI是Linux环境的,但是被我发现了一个Windows环境的CI,而且它有几种环境可以选择,例如Windows Server 2008,最重要的是它也是免费的而且支持许多版本的Visual Studio,也就是说C#、.NET或者是包含Windows API的这些仅能在Windows环境下编译的代码也能够编译,https://ci.appveyor.com就是它,Windows应用程序开发者的福音。


fork()函数
Tag fork, 进程, on by view 5581

fork函数可以复制一个当前进程。看代码

#include <stdio.h>
int main(void){
    int pid;
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    for(;1;){
        sleep(1);
    }
    return 0;
}

为了让程序不会运行结束自动退出我使用for死循环和sleep函数将程序阻塞,fork函数会创建一个进程,至於是否创建了进程我们看任务管理器的截图

t1.png

任务管理器中有两个相同名称的进程了,这说明进程创建成功。 看终端运行结果
 t2.png

结果表明程序成功的创建了一个新进程。 fork()函数可以创建一个新进程并且返回子进程的ID,子进程与父进程完全一样,即执行相同的代码可是当子进程执行到fork()函数处的时候返回的值为0,却没有再创建进程;而父进程执行到fork()处创建了这个子进程并且返回了子进程的ID,至於为什么子进程不继续创建子子进程,这个我暂时就不知道了。 后来我又到fork()函数之前添加了一个printf作为标识,代码如下

#include <stdio.h>
int main(void){
    int pid;
    printf("hello\n";);
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    for(;1;){
        sleep(1);
    }
    return 0;
}

运行,终端截图

t3.png

图中并没有两行hello,这说明,如果子进程是完全复制父进程从代码的第一行开始执行的话子进程也会打印hello,很明显子进程不是这样干的。 再改代码,在fork函数之后添加printf,为了避开if分支就把printf函数添加到for阻塞代码的前一行[14],代码如下

#include <stdio.h>
int main(void){
    int pid;
    printf("hello\n");
    pid=fork();
    if(pid>0){//在父进程
        printf("Parents Process, The son pid: %d\n", pid);
    }else if(0==pid){//在子进程
        printf("Son Process, The pid return: %d\n", pid);
    }else{
        printf("Error!\n");
    };
    printf("world\n");
    for(;1;){
        sleep(1);
    }
    return 0;
}

运行截图

t4.png

是的,结果如我所料,有两个world,这说明fork后的进程并不是从头开始执行的,而是从fork语句处开始执行的。这不就是在fork函数那个点产生了一个分支吗。对于长期在github里面混的我,这让我想起了github项目管理里面的fork,看样子他们的表达是一样的,github的fork截图

t5.png

你理解了fork吗?反正我是懂了……