Living a Simple Life is a Happy Life

有饭吃,自由自在,就非常开心

第一个AndroidApp-华容道

| Comments

第一个Android APP上线,很简单的一个小游戏:智取华容道

GooglePlay: https://play.google.com/store/apps/details?id=me.happy123.aklotski

百度应用下载地址: http://shouji.baidu.com/game/item?docid=7547918

小米商店下载地址: http://app.mi.com/detail/87656

老是对自己说,要不断学习,跟上时代浪潮。但是悲哀的发现,自己是跟的越来越吃力了。

早在大连搞电视机的时候,那时候Android刚出来,就对自己说,要好好看看。无奈之后各处辗转,这事就搁下了。

非不能也,实不为也!

过年空出来一个周,Python党也要写Android App了。

找了本 HeadFirst Java ,一天的时间扫了一遍Java的语法,写了几个小例子练练手。之后直接对着Android官方文档开干了。

流程就是 google -> 坑 -> 继续google ….,如此循环不已……

用父亲打扑克的机器,开个Eclipse基本上卡半天,本着一颗折腾的心,硬是PS出来几张背景图,小游戏<华容道>出炉啦。

图片和关卡都来自于代码发芽网,作者非常大度的授权我使用,非常感谢:

http://fayaa.com/

开发日志:

很巧的,我在知乎看到了类似”如何20天零基础开发Android游戏” 的问题。大家都是对这种问题不屑一顾的,但我从开始到完成恰好十来天,可以作为参考记录一下。

Day1

确立市场方向,要做个益智类游戏,啥好呢?啥好呢?我把所有的Android的华容道下载下来玩了一遍,嗯,都很粗糙,看来有的玩,就是它了。

这个时候,其实你已经完成一半工作了。没错,就是那句什么 决定出发的时候,旅行就完成了一半。 这个时候就不要纠结于做什么了,关键是如何做。

Day2

开始搭建开发环境,先后折腾了Android Studio和Eclipse,家里翻墙实在慢呀,另外我用父亲打扑克的机器跑Android Studio是在跑不动啊,最后装了Eclipse。推荐个链接,有兄弟收集了开发环境套装国内下载,你值得拥有。

http://www.cnblogs.com/bjzhanghao/archive/2012/11/14/android-platform-sdk-download-mirror.html

Day3

开始学习Java,神马?你都不会Java还来搞Android。嗯,我也没想到,有一天我要搞Java!要搞Java啊,我对它的印象还是航空母舰啊~~~。没办法,找了本 Head First Java 电子书,照着书里面的例子一个个敲,没错,不论懂不懂,就一个个敲。,幸亏 Head First 系列专门照顾低智商读者的,像我这样的一天敲到了300多页的例子。

Day4

不知不觉就把600页的书敲完了。嗯,看来那种xx天精通xxx,也不是骗人的,现在我的状态,估计能出去忽悠一些HR了。

Day5

先不管Android,搞一个穷人版的华容道。就开始封装三个类,什么接口啊,多态啊,统统不用,就是简单继承。搞了个核心库,棋盘就用 0011 先画出来了。嗯,最后可以玩了。不过只有我一个人懂得怎么玩。 不过很有成就感啊,看来一天写个俄罗斯方块之类的不是难事啊。

Day6

这个核心库很关键啊,挂了直接影响后面的开发啊。但是我用 0011 看的很累啊。赶紧google之,看看Java怎么搞测试~~~,好,最后集成了JUnit,顺便学了学怎么在Eclipse里面跑测试,最后写了一堆测试用例,果然发现了BUG,某些棋子竟然能对角线走动,罪过罪过。

Day7

Android程序怎么搞,现在八字还没一撇呢。热情啊,就要消耗光了。

赶紧找了本网上号称最简单的Android开发教程 第一行代码 ,再开始对着敲。没错,我就是只会这一招。

Day8

还在敲~~~,敲不完了。不是打字慢,而是电脑一开Android 手机模拟器就陷入昏睡模式,一气之下不敲了,直接接上手机开干。

Day9-Day13

游戏逻辑早写完了,我对Android App的开发印象就是:画View。没错,就是处理各种图像拉伸,各种尺寸适配,各种布局位置,各种奇葩的手机尺寸,各种API新老接口整合。

简单列举下中间遇到的坑:

  • 怎么在各种尺寸下画出一个比较漂亮的棋盘布局

  • 怎么用相对布局把几个菜单搞得整齐一点

  • 怎么PS出一个不那么生硬的背景,没错,我还同时开了Photoshop做美工,这个时候电脑已经处在超脱状态

  • 怎么在两个View直接传数据,怎么处理活动的生存周期

  • 怎么加背景音乐,怎么搞成服务,怎么能让程序退出的时候结束服务,怎么搞得好听一点,没错,这个时候我又客串DJ,研究了MP3各码率下音质和文件大小的关系,最后以专业乐评人的视角选择了一首古筝曲

  • 晕,现在为止还是只有一关呢,怎么办?我到 代码发芽 网上,写个Python脚本爬下来了所有关卡,同时邮件问作者申请授权

  • 到底是拖动棋子效果好呢,还是点击移动棋子效果好呢?我又化身PM,反反复复走最简单的一关, 十八步 ,最后确定还是点击效果好。没错,到目前为止,这个游戏我只玩通了三关,我的智商就是这样。最后我爸还解决了 横刀立马 ,我怎么也不行。

  • 发现一个重大问题,原来我一直以为只有五虎上将的,一共五横五竖十个长条棋子,突然发现有一关里面出现了魏延,我晕,再调吧

  • 又发现个问题,Android的默认sqlite模块好像没说怎么把已有的数据库集成进去,怎么办,Google吧

Day14

到各个Android市场注册账号,提交APP,等审核,之间经历若干PS工作,这也是辛苦活啊。

~~~~~

就是这样,我在各种大坑小坑瞎搞之后,瞎搞出来了。。。

结论

  • 所谓零基础xx天搞APP是可以的,但是有条件,你只是Android零基础或者Java零基础,其它编程基础还是要有的,不然期间大坑小坑肯定要浪费很多时间

  • 熟练工和初级工效率差距巨大,一天写个游戏APP不是开玩笑

  • 这样写出的APP完成度低,要加入完整的体验流程,工作量还是很大的

Javascript设计模式 - 笔记3

| Comments

javascript 里面的继承是个非常复杂的话题,一言蔽之,就是你要替解释器干点活。另外,javascript属于使用原型式继承的语言,这个比较少见,所以直觉上不好拐弯。

先回顾比较简单的办法

类式继承

一个简单的类

1
2
3
4
5
6
7
8
9
10
11
/* class Person */
function Person(name) {
    this.name = name;
}

Person.prototype.getName = functino() {
    return this.name;
}

var reader = new Person('brainzhang');
reader.getName();

好,下面定义一个它的子类

1
2
3
4
5
6
7
8
/* Class Author */
function Author(name, books) {
    Person.call(this, name);
    this.books = books;
}
Author.prototype = new Persion();  //set up the prototype chain
Author.prototype.contructor = Author; //set the constructor attribute to author
Author.getBooks = function() {return this.books;}

容易费解的是这两行:

1
2
Author.prototype = new Persion();  //set up the prototype chain
Author.prototype.contructor = Author; //set the constructor attribute to author

javascript中,每个对象都有一个原型对象,在创建一个对象时,javascript会自动将其原型对象设置为其构造函数的prototype属性所指的对象。 在访问对象的某个成员时,如果这个成员未见于当前对象,那么javascript会沿着原型链向上逐一访问每个原型对象(最顶端为Object.prototype对象),直到找到这个成员为止。 这意味着,为了让一个类继承另一个类,只需将子类的prototype设置为基类的一个实例即可。

第二行将prototype的constructor属性重新设置为Author。是因为: 定义一个构造函数时,其默认的prototype对象是一个Object类型的实例,其contructor属性会被设置为构造函数本身。如果手工将prototype设置为另一个对象,就要重新设置其constructor属性。

最后,为了简化类的声明,可以将这些工作封装在extend函数中:

1
2
3
4
5
6
function extend(subClass, superClass) {
    var F = function(){};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.contructor = subClass;
}

作为改进,定义了一个新对象F,避免基类对象过大,创建实例浪费资源。

但是这样还有个小缺点,声明 Author的时候,还要显式的调用一下Person.call(),下面这个版本进一步做了改进:

1
2
3
4
5
6
7
8
9
10
11
function extend(subClass, superClass) {
    var F = function(){};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.contructor = subClass;

    subClass.superclass = superClass.prototype;
    if(superClass.prototype.contructor == Object.prototype.constructor) {
        superClass.prototype.contructor = superClass;
    }
}

增加了一个superclass属性来直接访问基类,这样声明Author的时候可以这么写:

1
2
3
4
5
6
7
/* Class Author */
function Author(name, books) {
    Author.superclass.contructor.call(this, name);
    this.books = books;
}
extend(Author, Person);
Author.getBooks = function() {return this.books;}

原型式继承

TODO

Git Cheat

| Comments

整理一下经常忘记的Git命令

remote 同步

1
2
3
4
$ git remote -v                                                     // 查看当前远程版本库
$ git remote add cocos2d-x git://github.com/cocos2d/cocos2d-x.git   // 添加原始版本库
$ git fetch cocos2d-x                                               // 获取原始版本库的更新
$ git merge cocos2d-x/master                                        // 合并原始版本库的代码到当前版本库中,合并前确保当前分支是master

remote branch

1
2
3
4
5
6
$ git branch -a                                                     //查看所有分支
$ git checkout -b branches/xxx origin/branches/xxx                  //chekcout远程分支
$ git push origin --delete <branchname>                             //删除远程分支
$ git push origin --delete tag <tagname>                            //删除远程tag
$ git push origin :<branchName>                                     //推送一个空分支到远程分支,相当于删除分支
$ git push origin :refs/tags/<tagName>                              //推送一个空tag到远程,相当于删除tag

删除不存在对应远程分支的本地分支

假设这样一种情况:

  1. 我创建了本地分支b1并pull到远程分支 origin/b1;
  2. 其他人在本地使用fetch或pull创建了本地的b1分支;
  3. 我删除了 origin/b1 远程分支;
  4. 其他人再次执行fetch或者pull并不会删除这个他们本地的 b1 分支,运行 git branch -a 也不能看出这个branch被删除了,如何处理?
1
git fetch -p                                                        //在fetch之后删除掉没有与远程分支对应的本地分支

重命名远程分支

在git中重命名远程分支,其实就是先删除远程分支,然后重命名本地分支,再重新提交一个远程分支

1
2
3
$ git push --delete origin devel                                    //删除远程分支
$ git branch -m devel develop                                       //重命名本地分支
$ git push origin develop                                           //推送本地分支

把本地tag推送到远程

1
$ git push --tags

获取远程tag

1
$ git fetch origin tag <tagname>

合并branch上的指定文件

branches/A 上修改了一个文件A.h,新增了一个文件B.h,删除了一个文件C.h。

1
2
3
4
5
6
7
$ git checkout master                                               //首先切换到master分支

$ git checkout -p branches/A A.h                                    //不切换branch,把RemLanbranches/A上的A.h更新到当前分支

$ git checkout branches/A B.h                                       //去掉-p参数,新增该B.h文件

$ rm C.h                                                            //删除文件目前还没找到其他办法,但效果是一样的

分支的衍合

参考:http://git-scm.com/docs/git-rebase

diff

只显示两个分支间的差异,如果你想找出‘master’,‘test’的共有 父分支和’test’分支之间的差异,你用3个‘.’来取代前面的两个’.’ 。

1
$ git diff master..test

显示你当前的索引和上次提交间的差异;这些内容在不带”-a”参数运行 “git commit”命令时就会被提交。

1
$ git diff --cached

显示你工作目录与上次提交时之间的所有差别,这条命令所显示的 内容都会在执行”git commit -a”命令时被提交。

1
$ git diff HEAD

如果你要查看当前的工作目录与另外一个分支的差别,你可以用下面的命令执行: 这会显示你当前工作目录与另外一个叫’test’分支的差别。你也以加上路径限定符,来只 比较某一个文件或目录。

1
$ git diff test

显示你当前工作目录下的lib目录与上次提交之间的差别(或者更准确的 说是在当前分支)。

1
$ git diff HEAD -- ./lib

如果不是查看每个文件的详细差别,而是统计一下有哪些文件被改动,有多少行被改 动,就可以使用‘–stat’ 参数。

1
$ git diff --stat

Javascript设计模式 - 笔记2

| Comments

如何封装一个对象

门户大开型

最简单的办法就是按传统方法创建一个类,用一个函数来做其构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Book = function(isbn, title, author) {
    if (isbn === undefined) {
        throw new Error('Book constructor requires an isbn.');
    }
    this.isbn = isbn;
    this.title = title || 'No title specified';
    this.author = author || 'No title specified';
}

//define by attr
Book.prototype.display = function() {...};

//define by object literals
Book.prototype = {
    display: function(){...},
    checkIsdn: function(){...}
};
  • 优点:简单
  • 缺点:没有保护,需要加各种校验。但内部的成员还是有很大可能被修改的。

语法修饰增强型

用setattr,getattr等赋值取值方法及命名规范区别私有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var Book = function(isbn, title, author) {
    if (isbn === undefined) {
        throw new Error('Book constructor requires an isbn.');
    }
    this.setIsbn(isbn);
    this.setTitle(title);
    this.setAuthor(author);
}

//define by attr
Book.prototype.display = function() {...};

//define by object literals
Book.prototype = {
    _checkIsdn: function() {...}

    setIsbn: function(isbn) {
        if!(this._checkIsbn(isbn)) {
            throw new Error("Invalid isbn");
        }
        this._isbn = isbn;
    }

    getIsbn: function() {
        return this._isbn;
    }
    .......
};
  • 优点:简单,安全性也有所增强
  • 缺点:不是真正的私有成员,内部的成员还是有很大可能被修改的。

闭包实现私有成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var Book = function(iisbn, ititle, iauthor) {

    //private attributes
    var isbn, title, author;

    //private method
    function _checkIsbn(iisbn) {
        ...
    }

    //privileged methods
    this.getIsbn = function() {
        return isbn;
    };

    this.setIsbn = function(iisbn) {
        this._checkIsbn(iisbn) ...
        isbn = iisbn;
    }

    .......

    //contructor code
    this.setIsbn(iisbn);
    this.setTitle(ititle);
    this.setAuthor(iauthor);
};

//public, non-privileged methods

Book.prototype = {
    display: fucntion(){},
    ....
}

这里应用了js的闭包特性,isbn等属性不再通过this来引用,而是放到函数的构造器里面。既要访问到私有成员,又要对外的方法放到函数的构造中,对私有成员没有依赖的函数用prototype。

  • 优点:比较完整的模拟了private特性
  • 缺点:private方法不再存在prototype里面,这样没生成一个新的对象实例都会为每个每个私有方法和特权方法生成一个新副本,耗费内存。

实现静态方法和属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var Book = (function() {

    //private static attributes
    var numberOfBooks = 0;

    //private static method
    function checkIsbn(iisbn) {
        ...
    }

    //return the contructor
    return function(iisbn, ititle, iauthor) {
        //private attributes
        var isbn, title, author;

        //privileged methods
        this.getIsbn = function() {
            return isbn;
        };

        this.setIsbn = function(iisbn) {
            this._checkIsbn(iisbn) ...
            isbn = iisbn;
        }
        .......

        //contructor code
        numOfBooks++;
        this.setIsbn(iisbn);
    }
})();

//public, static methods
Book.converTotitleCase = function(){...};

//public, non-privileged methods

Book.prototype = {
    display: fucntion(){},
    ....
}

这里和闭包实现私有成员的区别就在于构造函数变成了一个内嵌函数,这样就创建了一个闭包,可以把静态的私有成员声明在最顶层。

实现常量

常量就设置为一个私有静态属性,用大写区分即可。我认为没有必要实现一个取值器去限制,用CONST前缀从代码风格上约束即可。

Javascript设计模式 - 笔记1

| Comments

富有表现力的javascript

弱类型语言

javascript中有三种原始类型:布尔型、数值型(不区分浮点数和整数)和字符串型。

此外,还有对象类型和包含可执行代码的函数类型。前者是一种复合类型(数组是一种特殊的对象)。

最后,还有空类型(null)和未定义类型(undefined)。

原始数据类型按值传送,其他数据类型则按引用传送。

函数是一等对象

  • 匿名函数
1
2
3
4
5
(function(){
    var foo = 10;
    var bar = 2;
    alert(foo * bar);
})();
  • 闭包
1
2
3
4
5
6
7
8
9
var baz;
(function(){
    var foo = 10;
    var bar = 2;
    baz = function(){
        return foo * bar;
    }
})();
baz(); //baz可以访问foo和bar,即使是在匿名函数外面执行
  • 作用域、嵌套函数和闭包

    • js中,只有函数具有作用域:

      在一个函数内部声明的变量,外部无法访问; 定义在一个函数中的变量在该函数的内嵌函数中是可以访问的

    • js中的作用域是词法性的:

      函数运行在定义他们的作用域中,而不是调用他们的作用域中; 可以利用这个特性定义静态方法和属性;

对象的易变性(mutable)和内省(introspection)

  • 易变性:js中可以对象前定义的类和实例化的对象进行修改
  • 内省:js中可以在运行时检查对象所具有的属性和方法

接口

  • 接口也是一种对象,判断一个类是否是实现了某类接口,就是传入这个接口,而后比较。

    java有专门的接口类,C++有虚基类,而C在linux kernel中的对象设计方法实际上也是一种接口实现,这都是接口在语言层面支持的体现

  • 封装(encapsulation)和信息隐藏:信息隐藏是目的,而封装则是藉以达到这个目的的技术

    java和C++有 private关键字作为支持,Python有’__‘的命名约定,js中一般用闭包来模拟

接口实现

用注释来模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
interface Composite {
    function add(child);
    function remove(child);
    ...
}

interface FormItem{
    function save();
}

var CompositeForm = function(id, method, action) {
    //implements Composite, FromItem
    ...
};

CompositeForm.prototype.add = function(child) {
    ...
}
CompositeForm.prototype.remove= function(child) {
    ...
}
CompositeForm.prototype.save= function() {
    ...
}

  • 优点:简单明了,代码体积小
  • 缺点:无法错误检查

用属性检查模仿接口

约定所有类明确声明实现了那些接口,和这些类打交道的对象可以针对这些声明做检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
interface Composite {
    function add(child);
    function remove(child);
    ...
}

interface FormItem{
    function save();
}

var CompositeForm = function(id, method, action) {
    //implements Composite, FromItem
    this.implementsInterfaces = ['Composite', 'FromItem'];
    ...
};
    ...

function addForm(formInstance) {
    if (!implements(formInstance, 'Composite', 'FormItem')) {
        throw new Error("object does not implement a required interface");
    }
}

function implements(objects) {
    for (var i = 1; i < arguments.length; i++) {
        var interfaceName = arguments[i];
        var interfaceFound = false;
        for (var j = 0; j < object.implementsInterfaces[j] == interfaceName) {
            interfaceFound = true;
            break;
        }
        if (!interfaceFound) {
            return false;
        }
    }
    return true;
}

  • 优点:有错误检查
  • 缺点:每次调用都要检查,啰嗦,另外防不住有说了实现但没有干活的

鸭式辨型模仿接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var Composite = new Intreface('Composite', ['add', 'remove']);
var FormItem = new Interface('FormItem', ['save']);
var CompositeForm = function(id, method, action) {
    ...
};

function addForm(formInstance) {
    ensureImplements(formInstance, Composite, FormItem);
    ......
}

var Interface = function(name, methods) {
    if (arguments.length != 2) {
        throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2.");
    }
    this.name = name;
    this.methods = [];
    for (var i = 0; len = methods.length; i < len; i++) {
        if (typeof methods[i] !== 'string')  {
            throw new Error("Interface contructor expects method names to be " +
                            "passed in as string");
        }
    }
    this.methods.push(methods[i]);
}

Interface.ensureImplements = function(object) {
    if (arguments.length < 2) {
        throw Error("Functino Interface.ensureImplements called with " + arguments.length +
                    "arguments, but expected at leaset 2.");
    }

    for (var i = 1, len = arguments.length; i < len; i++) {
        if (interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments" +
                            "two and above to be instances of Interface.");
        } 
    }

    for (var j = 1, methodsLen = interface.methods.length; j < methodsLen; j++) {
        var method = interface.methods[j];
        if(!object[method] || typeof object[method] !== 'function') {
            throw new Error("Function Interface.ensureImplements: object " +
                            "does not implement the " + interface.name)  +
                            "interface.Method " + method + " was not found.");
        }
    }
}

  • 优点:进一步加强了错误检查
  • 缺点:增大了调试难度

Macvtap Ethernet Support Tcpdump

| Comments

用macvtap模拟网卡时,用tcpdump抓包是抓不到的,后来发现3.14版本以上的内核修正了这一点。

参考这个提交:

https://github.com/torvalds/linux/commit/6acf54f1cf0a6747bac9fea26f34cfc5a9029523

Linux多网卡多路由设置

| Comments

折腾了半天,原始出处不知道了,转记一下。

比如如果一个linux服务器有三个口接三个不同的网络,假设对应的网络信息是如此

  • eth0是电信,ip地址为1.1.1.1/24,电信网关为1.1.1.254

  • eth1是网通,ip地址为2.2.2.2/24,网通网关为2.2.2.254

  • eth2是教育网,ip地址为3.3.3.3/24,教育网网关为3.3.3.254

传统情况下,如果是为了从内向外访问获得更好的速度,让访问电信走电信,访问网通走网通,那么配置是网关只能够配置一个。

比如以电信为主的,那么网关就只设置电信的1.1.1.254,而针对网通和教育网设置不同的路由,路由下一跳指向网通和教育网对应的 网关。

如果这样做的目的只是实现内部访问外面,那么是没问题了,但是如果是为了让外面的用户能够正常访问到服务器上的服务就会出问题。比如电信用户会无法访问网通和教育网的ip,网通用户会无法访问电信和教育网的ip。

要解决这个问题,思路就是由哪个网口进来的流量希望全部就由哪个回去。用lartc里面提到的方法就是来源的口不同,走不同的路由表。在默认的路由表基础上再建立三个路由表。

用 ip route show 可以看到默认有local,main,default三个路由表,这三个路由表的名称命名来自 /etc/iproute2/rt_tables ,这里先在这个配置文件里面添加三个不同的路由表表名,

1
2
3
echo “101 ChinaNet” >> /etc/iproute2/rt_tables
echo ”102 ChinaCnc“ >> /etc/iproute2/rt_tables
echo ”103 ChinaEdu“ >> /etc/iproute2/rt_tables

之后建立这三个路由表的内容,因为这三个路由表的只是用来响应来自不同接口的,而不是用来相应从哪个接口出去的,所以只需要每个路由表里面建立默认网关即可。

1
2
3
ip route add default via 1.1.1.254 dev eth0 table ChinaNet
ip route add default via 2.2.2.254 dev eth1 table ChinaCnc
ip route add default via 3.3.3.254 dev eth2 table ChinaEdu

之后再加上三条规则,使来自不同的口的走不同的路由表

1
2
3
ip rule add from 1.1.1.1 table ChinaNet
ip rule add from 2.2.2.2 table ChinaCnc
ip rule add from 3.3.3.3 table ChinaEdu

至此无论是电信还是网通还是教育网用户,访问三个ip的任意一个地址都能够连通了。即便是服务器上本身的默认路由都没有设置,也能够让外面的用户正常访问。

命令汇总:

1
2
3
4
5
6
7
8
9
10
11
12
13
ip route show

echo “101 ChinaNet” >> /etc/iproute2/rt_tables
echo ”102 ChinaCnc“ >> /etc/iproute2/rt_tables
echo ”103 ChinaEdu“ >> /etc/iproute2/rt_tables // 这里也可以直接通过Vi编辑

ip route add default via 1.1.1.254 dev eth0 table ChinaNet
ip route add default via 2.2.2.254 dev eth1 table ChinaCnc
ip route add default via 3.3.3.254 dev eth2 table ChinaEdu

ip rule add from 1.1.1.1 table ChinaNet
ip rule add from 2.2.2.2 table ChinaCnc
ip rule add from 3.3.3.3 table ChinaEdu //如果用数字,可以不要上面的“echo”过程

Libpcap PACKET_MMAP内存分配

| Comments

libpcap为了提高效率,调用setsockopt(handle->fd, SOL_PACKET, PACKET_RX_RING,(void *) &req, sizeof(req))时采用kmalloc分配内存。

可以参考:

https://www.kernel.org/doc/Documentation/networking/packet_mmap.txt

kmalloc底层依赖linux的slab内存分配机制,在2.6.22内核之后,slub取代slab成为默认的内存分配器。空间和时间上都有所提升。值得升级。

另外,centos5.9默认采用的2.6.18内核,编译的时候默认的KMALLOC_MAX_SIZE 设置为size-131072,这对于有很大内存的机器,分配效率是不高的。

最简单的办法就是用rpm包升级到2.6.33.9-rt31.86.el5rt,这个内核编译的时候已经将KMALLOC_MAX_SIZE设置为size-4194304。

这个问题是追踪libpcap的抓包程序,内存分配频繁失败发现的。

只能说,内核升级频繁,很多编译开关影响很大,要想全面发挥linux的性能,只能紧跟潮流啊。

Docker中的网络

| Comments

Docker的默认网络是非常弱的,他使用的是一个虚拟网桥和container中的veth pair通信,在container中,默认是没有对外的IP的,外部主机或容器只能通过NAT,或者自定义iptable来实现主机或容器间的互联互通。 这种局限性非常明显:

  • 如果我要配置一个sshd service,需要手工配置转发规则,非常不便

  • 无法使用DHCP

  • NAT无法在宿主机上用一个端口提供不同服务,所以有多个container绑定到一个物理网卡时,因为无法分配多个对外IP,所以诸如Http 这样的服务只能跑在同一IP的不同端口上。

  • 在Container中无法正常tcpdump

理想的容器内网卡应该像VMware的NSX那样,让你’基本上’感觉不到这是个虚拟的网卡,当然,这个和Docker的初衷有点不符了。但我们解决问题为先,工具是那一个,但不同人用法不同。

将Docker Container连接到本地网络,有四种搞法 (具体请参考:http://blog.oddbit.com/2014/08/11/four-ways-to-connect-a-docker/),下面简单描述下:

  • 采用官方默认的搞法,用NAT:
1
2
3
ip addr add 10.12.0.117/21 dev em1

docker run -d --name web -p 10.12.0.117:80:80 centos/simpleweb

这种方法简单,但也有上面所说的各种缺点

  • 建立自己的网桥和veth pair,为每个网桥分配一个IP,每个veth对绑定一个网桥,映射进docker容器,这样在容器内就得到了一个接近于真实的网卡。并且有能与本地网络的通信

    这种办法其实是对默认docker网络实现的一种升级,但是解决了原来的诸多局限,缺点是操作比较麻烦,另外容器内用tcpdump也会有问题

  • 使用Open vSwitch Bridge,模拟第二种办法。

    这种方法就是用Open vSwitch简化了操作,但是又引入了一套东西。

  • 建立macvlan虚拟网卡,容器启动后用nsenter工具映射到容器的network namespace中

    这种办法我觉得是最干净简洁的,而且采用macvlan,还意外获得了一种能力,就是你能在宿主机上创建子macvlan设备,从而能向容器内的macvlan设备打入精确的流量。 采用这种方法得到的container,可以在里面启动sshd,远程ssh上去各种操作,这样使用同一般的虚拟机没有多大差别。 另外,在容器内tcpdump包也很完美,如果想捕获二层协议包,可以用macvtap替换macvlan。

需要注意的是,如果想要tcpdump macvtap,需要linux kernel 3.14以上的支持,参见这里

最后推荐为了简化macvlan的操作,我写的一个小工具:dockerfly

参考:

  • Linux 上的基础网络设备详解

http://www.ibm.com/developerworks/cn/linux/1310_xiawc_networkdevice/index.html

  • Linux 上虚拟网络与真实网络的映射

http://www.ibm.com/developerworks/cn/linux/1312_xiawc_linuxvirtnet/index.html

  • 网络虚拟化技术: TUN/TAP MACVLAN MACVTAP

https://blog.kghost.info/2013/03/27/linux-network-tun/

  • Coupling Docker and Open vSwitch

http://fbevmware.blogspot.com/2013/12/coupling-docker-and-open-vswitch.html

  • four ways to connect a docker

http://blog.oddbit.com/2014/08/11/four-ways-to-connect-a-docker/

  • Docker containers should not run an SSH server

https://news.ycombinator.com/item?id=7950326

  • Proposal: Native Docker Multi-Host Networking

https://github.com/docker/docker/issues/8951

多个git账号之间的切换

| Comments

做过很多遍了,却总是记不住,这回从头来描述一下。

介绍

所谓多个git账号,可能有两种情况:

  • 我有多个github的账号,不同的账号对应不同的repo,需要push的时候自动区分账号

  • 我有多个git的账号,有的是github的,有的是bitbucket的,有的是单位的gitlab的,不同账号对应不同的repo,需要push的时候自动区分账号

这两种情况的处理方法是一样的,分下面几步走:

处理

  • 先假设我有两个账号,一个是github上的,一个是公司gitlab上面的。先为不同的账号生成不同的ssh-key
1
    ssh-keygen -t rsa -f ~/.ssh/id_rsa_work -c [email protected]
然后根据提示连续回车即可在~/.ssh目录下得到id_rsa_work和id_rsa_work.pub两个文件,id_rsa_work.pub文件里存放的就是我们要使用的key
1
    ssh-keygen -t rsa -f ~/.ssh/id_rsa_github -c [email protected]
然后根据提示连续回车即可在~/.ssh目录下得到id_rsa_github和id_rsa_github.pub两个文件,id_rsa_gthub.pub文件里存放的就是我们要使用的key
  • 把id_rsa_xxx.pub中的key添加到github或gitlab上,这一步在github或gitlab上都有帮助,不再赘述

  • 编辑 ~/.ssh/config,设定不同的git 服务器对应不同的key

1
2
3
4
5
6
7
8
9
10
11
12
# Default github user([email protected]),注意User项直接填git,不用填在github的用户名
Host github.com
 HostName github.com
 User git
 IdentityFile ~/.ssh/id_rsa_github

# second user([email protected])
# 建一个gitlab别名,新建的帐号使用这个别名做克隆和更新
Host 172.16.11.11
 HostName 172.16.11.11
 User work
 IdentityFile ~/.ssh/id_rsa_work

编辑完成后可以使用命令 ssh -vT [email protected] 看看是不是采用了正确的id_rsa_github.pub文件

这样每次push的时候系统就会根据不同的仓库地址使用不同的账号提交了

  • 从上面一步可以看到,ssh区分账号,其实靠的是HostName这个字段,因此如果在github上有多个账号,很容易的可以把不同的账号映射到不同的HostName上就可以了。比如我有A和B两个账号, 先按照步骤一生成不同的key文件,再修改~/.ssh/config 内容应该是这样的。
1
2
3
4
5
6
7
8
9
10
11
12
# Default github user([email protected]),注意User项直接填git,不用填在github的用户名
Host A.github.com
 HostName github.com
 User git
 IdentityFile ~/.ssh/id_rsa_github_A

# second user([email protected])
# 建一个gitlab别名,新建的帐号使用这个别名做克隆和更新
Host A.github.com
 HostName github.com
 User git
 IdentityFile ~/.ssh/id_rsa_github_B

同时你的github的repo ssh url就要做相应的修改了,比如根据上面的配置,原连接地址是:

[email protected]:testA/gopkg.git

那么根据上面的配置,就要把github.com换成A.github.com, 那么ssh解析的时候就会自动把testA.github.com 转换为 github.com,修改后就是

[email protected]:testA/gopkg.git

直接更改 repo/.git/config 里面的url即可

这样每次push的时候系统就会根据不同的仓库地址使用不同的账号提交了

一些题外话

我有一个repo,想要同时push到不同的仓库该如何设置?

很简单, 直接更改 repo/.git/config 里面的url即可,把里面对应tag下的url增加一个就可以了。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[remote "GitHub"]
    url = [email protected]:elliottcable/Paws.o.git
    fetch = +refs/heads/*:refs/remotes/GitHub/*
[branch "Master"]
    remote = GitHub
    merge = refs/heads/Master
[remote "Codaset"]
    url = [email protected]:elliottcable/paws-o.git
    fetch = +refs/heads/*:refs/remotes/Codaset/*
[remote "Paws"]
    url = [email protected]:Paws/Paws.o.git
    fetch = +refs/heads/*:refs/remotes/Paws/*
[remote "Origin"]
    url = [email protected]:Paws/Paws.o.git
    url = [email protected]:elliottcable/paws-o.git

上面这个立即就是有4个远端仓库,不同的tag表示不同的远端仓库,最后的Origin标签写法表示默认push到github和codaset这两个远端仓库去。当然,你可以自己随意定制tag和url

我有一个github的repo,clone没有问题,push的时候总是报错:error: The requested URL returned error: 403 while accessing xxx

这个问题也困扰了我一段时间,后来发现修改 repo/.git/config 里面的url,把https地址替换为ssh就好了。

例如

url=https://[email protected]/derekerdmann/lunch_call.git

替换为

url=ssh://[email protected]/derekerdmann/lunch_call.git

参考

http://stackoverflow.com/questions/7438313/pushing-to-git-returning-error-code-403-fatal-http-request-failed http://stackoverflow.com/questions/849308/pull-push-from-multiple-remote-locations/3195446#3195446