RabbitMQ

最早接触rabbitMQ的时候很奇怪,并没有用到这个产品,却因为其网络层设计的牛逼,在许多文章的建议下(好像最早是霸爷在建议),也跟着去扒它的网络层,有点跟风的意思,看到大家都在挖金矿,于是顺手操起家伙也跟着挖。


介绍它网络层设计的文章很多,不乏许多写得还不错的,例如
http://www.blogjava.net/killme2008/archive/2009/11/29/304079.html


就这样跟着扒了代码放在erlang服务器上用来处理网络连接。很长一段时间里,我仍然只知道rabbitMQ是一个实现了AMQP的消息队列,用来进行服务器间,或是应用间消息投递用,它属于网络设计中的一个基础组件。
后来没事了想搭一个message queue server 来玩玩,又想到了rabbitmq,macos下安装比较简单:

brew install rabbitmq  

安装完成后修改~/.bash_profile 或~/.profile

export PATH=$PATH:/usr/local/sbin

保存退出后

source ~/.bash_profile

这样就可以启动rabbitmq了:

rabbitmq-server

Ctl+C + a 就退出了

Ubuntu下verilog实现简单“与门”


第一步 搭建环境

verilog有两个比较常用的编译器:
1. Icarus Verilog
2. VeriWell Verilog Simulator
ubuntu源里面默认的是Icarus Verilog,安装编译器:

sudo apt-get install verilog  

然后再安装模拟器,用来查看波形的软件:

sudo apt-get install gtkwave  

这样就ok了

第二步 写一个简单的与门

and.v

module add(a, b, c);
input a;
input b;
output c;

assign c = a & b;
endmodule

ok了,再写一个testbench,用来测试刚才的与门是否正常工作:
test_and.v

`timescale 1ns/1ns
module test_and;
reg a;
reg b;
wire c;

add t(a, b, c);

initial
begin
    $dumpfile("test.lxt");
    $dumpvars(0, test_add);
    $dumpvars(0, t.a, t.b, t.c);
end

initial
begin
    #10 a = 1; b = 0;
    #10 a = 0; b = 1;
    #10 a = 0; b = 0;
    #10 a = 1; b = 1;
    #50 $finish;
end

initial
    $monitor("a = %d, ", a, "b = %d, ", b, "c = %d\n", c);
endmodule

第三步 编译运行和查看波形

在命令行下执行:

iverilog -o my_and and.v test_and.v

执行完后会生成一个名为my_and 的文件,./my_and就能运行,如图:

这个命令跟gcc很像,当然后期你也可以用MakeFile来写编译依赖关系。

vvp -n my_and -lxt2

之后就会生成一个test.lxt的文件

gtkwave test.lxt

然后选择t,同时选中a[0], b[0], c[0], 点击Insert, 就能看到波形了:结果如图:

阅读skynet(一)

一直在关注云风大神的skynet,大神已经写了21篇关于skynet设计以及 优化的博客了。
云风关于skynet的介绍说了,skynet主要还是参照了erlang的 服务器异步编程思想,鉴于做过erlang开发的缘故,我比较能理解他博客里面 关于设计思想方面的说明。
不过c根基薄弱,加上也比较懒惰,一直没认真读代码,不过skynet主要部分 代码并不多,代码跟设计一样飘逸,是深入学习c的好教材。

skynet是什么

请看原作者博客skynet开源
“其实底层框架需要解决的基本问题是,把消息有序的,从一个点传递到另一个点。每个点是一个概念上的服务进程。这个进程可以有名字,也可以由系统分配出唯一名字。本质上,它提供了一个消息队列,所以最早我个人是希望用 zeromq 来开发的。
现在回想起来,无论是利用 erlang 还是 zeromq ,感觉都过于重量了。”

由此可知它是一个服务器端的消息框架,由于引入了lua,用户基于skynet可以创建由lua写的服务,也叫agent,而不同agent之间的通信就类似erlang里面不同进程的通信一样(不懂erlang的童鞋理解起来可能有点费力)。


下面来看关于skynet架构的说明:
“这个系统是单进程多线程模型。”
“我用多线程模型来实现它。底层有一个线程消息队列,消息由三部分构成:源地址、目的地址、以及数据块。框架启动固定的多条线程,每条工作线程不断的从消息队列取到消息。根据目的地址获得服务对象。当服务正在工作(被锁住)就把消息放到服务自己的私有队列中。否则调用服务的 callback 函数。当 callback 函数运行完后,检查私有队列,并处理完再解锁。
线程数应该略大于系统的 CPU 核数,以防止系统饥饿。(只要服务不直接给自己不断发新的消息,就不会有服务被饿死”

skynet是单进程多线程模型,可以看skynet/config 这个配置文件里面:

root = "./"
thread = 8                                                                      
logger = nil 
harbor = 1 
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main"
standalone = "0.0.0.0:2013"
luaservice = root.."service/?.lua;"..root.."service/?/init.lua"
cpath = root.."service/?.so"
protopath = root.."proto"
redis = root .. "redisconf"

thread = 8 这里给skynet的线程池配置了8个线程,并在skynet_start里面给它们起起来


来看看关于agent的说明:
“每个内部服务的实现,放在独立的动态库中。由动态库导出的三个接口 create init release 来创建出服务的实例。init 可以传递字符串参数来初始化实例。比如用 lua 实现的服务(这里叫 snlua ),可以在初始化时传递启动代码的 lua 文件名。”
是不是跟erlang的init, terminate 很像?
“每个服务都是严格的被动的消息驱动的,以一个统一的 callback 函数的形式交给框架。框架从消息队列里取到消息,调度出接收的服务模块,找到 callback 函数入口,调用它。服务本身在没有被调度时,是不占用任何 CPU 的。框架做两个必要的保证。
一、一个服务的 callback 函数永远不会被并发。
二、一个服务向两一个服务发送的消息的次序是严格保证的。
我用多线程模型来实现它。底层有一个线程消息队列,消息由三部分构成:源地址、目的地址、以及数据块。框架启动固定的多条线程,每条工作线程不断的从消息队列取到消息。根据目的地址获得服务对象。当服务正在工作(被锁住)就把消息放到服务自己的私有队列中。否则调用服务的 callback 函数。当 callback 函数运行完后,检查私有队列,并处理完再解锁。”

来看它的启动流程:
skynet_start函数里,显示group, harbor, handle, mq, module 这些组件的初始化
然后启动所有服务模块,并根据配置中standalone来判断是否要启动skynet_context
接着是logger, harbor, snlua这些服务模块的启动
所有这些启动完毕之后,转入_start 函数开始线程池,进行消息dispatch循环
亮代码:

void 
skynet_start(struct skynet_config * config) {
    skynet_group_init();
    skynet_harbor_init(config->harbor);
    skynet_handle_init(config->harbor);
    skynet_mq_init();
    skynet_module_init(config->module_path);
    skynet_timer_init();
    skynet_socket_init();

    struct skynet_context *ctx;
    ctx = skynet_context_new("logger", config->logger);
    if (ctx == NULL) {
        fprintf(stderr,"launch logger error");
        exit(1);
    }   

    if (config->standalone) {
        if (_start_master(config->standalone)) {
            fprintf(stderr, "Init fail : mater");
            return;
        }   
    }   
    // harbor must be init first
    if (skynet_harbor_start(config->master , config->local)) {
        fprintf(stderr, "Init fail : no master");
        return;
    }   

    ctx = skynet_context_new("localcast", NULL);
    if (ctx == NULL) {
        fprintf(stderr,"launch local cast error");
        exit(1);
    }   
    ctx = skynet_context_new("snlua", "launcher");
    if (ctx) {
        skynet_command(ctx, "REG", ".launcher");
        ctx = skynet_context_new("snlua", config->start);
    }   

    _start(config->thread);
    skynet_socket_free();                                                       
}

skynet集群及RPC

云风的博客skynet集群及RPC上这么写着:
“最终,我们希望整个 skynet 系统可以部署到多台物理机上。这样,单进程的 skynet 节点是不够满足需求的。我希望 skynet 单节点是围绕单进程运作的,这样服务间才可以以接近零成本的交换数据。这样,进程和进程间(通常部署到不同的物理机上)通讯就做成一个比较外围的设置就好了。”
按照云风说的设计思路,我是这样理解的,服务器分为多个节点,例如网关节点,登陆节点,游戏场景节点等等,节点之间通过rpc通信,而节点内则是单进程多线程(后文统称skynet进程),采用共享内存进行数据交换。
而进行skynet进程间数据交换的部件就是skynet_harbor,我们来看skynet_harbor.h文件

#ifndef SKYNET_HARBOR_H                                                          
#define SKYNET_HARBOR_H

#include <stdint.h>
#include <stdlib.h>

#define GLOBALNAME_LENGTH 16
#define REMOTE_MAX 256

// reserve high 8 bits for remote id
// 可以看到,这里取高8位用来作为机器识别,而低24位用作服务节点id
#define HANDLE_MASK 0xffffff
#define HANDLE_REMOTE_SHIFT 24

// 消息目的skynet节点名,包含一个名字和一个32位无符号的id
struct remote_name {
    char name[GLOBALNAME_LENGTH];
    uint32_t handle;
};

struct remote_message {
    struct remote_name destination;
    const void * message;
    size_t sz; 
};

// 发送消息,同时带上发送者的id
void skynet_harbor_send(struct remote_message *rmsg, uint32_t source, int session);
// 向master节点注册一个skynet进程
void skynet_harbor_register(struct remote_name *rname);
// 这个函数用来判断消息是来自本机器还是外部机器
int skynet_harbor_message_isremote(uint32_t handle);
// 初始化harbor
void skynet_harbor_init(int harbor);
// 启动harbor
int skynet_harbor_start(const char * master, const char *local);

#endif

看这段文字:
“为了定位方便,我希望整个系统里,所有服务节点都有唯一 id 。那么最简单的方案就是限制有限的机器数量、同时设置中心服务器来协调。我用 32bit 的 id 来标识 skynet 上的服务节点。其中高 8 位是机器标识,低 24 位是同一台机器上的服务节点 id 。我们用简单的判断算法就可以知道一个 id 是远程 id 还是本地 id (只需要比较高 8 位就可以了)。”
HANDLE_REMOTE_SHIFT 其实是用来取高8位机器识别码,而HANDLE_MASK则是取低24位skynet节点唯一id长度。我们看一下skynet_harbor_send(skyner_harbor.c 13行) 发消息函数的实现就知道:

void 
skynet_harbor_send(struct remote_message *rmsg, uint32_t source, int session) {
    int type = rmsg->sz >> HANDLE_REMOTE_SHIFT;
    rmsg->sz &= HANDLE_MASK;
    assert(type != PTYPE_SYSTEM && type != PTYPE_HARBOR);                                       
    skynet_context_send(REMOTE, rmsg, sizeof(*rmsg) , source, type , session);
}

通过将sz向右移24位来取高8位的机器识别码,而通过与0xffffff相与来取低24位的id,在断言这里,有PTYPE_SYSTEM 和PTYPE_HARBOR 两个宏定义在skynet.h中定义着,它们标识着skynet中的消息类型,看skynet.h:

...
#define PTYPE_TEXT 0
#define PTYPE_RESPONSE 1
#define PTYPE_MULTICAST 2
#define PTYPE_CLIENT 3
#define PTYPE_SYSTEM 4 // SYSTEM
#define PTYPE_HARBOR 5 // HARBOR                                                               
#define PTYPE_SOCKET 6
// read lualib/skynet.lua lualib/simplemonitor.lua
#define PTYPE_RESERVED_ERROR 7  
// read lualib/skynet.lua lualib/mqueue.lua
#define PTYPE_RESERVED_QUEUE 8
#define PTYPE_RESERVED_DEBUG 9
#define PTYPE_RESERVED_LUA 10

#define PTYPE_TAG_DONTCOPY 0x10000
#define PTYPE_TAG_ALLOCSESSION 0x20000
...

再来看 skynet_harbor.c里面skynet_harbor_message_isremote(skynet_harbor.c 36行) 的实现:

int 
skynet_harbor_message_isremote(uint32_t handle) {
    int h = (handle & ~HANDLE_MASK);
    return h != HARBOR && h !=0;
}

挺简单的一个位运算,好了,再看skynet_harbor_register(skynet_harbor.c 21行):

void 
skynet_harbor_register(struct remote_name *rname) {
    int i;
    int number = 1;
    for (i=0;i<GLOBALNAME_LENGTH;i++) {
        char c = rname->name[i];
        if (!(c >= '0' && c <='9')) {
            number = 0;
            break;
        }   
    }   
    assert(number == 0); 
    skynet_context_send(REMOTE, rname, sizeof(*rname), 0, PTYPE_SYSTEM , 0); 
}

看到了,harbor在register的时候向master节点发送的是类型PTYPE_SYSTEM的系统消息,并且source id为0, session 也为0,但是skynet_context_send 函数干了什么呢?
好了,等我们先看完skynet_harbor_init(skynet_harbor.c 42行) 和skynet_harbor_start(skynet_harbor.c 47行)分别做了什么之后,再来看skynet_context_send 到底干了什么

void
skynet_harbor_init(int harbor) {
    HARBOR = (unsigned int)harbor << HANDLE_REMOTE_SHIFT;
}

int
skynet_harbor_start(const char * master, const char *local) {
    size_t sz = strlen(master) + strlen(local) + 32; 
    char args[sz];
    sprintf(args, "%s %s %d",master,local,HARBOR >> HANDLE_REMOTE_SHIFT);
    struct skynet_context * inst = skynet_context_new("harbor",args);
    if (inst == NULL) {
        return 1;
    }   
    REMOTE = inst;

    return 0;
}  

哦,init函数里设置了HARBOR的值,它在skynet_harbor.c 第10行声明着。
而start函数设置了REMOTE的值,它在skynet_harbor.c 第9行声明着。
skynet_context_send(skynet_server.c 第682行)

void
skynet_context_send(struct skynet_context * ctx, void * msg, size_t sz, uint32_t    source, int type, int session) {
    struct skynet_message smsg;
    smsg.source = source;
    smsg.session = session;
    smsg.data = msg;
    smsg.sz = sz | type << HANDLE_REMOTE_SHIFT;

    skynet_mq_push(ctx->queue, &smsg);
} 

它调用的是skynet_mq_push(skynet_mq.c 182行),可见harbor使用skynet_mq 来传递消息,而skynet_mq则是skynet里面非常重要的一个组件,它实现了skynet agent之间的消息传递(这个有点类似erlang的cast message)。
最终harbor的register消息发向了哪里呢?master !


RPC核心和模块化思想

RPC的实现,就是先创建一个master,然后所有的worker向master注册,而master纪录下所有注册信息,用云风的原话来讲:
“master 服务其实就是一个简单的内存 key-value 数据库。数字 key 对应的 value 正是 harbor 的通讯地址。另外,支持了拥有全局名字的服务,也依靠 master 机器同步。比如,你可以从某台 skynet 节点注册一个叫 DATABASE 的服务节点,它只要将 DATABASE 和节点 id 的对应关系通知 master 机器,就可以依靠 master 机器同步给所有注册入网络的 skynet 节点。
master 做的事情很简单,其实就是回应名字的查询,以及在更新名字后,同步给网络中所有的机器。
skynet 节点,通过 master ,认识网络中所有其它 skynet 节点。它们相互一一建立单向通讯通道。也就是说,如果一共有 100 个 skynet 节点,在它们启动完毕后,会建立起 1 万条通讯通道。”

skynet/config配置文件里面有这么两条配置:

master = "127.0.0.1:2013"
standalone = "0.0.0.0:2013"  

然后再看:

skynet_start(struct skynet_config * config) {
// ...
if (config->standalone) {
    if (_start_master(config->standalone)) {
        fprintf(stderr, "Init fail : mater");
        return;
    }
}

// harbor must be init first
if (skynet_harbor_start(config->master , config->local)) {
    fprintf(stderr, "Init fail : no master");
    return;
}
// ...
}

这里配得standalone = “0.0.0.0:2013” 就表示这个skynet节点在本机开启2013端口作为master使用。
而如果这个节点不是master,那么这里配的master = “127.0.0.1:2013” 则告诉它master在哪里。

master纪录所有worker的信息是在skynet_handle文件实现的一个哈希表存储的。当由skynet_harbor发起注册register的时候,它就实现了一个句柄handle到skynet_context的映射。

真正到master的实现,得先了解模块化思想,这里每个服务提供者都做成了一个模块,放在service-src目录下,比如service_master.c,service_harbor.c …等等。 这里文件名都叫service_XXX 其实就是文章开头所说的agent。在agent中,用户可以用c来实现所有需求,也可以调用lua。这样就用lua实现了类似erlang的gen_server 回调模式。skynet_module 的作用就是模块管理。最终这些模块(agent)都做成了.so文件加载。每个模块都实现了create, init, release 几个函数。

基本上以上就是skynet的主体流程了。

c

Haskell(二)

在haskell(一)中学习了haskell的基本语法。在输入ghci之后能够进入haskell终端, 在终端里可以执行运算,写一些简单的函数,接下来要在文件中写代码,并编译,执行
创建hello.hs文件

main = putStrLn "Hello world !"

保存之后,编译

ghc -o hello hello.hs

编译之后执行

./hello

便能够看到

Hello world !

Haskell(一)

Haskell是一门纯函数式语言。它因为monads以及其类型系统而出名,初窥haskell,倒是觉得 其中的列表跟erlang特别像。
下面是基础语法:

-- 单行注释以两个破折号开头
{-  多行注释被
    一个闭合的块包围
-}
-----------------------------------------------
-- 1. 简单地数据类型和操作符
-----------------------------------------------

-- 你有数字
3 --3
-- 数学计算就像你所期待的那样
1 + 1 --2
8 - 1 --7
10 * 2 --20
35 / 5 --7.0 注意这里除了之后是浮点数
35 `div` 5 -- 7 
True
False
not True
not False
1 == 1 -- True
1 /= 1 -- False
1 < 10 -- True
"This is a string" -- 字符串用双引号
'a' -- 字符用单引号
"Hello" ++ "world !" -- "Hello world !" 字符串连接
"This is a string" !! 0 -- 一个字符串是一系列字符,取第一个就是'T'

-----------------------------------------------
-- 列表和元组 
-----------------------------------------------

-- 一个列表中得每一个元素都必须是相同类型
[1, 2, 3, 4, 5] == [1..5] -- True

-- 在haskell 你可以拥有含有无限元素的列表
[1..] -- 一个含有所有自然数的列表

-- 因为haskell 有“懒惰计算”,所以无限元素的列表可以正常运作。这意味着
-- haskell 可以只在它需要的时候计算。所以你可以请求
-- 列表中的第1000个元素,haskell 会返回给你
[1..] !! 999 -- 1000

-- haskell 计算了列表中 1 - 1000 个元素。。但是
-- 这个无限元素的列表中剩下的元素还不存在! haskell 不会
-- 真正地计算它们直到它需要

-- 连接两个列表
[1..5] ++ [6..10]

-- 往列表头增加元素
0:[1..5] -- [0, 1, 2, 3, 4, 5]

-- 列表中的下标
[0..0] !! 5 -- 5

-- 更多的列表操作
head [1..5] -- 1
tail [1..5] -- [2, 3, 4, 5]
init [1..5] -- [1, 2, 3, 4]
last [1..5] -- 5

-- 列表推导
[x * 2 | x <- [1..5]] -- [2, 4, 6, 8, 10]

-- 附带条件
[x * 2 | x <- [1..5], x * 2 > 4] -- [6, 8, 10]

-- 元组中的每一个元素可以是不同类型的,但是一个元组
-- 的长度是固定的
-- 一个元组
("haskell", 1)

-- 获取元组中的元素
fst("haskell", 1) -- "haskell"
snd("haskell", 1) -- 1

-----------------------------------------------
-- 3. 函数 
-----------------------------------------------
-- 一个接受两个变量的简单函数
add a b = a + b

-- 注意,如果你使用ghci(haskell 解释器)
-- 你将需要使用 `let`, 也就是
-- let add a b = a + b

-- 使用函数
add 1 2 -- 3

-- 你也可以把函数放置在两个参数之间
-- 附带倒引号:
1 `add` 2 --3

-- 你也可以定义不带字符的函数!这使得
-- 你定义自己的操作符!这里有个操作符
-- 来做整除
(//) a b = a `div` b
35 //4 --8

-- 守卫: 一个简单的方法在函数里做分支
fib x
  | x < 2 =x
  | otherwise = fib (x -1) + fib (x -2)

-- 模式匹配是类型的。这里有三种不同的fib
-- 定义。haskell将自动调用第一个
-- 匹配值的模式的函数。
fib 1 = 1
fib 2 = 2
fib x = fib (x - 1) + fib (x - 2)

-- 元组的模式匹配
foo (x, y) = (x + 1, y + 2)

-- 列表的模式匹配。 这里 `x`是列表的第一个元素,
-- 并且 `xs` 是列表剩余的部分。我们可以写
-- 自己的map 函数
myMap func [] = []
myMap func (x:xs) = func x:(myMap func xs)

-- 编写出来的匿名函数带有一个反斜杠,后面跟着
-- 所有的参数
myMap (\x -> x + 2)[1..5] -- [3, 4, 5, 6, 7]

-- 使用fold (在一些语言称为`inject`)随着一个匿名的
-- 函数。foldl1 意味着左折叠(fold left), 并且使用列表中的第一个值
-- 作为累加器的初始化值。
foldl1(\acc x -> acc + x) [1..5] -- 15

----------------------------------------------------------
-- 4. 更多函数
----------------------------------------------------------

-- 柯里化(currying):如果你不传递函数中所有的参数,
-- 它就变成“柯里化的”。这意味着,它返回一个接受剩余参数的函数.

add a b = a + b
foo = add 10 -- foo 现在是一个接受一个数并对其加10的函数
foo 5 -- 15

-- 另外一种方式去做同样的事
foo = (+10)]
foo 5 -- 15

-- 函数组合
-- (.) 函数把其他函数链接到一起
-- 举个例子,这里foo是一个接受一个值的函数。它对接受的值加10,
-- 并对结果乘以5, 之后返回最后的值
foo = (*5) . (+10)

-- (5 + 10) * 5 = 75
foo 5 --75

-- 修复优先级
-- haskell 有另外一个函数称为 `$`. 它改变优先级
-- 使得其左侧的每一个操作先计算然后应用到
-- 右侧的每一个操作。你可以使用 `.` 和 `$` 来除去很多
-- 括号:

-- before
(even (fib 7)) -- true

- after
even . fib $ 7 -- true

------------------------------------------------------------
-- 5. 类型签名
------------------------------------------------------------

-- haskell 有一个非常强壮的类型系统,一切都有一个类型签名。
-- 一些基本的类型
5 :: Integer
"hello" :: String
True :: Bool

-- 函数也有类型
-- `not` 接受一个布尔型返回一个布尔型:
-- not :: Bool -> Boll

-- 这是接受两个参数的函数:
--add :: Integer -> Integer -> Integer

-- 当你定义一个值,在其上写明它的类型是一个好实践
double :: Integer -> Integer
double x = x * 2

-------------------------------------------------------------
-- 6. 控制流和If语句
-------------------------------------------------------------
-- if 语句
haskell = if 1 == 1 then "awesome" else "awful" -- haskell = "awesome"

-- if 渔具也可以有多行,缩进是很重要的
haskell = if 1 == 1
            then "awesome"
            else "awful"

-- case 语句:这里是你可以怎样去解析命令行参数
case args of
    "help" -> printHelp
    "start" -> startProgram
    _ -> putStrLn "bad args"

-- haskell 没有循环因为它使用递归取代之。
-- map 应用一个函数到数组中的每一个元素

map (*2)[1..5] -- [2, 4, 6, 8, 10]

-- 你可以使用map来编写for函数
for array func = map func array

-- 然后使用它
for [0..5] $ \i -> show i

-- 我们也可以这样写
for [0..5] show

-- 你可以使用foldl或者foldr来分解列表
-- foldl (\x y -> 2*x + y) 4 [1, 2, 3] -- 43

-- 这和下面是一样的
(2 * (2 * (2 * 4 + 1) + 2) + 3)

--foldl 是左手边的,foldr是右手边的
foldr (\x, y -> 2*x + y) 4 [1, 2, 3] -- 16

-- 这和下面的是一样的
(2 * 3 + (2 * 2 + (2 * 1 + 4)))

-------------------------------------------------------
-- 7. 数据类型
-------------------------------------------------------

-- 这里展示在haskell 中你怎样编写自己的数据类型
data Color = Red | Blue | Green

-- 现在你可以在函数中使用它

say :: Color -> String
say Red = "You are Red !"
say Blue = "You ara Blue!"
say Green = "You are Green!"

-- 你的数据类型也可以有参数

data Maybe a = Nothing | Just a

-- 类型 Maybe 的所有
Just "hello"    -- of type `Maybe String`
Just 1          -- of type `Maybe Int`
Nothing         -- of type `Maybe a` for any `a`

--------------------------------------------------------
-- 8. haskell IO
--------------------------------------------------------

-- 虽然在没有解释monads 的情况下,IO不能被完全地解释,
-- 着手解释到位并不难

-- 当一个haskell程序被执行,函数`main` 就被调用。
-- 它必须返回一个类型`IO()`的值。举个例子

main :: IO ()
main = putStrLn $ "Hello, sky! " ++ (say Blue)
-- putStrLn has type String -> IO ()

-- 如果你能实现你的程序依照函数从String到String,那样编写IO是最简单的。
-- 函数
--      interact :: (String -> String) -> IO ()
-- 输入一些文本,在其上运行一个函数,并打印出输出

countLines :: String -> String
countLines = show . length . lines

main' = interact countLines

-- 你可以考虑一个 `IO()` 类型的值,当做一系列计算机所完成的动作的代表
-- 就像一个以命令式语言编写的计算机程序。我们可以使用`do`符号来把动作连接到一起。
-- 举个例子

sayHello :: IO ()
sayHello = do
    putStrLn "What is your name?"
    name <- getLine -- this gets a line and gives it the name "input"
    putStrLn $ "Hello, " ++ name

-- 练习:编写只读取一行输入的`interact`

-- 然而,`sayHello` 中得代码将不会被执行。唯一 被执行的动作是`main`的值。
-- 为了运行 `sayHello`, 注释上面`main` 的定义,并代替它:
-- main = sayHello

--让我们来更好地理解刚才所使用的函数 `getLine` 是怎样工作的。它的类型是:
-- getLine :: IO String
-- 你可以考虑一个 `IO a` 类型的值,代表一个当被执行的时候
-- 将产生一个 `a` 类型的值的计算机程序(除了它所做的任何事情之外)。我们可以保存
-- 和重用这个值通过`<-`
-- 我们也可以写自己的 `IO String` 类型的动作

action :: IO String
action = do
    putStrLn "This is a line, Duh"
    input1 <- getLine
    input2 <- getLine
    -- The type of the `do` statement is that of its last line
    -- `return` is not a keyword, but merely a function
    return (input1 ++ \n ++ input2) --return :: String -> IO String

-- 我们可以使用这个动作就像我们使用 `getLine`:
main'' = do
    putStrLn "I will echo two lines!"
    result <- action
    putStrLn result
    putStrLn "This was all, folks!"

-- `IO` 类型是一个"monad"的例子,haskell使用一个monad来 做IO得方式允许它是一门纯函数式语言。
-- 任何与外界交互的函数(也就是IO)都在它的类型签名处做一个`IO`标识
-- 这让我们推出 什么样的函数是“纯洁的”(不与外界交互,不修改状态)和 什么呀的函数是“不纯洁的”

-- 这是一个强有力的特征,因为并发地运行纯函数是简单的;因此,haskell中并发是非常简单的。

------------------------------------------------------------
-- 9. the haskell REPL
------------------------------------------------------------
-- 键入 `ghci` 开始repl.
--任何新值都需要通过 let 来创建
let foo = 5

-- 你可以查看任何值的类型,通过命令 :t
:t foo
foo :: Integer

-- 你也可以运行任何 `IO ()` 类型的动作

>sayHello
What is your name?
Friend!
Hello, Friend!

Cocos2dx解密执行lua文件

前一篇写了怎么将lua文件加密成,那么接着就该在cocos2dx中修改代码,使之能执行 解密后的lua代码了


cocos2dx是这样使用lua引擎的

// 初始化lua引擎
CCLuaEngine* pEngine = CCLuaEngine::defaultEngine();
CCScriptEngineManager::sharedManager()->setScriptEngine(pEngine);

CCLuaStack *pStack = pEngine->getLuaStack();

pStack->addLuaLoader(cocos2dx_lua_loader);

这里添加了cocos2dx_lua_loader,那么,在cocos2dx_lua_loader里面:

int cocos2dx_lua_loader(lua_State *L)
{
    std::string filename(luaL_checkstring(L, 1));
    // 这里我们将它改成查找.so后缀的lua文件
    size_t pos = filename.rfind(".so");
    if (pos != std::string::npos)
    {
        filename = filename.substr(0, pos);
    }

    pos = filename.find_first_of(".");
    while (pos != std::string::npos)
    {
        filename.replace(pos, 1, "/");
        pos = filename.find_first_of(".");
    }
    //后缀改为.so
    filename.append(".so");

    //使用一个tmpBuffer来读取密文
    unsigned long tmpSize = 0;
    unsigned char* tmpBuffer = CCFileUtils::sharedFileUtils()->getFileData(filename.c_str(), "rb", &tmpSize);
    if(!tmpBuffer){ return 1;}

    //解密后再传给codeBuffer执行
    unsigned long codeBufferSize = 0;
    unsigned char* codeBuffer = xxtea_decrypt(tmpBuffer, tmpSize, (unsigned char*)SCRIPT_KEY, sizeof(SCRIPT_KEY), &codeBufferSize);

    if (codeBuffer)
    {
        if (luaL_loadbuffer(L, (char*)codeBuffer, codeBufferSize, filename.c_str()) != 0)
        {
            luaL_error(L, "error loading module %s from file %s :\n\t%s",
                lua_tostring(L, 1), filename.c_str(), lua_tostring(L, -1));
        }
        delete []codeBuffer;
    }
    else
    {
        CCLog("can not get file data of %s", filename.c_str());
    }

    return 1;
}

我们采用的是xxtea加密,所以在这里,调用相反的算法,使用相同的秘钥SCRIPT_KEY 解密。 cocos2dx_lua_loader 这个函数会在当lua文件被required 进来的时候调用,因此就达到了加密和 解密的效果。 enjoying! 就在昨天,quick-cocos2dx 的2.2.1版本发布,已经支持lua的加密和解密了,也可以参考一下 http://cn.quick-x.com/

Lua代码加密

开发者为了防止代码泄漏,在发布前一般会对脚本进行加密,加密方式有多种, 比如常见的AES, XXTEA .. 等等,在cocos2dx加载加密后的lua文件后,解密之后再 执行.


加密工具我用c写了一个,代码放在github上了:https://github.com/zhizhen/cocos2dx-lua-crypto.git 关键代码如下

FILE *file;
char* inPath = argv[1];     //源文件路径
char* outPath = argv[2];    //目标文件路径
unsigned char* fileData = malloc(FILE_LEN);

file = fopen(inPath, "rb");
unsigned long num = fread(fileData, 1, FILE_LEN, file);
fclose(file);

unsigned int dataLen = 0;
unsigned char* data = xxtea_encrypt(fileData, num, (unsigned char*)KEY, 32, &dataLen);

file = fopen(outPath, "web+");
fwrite(data, 1, dataLen, file);
fclose(file);

在sh代码中遍历文件夹,调用c生成的工具对单个文件进行加密

#!/bin/sh
EXDIR=`cd $(dirname $0); pwd`
cd "$EXDIR/$1"

#echo please input source dir:
#read FROMDIR
#echo please input output dir:
#read TODIR

FROMDIR="lua"
TODIR="out"

rm -rf $TODIR
cp -r $FROMDIR $TODIR

deepls(){
    for x in $1/*
    do
        y=`basename $x .lua`
        if [ -f $x ]
        then
            $EXDIR/debug/file_encrypto "$EXDIR/$1/$y.lua" "$EXDIR/$2/$y.so"
        fi
        if [ -d $x ]
        then
            deepls "$1/$y" "$2/$y"
        fi
    done
}

deepls $FROMDIR $TODIR

find $TODIR -name '*.lua' -exec rm {} \;

执行./build.sh 就能将lua文件夹中的lua脚本全部加密后放到out目录下,然后就剩下 怎么在cocos2dx里面修改代码读取加密后的脚本问题了

INIT18 BOOT FAILURE

问题描述

在安装gentoo的时候,一切都安装完,在执行reboot这一步之后,无法正常启动grub


问题原因

相信你在执行reboot的时候,关机信息中看到了,系统无法umount cdrom


解决办法

改reboot为shutdown -h now,然后手工将安装光驱从virtualbox中删掉,再重新启动gentoo

One Billion Customers 部分摘录

是开端亦是转折

一只脚尚驻在过去,一只脚已踏入未来,中国是全世界最盛大的开端,也是最伟大的转折。


1989年TM是一场悲剧,但也是一次转折点。它是由党内的保守分子和改革人士之间的巨大 裂痕所导致的。保守分子赢得了这场战役,但是却输掉了整场战争。作为TM事件的后果, 党加速了私有化和市场改革的进程,因为党的威信已经被打破,只能通过快速提高人民 生活水平来重建。


看待中国这个全世界最宏大的开端和转折,同时思考外国公司和中国传统在这个过程中 所扮演的角色时,我们应该记住清朝时出现,毛泽东经常引用的一句口号:古为今用,洋为中用。


庄严的谈判

中国在2001年加入世界贸易组织,而谈判早在1973年就开始了,那时候中国还是乾隆盛世。 那一年,乔治.马格尔尼勋爵带领一支英国船队抵达中国的北部港口。作为乔治三世国王经验 最丰富的外交官之一,马格尔尼意在为英国商人打开广阔的中国市场。这本来就是一场 关于公平的讨论。中国出口丝绸,茶叶,家具和瓷器等产品,却很少甚至从来不进口任何东西。 金钱流入中国,但是从来不外流。因此马格尔尼带来英国生产的最好的产品,其中有来福枪, 加侬炮,堆成山的最好的毛纺织品,还有一个配备了驾驶员的热气球。受到了乾隆皇帝的冷待。 最后—中国真正有资格被批准进入全球贸易团体,是在206年以后。


我猜想大多数西方人都会怀疑从马格尔尼勋爵的中国之旅到中国最终被允许加入世界贸易组织之间 的二百年对在中国做生意到底有多大的影响。你也许会说这是老皇历了。但是在中国做生意的外国人 必须理解过去这二百年一点也不老,中国人在此期间所受的屈辱一点也不老。在中国人的 灵魂中,深深埋藏着的信念就是在过去二百年中,外国人用武力开道进入中国,是为了掠夺这个国家 的财富。他们从幼时起就被教导中国曾是全世界最强大的帝国,在各方面都是最优秀的,直到外国人 在18世纪末来到门口,无情地剥削这个民族,而这个民族在对外国人一点伤害也不曾有过。 所以即便在今天,仍有很多中国人,一旦谈到外国列强在中国的角色就会立刻变得愤怒。的确,谁也不能 对鸦片贸易以及英国人强加给中国人的这种灾难的流行性瘾毒,抑或日本在30到40年代早期堆中国大片 国土的占领以及随之而来的数百万人口的屠杀说出半句好话。


……


推动变革

89TM之后,1991年初,美中两国谈判代表们再次做到谈判桌前,中国的谈判代表们进退维谷。顽固的官僚们一点也不想为了讨好外国人而放弃自己的权利, 但是谈判代表们也明白中国的经济增长严重依赖于占到中国总出口三分之一的对美出口业务。最终, 谈判变成一场个人力量的较量。中国的谈判代表们只能通过李鹏总理的积极推进来击退来自各大部委和 国有企业的一片反对。李总理,天安门广场中政府的强硬派人物,无论是在国内还是国外 都受到广泛的鄙视。很多西方人都认为他是一个十足的顽固不化者。但他们没有意识到, 李鹏在推动谈判达成协议的过程中起到了至关重要的作用,他深深地希望作为一名改革者而被载入史册。 …..

Nginx中的try_files

在nginx的配置项里有try_files这一项,查了一下,是nginx0.6.36后增加的功能, 用于搜索指定目录下的N文件,如果找不到fileN,则调用fallback中指定的位置来 处理请求。利用它可以代替部分复杂的nginx rewrite语法


try_files file1 [file2...fileN] fallback

默认值:无 作用域:location


今天碰到一个问题,nginx 中这样写:

try_files $url $url/ /index.php

然后访问

localhost/guess/add/playWay/?pwid=3&gpid=1&inajax=1

这个访问被跳转到/index.php 之后,后面的参数

pwid=3&gpid=1&inajax=1

没有出现在$_GET变量中


于是上网查了try_files 的写法,说是如果要带上参数的话,必须这样写

try_files $url $url/ /index.php?$args

这样子前面那些参数就被带到了index.php中的$_GET变量里