使用Scrapy构建自己的定制网络爬虫

MitchellChu 2016-03-12 其他技术 编程语言
1
爬虫(Web Spider or Web Crawler),顾名思义:一个在网站之间互相游走的虫子,专好觅食各类页面数据。随着Spider技术门槛降低,爬虫也开始泛滥起来,很多时候爬虫变成了扒虫。然,技术本无善恶,全在用者之念。此处不做过多计较,我们当下要讨论也仅仅是定制一款自己的网络爬虫,仅此。
爬虫就Mitchell个人经验来说分为两大类:
•通用型网络爬虫:该类爬虫并无明确采集目标,每个能够爬及的页面都是其采集对象,除非满足系统指定条件,否则此类爬虫基本全年无休的辛勤劳作。最常见的就是搜索引擎的爬虫,如:Google,Baidu,Yahoo,Bing等;
•专用型网络爬虫:这种爬虫仅针对自身偏好的网站或者主题作为采集目标,采集到的内容或涉及到的URL为此爬虫不感兴趣的,将被爬虫直接忽略,此类爬虫根据需要采集的目标多寡采集时间有所不同。如:各类垂直搜索站,金融爬虫,站点采集等;

C# HttpWebResponse下载限速

MitchellChu 2016-03-10 .NET技术
0

在使用请求网络内容的时候,一般如下面这种方法:

// 非生产环境代码,请在实际使用时进行必要调整
// —— Mitchell Chu
// Blog:blog.useasp.net

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

using(var response = (HttpWebResponse)request.GetResponse())
using(var stream = response.GetResponseStream())
{
    byte[] buffer = new byte[4096];
    int read = 0;
    while((read = stream.Read(buffer, 0, buffer.Length)) >0){
        // buffer...
    }
}

 使用这种方式,一旦我们开始下载数据,即是全速下载——即理论上是客户端和服务端的最小速度为当前速度。如果以这个速度持续下载,要不服务端被拖垮,要不就是客户端被服务端封禁。当然,一般情况下,后者居多。在持续的针对某站资源下载的时候,限速是非常必要的。

在HttpWebResponse里面比较简单,只需要控制读取的速度,即可达到限速的目的:

// 非生产环境代码,请在实际使用时进行必要调整
// —— Mitchell Chu
// Blog:blog.useasp.net

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

using(var response = (HttpWebResponse)request.GetResponse())
using(var stream = response.GetResponseStream())
{
    byte[] buffer = new byte[4096];
    int read = 0;
    long total_read = 0;
    DateTime begin = DateTime.Now;
    TimeSpan ts = new TimeSpan();
    while((read = stream.Read(buffer, 0, buffer.Length)) >0){
        total_read += read;
        ts = DateTime.Now - begin;
        if((total_read / ts.TotalSeconds) > 1024){
            Thread.Sleep(1);  // 休息一下.
        }
    }
}

 

BitConverter.IsLittleEndian在x86的机器上返回false

MitchellChu 2016-03-09 .NET技术 编程语言
0

在DEBUG写的程序时,发现一个有趣的现象,就是本应该返回trueBitConverter.IsLittleEndian,返回的却是false!但在使用BitConverter之后,再次查看其值,却发现变成了正常的true状态。难道在X86的系统上存在两种状态?

DEBUG的代码如下:

...
// 前面没有使用BitConverter
// DEBUG断点设置到这里的时候,可以看到是false的值
// BitConverter.IsLittleEndian
// 如果使用BitConverter之后,得到的值却是true
byte[] bdata = BitConverter.GetBytes(num);
// 此时DEBUG中看到的是true.

但这明显不科学,首先BitConverter.IsLittleEndian在X86下应该是false,其次,不应该在一个系统内,同时出现truefalse这两种状态。

抱着好奇的心态,翻开了源代码看了一下,看到如下代码:

public static readonly bool IsLittleEndian = true;

赫然写着true啊!哪里来的false?

经过一番搜索,发现有这么一句:

通过调试器读取成员并不会触发执行成员的初始化代码。1

也就是说:通过调试器读取内存中的成员仅仅是读取到该对象的默认初始值而已。有这么一句话,解释起来就豁然开朗了。

为了验证这个说法的真实性,我们可以自己定义一个类来进行验证下:

public static class DebuggerInitTester
{
    public static bool BoolData = true;
}

中我们可以看到,我们可以在没有调用前尝试获取到BoolData值,发现是true,并非是默认的false!这是什么情况,再仔细看了一遍代码,发现BitConverter里面有静态构造函数:

static BitConverter()
{
    // Note: this type is marked as 'beforefieldinit'.
    BitConverter.IsLittleEndian = true;
}

BitConverter中使用了静态的构造函数,而我们的并没有,看来问题在这里了,我们也改造下代码:

static DebuggerInitTester()
{
    DebuggerInitTester.BoolData = true;
}

再次测试,确实,这个时候默认是false了。看起来问题就在这个静态构造函数了,原来不是一直听说static的是在加载前初始化了的么?看来并不是这个样子的,还是Too Young Too Simple!

看来是beforefieldinit引起的问题了(可参见Mitchell的另一篇关于BeforeFieldInit的文章)

如果没有标记,则CLR会在第一次访问静态成员的时候调用静态构造函数对静态类中的字段进行初始化。比如,DebuggerInitTester这个类,我们定义了静态构造函数,因此将初始化BoolData的时机锁定了。

一切看起来是这样了,可再仔细看,又蒙了——压根就没有静态构造函数!BitConverter那个构造函数是自动生成的,是有BeforeFieldInit标记的。

.class public abstract auto ansi sealed beforefieldinit System.BitConverter
       extends System.Object
{
  .custom instance void __DynamicallyInvokableAttribute::.ctor() = ( 01 00 00 00 ) 
}

根据BeforeFieldInit规则,也就是说BitConverter的初始化会在访问前的任意时候完成,自然也可能会和有自定义静态构造函数那样在第一次访问的时候初始化,也许CLR对于是这样处理。

 如果能解释初始化时间点后,调试器读取成员不触发成员初始化代码倒是非常可以理解的,因为这样调试器的调试才安全可靠,不至于导致意外的发生(如:读取导致数据变化带来后继逻辑的)。

 

困惑

BitConverter由于使用了BeforeFieldInit标记,将初始化时机交给了CLR,也就是说这将导致IsLittleEndian初始化时间点不确定,很有可能就是在第一次访问的时候进行初始化的。

令人疑惑的是,为什么自定义的DebuggerInitTester类中的BoolData会那么快就完成了初始化呢?另外测试了好几次(其他类)均是非常早的完成初始化。

这里面到底是什么原因决定CLR来选择BeforeFieldInit标记过的类的初始化时机?

  

参考

  1. Why does BitConverter.LittleEndian return false on my x86 machine?
  2. IsLittleEndian field reports false, but it must be Little-Endian?

 

FLV音频视频文件的加密解密方式

MitchellChu 2016-03-02 其他技术
0

FLV文件结构一文中,我们已经了解了FLV文件的大致结构信息,同时在FLV文件校验一文中,我们可以完成对FLV文件的完整性进行校验。此文在这两篇文章的基础上尝试进行更深入一些的探讨——对FLV文件进行加密和解密操作。

在互联网时代,资源的分发已经变得非常容易,也不例外,对于资源的版权方来说,分发难度的降低一方面降低了自身资源的传播成本,另一方面却由于没有良好的防止非法分发而带来对自身的利益侵害。稍稍驻步,我们就能看到已经有很多为保护版权而创造的各种方法,CSS。AACS,Key 2 Audio,Always-Online等等。本文作为保护FLV来说,也算是一种尝试,在实践中,灵活的使用也可一定程度保护资源的非法传播。下面让我们来简单聊聊:

 

Mitchell Chu 注:以下方法名称并非“官方”说法,而是自创,请注意!

 

第一式: 反位

反位的方法算是最基础的方式,原理就是将二进制中的比特位全部取反(注意:不是反码,是特定的异或操作)。代码如下:

byte GetReverse(byte b)
{
    return (byte)(b ^ 0xFF);
}

byte Encrypt(byte data)
{
    return GetReverse(data);
}

byte Decrypt(byte data)
{
    return GetReverse(data);
}

加密:

对每个字节进行操作之后,我们就能得到一个被加密的新的FLV文件,此时,FLV文件的Header,Tag之类的已经全部被抹掉了,剩下的就是一堆数据而已。

解密:

解密此文件的方式非常简单,仅需要对FLV重新再来一遍异或操作即可得到原始文件。

优缺点:

 这种加密方法的优点是,简单的没有负担,无需任何高达上的算法,不需要复杂的操作。但缺点是,只要有原始文件对比后,基本瞬间能了解加密算法,破解难度很低。

说明:

这种方法并非一无是处,相反,操作的恰当,还是有可为之处,比如,我们仅对某些敏感数据进行操作(哪些是敏感的?可以在FLV文件结构一文中找找:P),那么,在表面看起来这是一个完整(也可以是不完整)的FLV,但得到数据,没有得到具体算法的人拿到FLV也不再那么容易发现数据的加工之处。播放之时,却会因为数据的错误导致播放失败。

说到这里,看到网上曾经有了对FLV的加密方式是对每个字节进行-128,在此表示怀疑:此方法能否保证加密后的数据是真的能够被安全还原么?

 

 第二式:对称加密

对称加密这个方法的原理是让合法用户在请求之后,系统同时发送解密密码,这样,用户在获得FLV资源的时候,同时拥有一个匹配的密码,这样,在播放之前,可以通过这个密码来得到正常的FLV源文件。

这个方法的实现种类很多,这里就不放出代码,只要是对称加密方式均可用在此处。

加密:

初始化密码,而后用对称加密算法+密码对FLV文件进行加密,得到加密后的FLV文件。

解密:

获得密码,而后用相匹配的对称加密算法+密码来完成对FLV的解密,得到正常的FLV文件。

优缺点:

如果不嫌事多,可以达到一人一码或者说一机一码(广告词啊,这是),但劣势也明显,一旦获得一次密码,也就丧失了对FLV的控制权,因为解密后,就是FLV原始文件嘛——所以,很多时候,要隐藏机密算法。

说明:

稍显复杂的一个算法,但在使用配套的客户端(Flash Player/PC 播放器等),还是能够比较好的完成对FLV的保护工作的,不同重点的防护是对自身加解密算法和FLV对应文件密码做保护,同时还得顾虑到解密的文件处理方式。

 

恩,本来还有第三式的,但还是到此为止吧,以免贻误众生。

使用上面的加密解密方法基本上已经能够完成对FLV的保护,当然,要保护FLV也不一定要使用加密方式,当年的Key 2 Audio不是通过创造多余的数据区来达到毁灭消费者的光驱么。

本人并不是积极的DRM()拥护者,个人觉得适当保护对版权所有者,正版消费者有一定的益处,但过度的保护反而会让版权者迷失,而失去精进的动力,陷入过度的依赖DRM,反而可能会伤及正版消费者利益,比如:Online-Always的DRM,当年刺客信条的正版用户体验竟然不如盗版 。

 

C#的类型构造器和beforefieldinit标志

MitchellChu 2016-03-09 .NET技术 编程语言
0

首先让我们来看两个类型的定义:

// 代码来自C# in Depth
// 为了后继的讨论方便,对类名进行了更改(Test改为Test1和Test2)
// ——  Mitchell Chu
class Test1
{
    static object o = new object();
}

class Test2
{
    static object o;

    static Test2()
    {
        o = new object();
    }
}

这两个类,经常会被误认为是一样的,但实际情况是如何呢?让我们编译后,反编译后来看看他们产生的代码:

// IL代码
// Test1和Test2在定义的时候出现了不同.
// —— Mitchell Chu
.class private auto ansi beforefieldinit ClassLibrary1.Test1
    extends [mscorlib]System.Object
{
    // Fields
    .field private static object o

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x206b
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method Test1::.ctor

    .method private hidebysig specialname rtspecialname static 
        void .cctor () cil managed 
    {
        // Method begins at RVA 0x205f
        // Code size 11 (0xb)
        .maxstack 8

        IL_0000: newobj instance void [mscorlib]System.Object::.ctor()
        IL_0005: stsfld object ClassLibrary1.Test1::o
        IL_000a: ret
    } // end of method Test1::.cctor

} // end of class ClassLibrary1.Test1



.class private auto ansi ClassLibrary1.Test2
    extends [mscorlib]System.Object
{
    // Fields
    .field private static object o

    // Methods
    .method private hidebysig specialname rtspecialname static 
        void .cctor () cil managed 
    {
        // Method begins at RVA 0x2073
        // Code size 11 (0xb)
        .maxstack 8

        IL_0000: newobj instance void [mscorlib]System.Object::.ctor()
        IL_0005: stsfld object ClassLibrary1.Test2::o
        IL_000a: ret
    } // end of method Test2::.cctor

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x207f
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method Test2::.ctor

} // end of class ClassLibrary1.Test2

比较后发现,大体一致,但Test1多了一个beforefieldinit标志。那beforefieldinit这个标志是做什么用的?

 

是什么

MSDN上的表述是:

  • BeforeFieldInit:指定调用此类型的静态方法并不强制系统初始化此类型。

然而,这并不能为我们释惑。

我们再参考下规范(MSDN上关于静态类也有类似描述)1

类的静态构造函数在给定的程序域中最多只执行一次。静态构造函数的执行是通过程序域中的下列事件中第一个出现的事件来触发的:

  • 类的一个实例被创建
  • 类中的任一静态成员被引用

 

和CLI规范(ECMA 335)第8.9.5节有如下表述1*

  1. 类型可以有一个类型初始器方法(type-initializer method),或没有;
  2. 类型可为其类型初始器方法指定一个松语义(为了后继方便,我们称此松语义为BeforeFieldInit)
  3. 若标记为BeforeFieldInit,则类型初始器方法将在或某些时候先于第一次访问此类中定义的任一静态字段的时候执行;
  4. 如果没有标记为BeforeFieldInit,则类型的初始器方法将在下列情况下执行(即被触发):
    • 第一次访问类中的任意静态或实例字段
    • 第一次调用任意类的静态,实例或虚方法

从CLI规范中我们明白,BeforeFieldInit是一种类中成员初始化的方式,用来标明初始化时机。同时,还有一种没有使用BeforeFieldInit的方式,这种方式我们称之为—— 方式3

简单来说:

  • BeforeFieldInit方式,我们不知道.acctor在什么时候运行,一切都将交由CLR做处理;
  • Precise方式,我们可以明确的了解到.acctor的执行时间点。

 

如何定义BeforeFieldInit和Precise

如果我们在类中提供了静态的构造函数,那么,此类将自动变为Precise方式,这也就是为什么类的静态构造函数和CLI规范中未标记BeforeFieldInit的说明一致的原因。

而如果我们在类的定义中没有提供静态构造函数,.NET将会自动为我们提供一个静态构造函数(见后面静态VS实例构造函数),并将此类标记为BeforeFieldInit。

 

使用BeforeFieldInit或Precise

BeforeFieldInit将自身的初始化任务交给了CLR来处理,可以自行决定在合适的时机来调用类的静态进行对静态成员的初始化,这样的一个好处就是CLR可以对其进行更好的优化时机,来提高程序的使用效能。当然,这也有一些小细节需要注意,当多个含有BeforeFieldInit的类同时存在时,正如我们无法了解和控制初始化时机一样,我们同样无法控制和了解到这些类的初始化顺序——一切都在CLR的掌控中,而不是我们。这在需要有序的情况下,就会出故障了,此时,我们需要考虑的是使用Precise方式。

Precise方式初始化时间点是明确的,因此我们可以利用这点,尽量减少对资源的浪费3—— 比如,我们常用的单例模式。

此处应有例子~~~

// BeforeFieldInit 与 Precise的性能比较
// Blog:blog.useasp.net
// Mitchell Chu
class Precise
{
    public static int i = 1;

    static Precise()
    {
    }
}

class BeforeFieldInit
{
    public static int i = 1;
}

class Test
{
    static void Main(string[] args)
    {
        // 测试100,000,000次
        int times = 100000000;
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < times; i++)
        {
            BeforeFieldInit.i = i;
        }
        sw.Stop();
        Console.WriteLine("BeforeFieldInit take time:{0}ms", sw.ElapsedMilliseconds);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < times; i++)
        {
            Precise.i = i;
        }
        sw.Stop();
        Console.WriteLine("Precise take time:{0}ms", sw.ElapsedMilliseconds);
    }
}

得出结果:

BeforeFieldInit take time:204ms
Precise take time:316ms

 

行文至此,应该是对BeforeFieldInit有所有了解了,那么让我们再看看另外一个例子,这个和哪个类一样?:

// 来自C# in Depth
// 这个例子和开篇两个类中的那个类是一样的呢?
class Test
{
    static object o = new object();

    static Test()
    {
    }
}

 

静态构造函数 VS 实例构造函数

中类的初始化是在类的构造函数中进行的,因此一般类都有构造函数(也叫构造器,Constructor)。

在.NET中,对于没有构造函数的类,一般会自动为其分配一个默认的构造函数,如:对于含有静态成员的类,在没有使用静态构造函数的情况下,.NET会自动为其分配一个静态构造函数(.acctor,需要有字段并默认给定初始化值的情况),此构造函数与实例构造函数(.ctor)异同在于:

  • 两者不存在的时候,.NET均会自动为其生成;
  • .ctor是可编程访问的(在没有定义时,实例化会自动调用默认的),.accotr则无法直接访问;
  • 静态的构造函数(.acctor)提供对静态字段进行初始化,而实例构造函数(.ctor)可以对静态和实例字段进行初始化。

 

 CLI 规范第六版关于BeforeFieldInit的描述:

The semantics of when and what triggers execution of such type initialization methods, is as
follows:
1. A type can have a type-initializer method, or not.
2. A type can be specified as having a relaxed semantic for its type-initializer method
(for convenience below, we call this relaxed semantic BeforeFieldInit).
3. If marked BeforeFieldInit then the type’s initializer method is executed at, or
sometime before, first access to any static field defined for that type.
4. If not marked BeforeFieldInit then that type’s initializer method is executed at (i.e.,
is triggered by): 
a. first access to any static field of that type, or
b. first invocation of any static method of that type, or
c. first invocation of any instance or virtual method of that type if it is a value
type or
d. first invocation of any constructor for that type.
5. Execution of any type's initializer method will not trigger automatic execution of
any initializer methods defined by its base type, nor of any interfaces that the type
implements. 


[Note: BeforeFieldInit behavior is intended for initialization code with no interesting sideeffects,
where exact timing does not matter. Also, under BeforeFieldInit semantics, type
initializers are allowed to be executed at or before first access to any static field of that type, at
the discretion of the CLI. 

  

 参考:

  1. C# and beforefieldinit
  2. 静态构造函数(C#编程指南)
  3. JIT compiler and type constructors(.acctors)

 

FLV文件完整性校验方法

MitchellChu 2016-02-28 其他技术
0

书接上回,我们讨论了FLV文件的结构,有了这些基础之后,我们就能够对FLV文件进行一些操作了,比如本文要讨论的——校验FLV文件的完整性。

完整的文件应该按照FLV文件的结构提供完整的数据信息,如:FLV HeaderFLV Body中的Previous Tag SizeTag。那我们下面由简入繁的步骤来对FLV文件校验一番。

最简单的校验方法,仅校验FLV Header信息,如(代码):

public static bool IsValidFlvHeader(byte[] bytes)
{
    if (bytes.Length != 9) return false;
    byte[] header0 = new byte[] { 0x46, 0x4C, 0x56 };
    byte flvVer = bytes[3]; 
    byte flvType = bytes[4];
    byte[] header_offset = new byte[] { 0x00, 0x00, 0x00, 0x09 };

    if (!header0.SequenceEqual(bytes.Take(3).ToArray())) return false;
    if (flvVer != 0x01) return false;
    if (flvType != 0x01
        && flvType != 0x04
        && flvType != 0x05)
        return false;
    return header_offset.SequenceEqual(bytes.Skip(5).Take(4).ToArray());
}

这种方法仅的Header信息是否是标准的FLV Header,在进行文件区分——特别是文件名缺失的情况下,这种方法能够快速的对FLV文件进行提取。

当然,如果我们不仅仅是要对文件进行简单的识别,要更进一步,那么我们就需要更多的校验,比如校验FLV文件的完整性,下面是FLV文件的整体结构:

------------------------
|       FLV Header     |
------------------------
|  Previous Tag Size#0 |
------------------------
|        Tag #1        |
------------------------
|  Previous Tag Size#1 |
------------------------
|        Tag #2        |
------------------------
|  Previous Tag Size#2 |
------------------------
|         ...          |
------------------------
|        Tag #n        |
------------------------
|  Previous Tag Size#n |
------------------------

从结构中可以明确,要校验,只需要校验每个FLV Header,Tag Size即可基本完成校验,关于具体的细节请参见FLV文件格式详解

有结构的特性,我们可以从文件末尾往前推演进行校验,代码就不放出来了,留给参阅的朋友自己写吧,这里给个思路:

  1. 读取末尾的4字节,转化成整数,得出这之前的Tag Size
  2. 根据获取的Tag Size,向前跳过Tag块,再读取更前面的一个Tag的Tag Size(这里重复1)
  3. 重复1,2两步,直到Tag Size为0,或者当前读取位置(Position)已经小于读取的Tag Size + 9。
  4. 如果,读取到Tag Size为0时,且前面的数据长度为9(如果Position移动后,可能是13)则认为是完整FLV
  5. 如果,读取的Tag Size + 9 已经超出了剩下的可用长度,则认为不是完整FLV。

 这样就能够简单的分析一个FLV是否完整,这在对文件完整性粗略校验中有比较好的表现。

当然,如果你知道FLV文件的结构的话,你也可以通过下面这些方法来校验文件的完整性:

  1. 顺序来读取FLV文件进行校验,因为Tag的大小可以在Tag Header中获取,不能100%确认
  2. 知道在onMetaData中能够得到FLV文件的大小的话,那么,通过这种方法来判断完整性也是可行的。

结合上面的多种校验方式,就能比较准确的了。

基本的校验方法就这些,在这里起个抛砖引玉的作用,如果你有更好的方法,欢迎讨论。

FLV文件格式详解

MitchellChu 2016-02-28 其他技术
0

在集体挺进HTML5的时代,来讨论相关的话题似乎有点过时,但现如今还是有很多的视频网站采用的是Flash播放器,播放的文件也依然还有很多是FLV格式,而且仅从一个文件格式的角度去了解和分析FLV应该也还说的过去的。

FLV(Flash Video)是Adobe的一个免费开放的音视频格式,babala~~ 省略若干字的介绍,要看,到官网看吧,这里不赘述,我们主要来讨论下FLV文件格式的细节,在此之后,我们会进一步讨论下的加密解密相关内容。

整体上,FLV分为HeaderBody两大块。

Header: 记录FLV的类型,版本,当前文件类型等信息,这些信息可以让我们对当前FLV文件有个概括的了解。

Body: FLV的Body是Flv的数据区域,这些是FLV的具体内容,因为FLV中的内容有多种,并可同时存在,因此,Body也不是一整块的数据,而是由更细分的块来组成,这个细分的块叫Tag。

这就是整个FLV的大概结构,下面我们进入到比特/字节数据的世界,看看FLV的内部世界。

 

HEADER

Flv 文件的Header总共由9个字节组成,他们构成如下:

           ----------------------------------------------
字节序     | 46 | 4c | 56 | 01 | 05 | 00 | 00 | 00 | 09 |
           ----------------------------------------------
字符序       F    L    V    1  /    \                9
                    ---------------------------------
bit序               | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
                    ---------------------------------
                                         1/0     1/0
                                        音频位  视频位

附注说明:字节序表示文件中的存放的字节流顺序,字符序是按照字节序展示出可见的字符数据,bit序此处为第5字节的bit位值.

FLV 文件Header的9字节意义如下:

  1. 第1-3字节:文件标志,FLV的文件标志为固定的“FLV",字节(0x46, 0x4C,0x56),见上面的字节序和字符序两行;
  2. 第4字节:当前文件版本,固定为1(0x01)
  3. 第5字节:此字节当前用到的只有第6,8两个bit位,分别标志当前文件是否存在音频,视频。参见上面bit序,即是第5字节的内容;
  4. 第6-9字节:此4字节共同组成一个无符号32位整数(使用大头序),表示文件从FLV Header开始到Flv Body的字节数,当前版本固定为9(0x00,0x00,0x00,0x09)

Header之后,即是FLV的Body了,Body定义了整个FLV的数据内容,他的结构又是如何的呢,让我们看下:

 

BODY

Body是由Tag Size(4字节)和Tag组成,其结构如下:

-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------
|          Tag          |
-------------------------
|  Previous Tag Size    |
-------------------------

 

Previous Tag Size

这个比较好理解,就是前一个Tag的大小,这里同样存的是无符号32位整型数值。因为第一个Previous Tag Size是紧接着FLV Header的,因此,其值也是固定为0(0x00,0x00,0x00,0x00)。

 

TAG

FLV中的TAG不止一种,当前版本共有3种类型组成:音频(audio),视频(video),脚本数据(script data),这三种类型会在Tag内进行标志区分。其中:Audio Tag是音频数据,Video Tag是视频数据,Script Data存放的是关于FLV视频和音频的一些参数信息(亦称为Metadata Tag),通常该Tag会在FLV File Header后面作为第一个Tag出现,并且一个文件仅有一个Script Data Tag。

为了在Tag内存放不同的数据,并且能够方便区分,每个Tag被定义除了Tag HeaderTag Data两部分,他们的结构如下:

-------------------------
|       Tag Header      |
-------------------------
|       Tag  Data       |
-------------------------



                -------------------------
                |       Tag Header      |
                -------------------------
                 /                    \
--------------------------------------------------------
| 08 | 00 | 00 | 18 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
--------------------------------------------------------

Tag Header由11字节组成:

  1. 第1字节:标志当前Tag的类型,音频(0x08),视频(0x09),Script Data(0x12),除此之外,其他值非法;
  2. 第2-4字节:表示一个无符号24位整型数值,表示当前Tag Data的大小;
  3. 第5-7字节:无符号24位整型数值(UI24),当前Tag的时间戳(单位为ms),第一个Tag的时间戳总为0;
  4. 第8字节:为时间戳的扩展字节,当前24位不够用时,该字节作为最高位,将时间戳扩展为32位无符号整数(UI32)
  5. 第9-11字节:UI24类型,表示Stream ID,总是0

 

Tag Data

Tag Data由Tag Header标志后,就被分成音频,视频,Script Data三类,分别如下:

 

1. Audio Tag Data

Audio Tag Data开始的第一个字节包含了音频数据的参数信息,第二个字节开始为音频流数据:

第1字节:

  • UB[4],前4位标识音频数据的格式,如:0x2表示的是MP3数据,当前合法的数值为0,1,2,3,4,5,6,7,8,9,10,11,14,15(7,8,14,15保留为内部使用)
  • UB[2],第5,6位bit表示采样率(AAC,总是3)
  • UB[1],采样精度, 0为8bits, 1为16bits
  • UB[1],音频类型,mono=0, stereo=1

 

2. Video Tag Data

和音频一样,其第一个字节包含的是视频参数信息,第二字节开始为视频流数据。

第1字节:

  • UB[4],前4位标识帧类型。1: keyframe (for AVC, a seekable frame); 2: inter frame (for AVC, a nonseekable frame); 3: disposable inter frame (H.263 only); 4: generated keyframe (reserved for server use only); 5: video info/command frame
  • UB[4],后4位标识视频编码。1: JPEG (currently unused) ;2: Sorenson H.263; 3: Screen video; 4: On2 VP6; 5: On2 VP6 with alpha channel; 6: Screen video version 2; 7: AVC

第二字节开始的数据有以下规律:

If CodecID == 2
  H263VIDEOPACKET
If CodecID == 3
  SCREENVIDEOPACKET
If CodecID == 4
  VP6FLVVIDEOPACKET
If CodecID == 5
  VP6FLVALPHAVIDEOPACKET
If CodecID == 6
  SCREENV2VIDEOPACKET
if CodecID == 7
  AVCVIDEOPACKET

 

3. Script Tag Data

这个Tag Data里面在未加密的时候,是ScriptTagBody类型,里面的SCRIPTDATA编码为AMF(Action Message Format)。ScriptTagBody由Name和Value两个字段组成(类型均为SCRIPTDATAVALUE)。

----------------------------------
|     Name (SCRIPTDATAVALUE)     |
----------------------------------
|     Value (SCRIPTDATAVALUE)    |
----------------------------------

 SCRIPTDATAVALUE类型比较多,它使用UI8(1个字节)来表示类型,可用的类型如下:

  • 0 = Number
  • 1 = Boolean
  • 2 = String
  • 3 = Object
  • 4 = MovieClip (reserved, not supported)
  • 5 = Null
  • 6 = Undefined
  • 7 = Reference
  • 8 = ECMA array
  • 9 = Object end marker
  • 10 = Strict array
  • 11 = Date
  • 12 = Long string

每种类型有对应的定义,这个比较多,不加详述,可以参看官方文档。

这里需要特别提出的一个是onMetaData,在SCRIPTDATA Tag中,它名称即为onMetaData,它提供一系列可用于AS编程的变量(通过NetStream.onMetaData属性获取)。由于每种软件创建的FLV文件,均可有不同的变量,无法一一列出,但一般包含:FLV的音频,视频还有文件相关的基本信息,如:文件大小,时长,视频宽高等。

onMetaData的一般结构如下:

              -------------------------------------
              |       Name (SCRIPTDATAVALUE)      |
              -------------------------------------
               /                                 \
------------------------------------------------------------------
| 02 | 00 | 0a | 6f | 6e | 4d | 65 | 74 | 61 | 44 | 61 | 74 | 61 |
------------------------------------------------------------------

后面的Value由于每个软件产生的不同,但类型是0x08,ECMA ARRAY。大概结构如下:

----------------------------------
|     ECMAArray Length(UI32)     |
----------------------------------
|     Variables Array[]          |
----------------------------------
|     SCRIPTDATAOBJECTEND        |
----------------------------------

其中Variables Array为SCRIPTDATAOBJECTPROPERTY类型数组,最后用3字节的SCRIPTDATAOBJECTEND结束VALUE值。这个值固定为:0x00,0x00,0x09

 

到此,FLV的基本就讲解完成了,基于这个结构作为基石,我们可以对FLV进行校验,解析数据,当然,还有加密/解密。

 

注意:: FLV文件中,使用的是大头序字节

 

感谢

由于本人对于零基础,对于FLV更是门外汉,特别感谢Merry引荐的Colin让我在Flv文件的研究少走很多弯路(包含Specification文档),并在短时间内出色的完成了工作中对FLV文件的析构任务。

 

参考

1. Adobe Video File Format specification V10

2. Video File Format Spcification V10.1

 

Windows 7下启用对SATA硬盘支持的AHCI模式

MitchellChu 2016-02-18 其他技术
2
在主板支持AHCI(Serial ATA Advanced Host Controller Interface)模式的情况下,一般为了兼容也会将这个模式切到IDE的模式,在IDE模式下使用SATA硬盘,写问题不大,但读的速度大受影响,建议在Win7下开始对SATA硬盘的支持以获得更好的读性能。 如果Win7没有开启SATA模式的时候,切换到AHCI模式会出现蓝屏,因此,操作顺序应为: 1.Win7上先行切换SATA模式(后附方法) 2.重启到主板切换为SATA的AHCI模式 3.重启后进入Win7,自动安装AHCI相关的驱动 4.重启,正常使用.

用时间换空间的缓存算法

MitchellChu 2016-02-18 其他技术 编程语言
0

在使用Scrapy爬网站的时候,产生出来的附加产物,因为在爬取的时候,CPU的运行时间紧迫度不高(访问频次太高容易被封禁),借此机会难得来上一下,让自己的内存解放一下。

算法原理:

通过将要缓存的数据用二进制展开,得到的二进制数据映射到缓存字段上,要检验是否已经缓存过,仅需要去查找对应的映射位置即可,如果全部匹配上,则已经缓存。

# 二进制就是个二叉树
# 如下面可以表示出来的数据有0, 1, 2, 3四个(两个树独立)

  0      1
 / \    / \
0   1  0   1

因此对缓存的操作就转化为对二叉树的操作,添加和查找只要在二叉树上找到对应路径的node即可。

 

关键代码:

    def _read_bit(self, data, position):
        return (data >> position) & 0x1

    def _write_bit(self, data, position, value):
        return data | value << position

 

实际使用效果如何呢?

在和默认的set相比较,得出测试结果如下(存取整型,不定长字符串,定长字符串):

Please select test mode:4
Please enter test times:1000
====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.0                                     0.0209999084473
read(s)             0.0                                     0.0149998664856
hits                1000                                    1000
missed              0                                       0
size                32992                                   56
add(s/item)         0.0                                     2.09999084473e-05
read(s/item)        0.0                                     2.09999084473e-05
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set): N/A
read time (bytecache / set): N/A
====================================================================================================
...test fixed length & int data end...

====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.00100016593933                        6.1740000248
read(s)             0.0                                     7.21300005913
hits                999                                     999
missed              0                                       0
size                32992                                   56
add(s/item)         1.00016593933e-06                       0.0061740000248
read(s/item)        0.0                                     0.0061740000248
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set): 6172.97568534
read time (bytecache / set):  N/A
====================================================================================================
...test mutative length & string data end...

====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               1000                                    1000
add(s)              0.0                                     0.513999938965
read(s)             0.0                                     0.421000003815
hits                999                                     999
missed              0                                       0
size                32992                                   56
add(s/item)         0.0                                     0.000513999938965
read(s/item)        0.0                                     0.000513999938965
====================================================================================================
size (set / bytecache): 589.142857143
add time (bytecache / set):  N/A
read time (bytecache / set):  N/A
====================================================================================================
...test Fixed length(64) & string data end...

测试下来,内存消耗控制的比较好,一直在56字节,而是用set的内存虽然也不是很大,当相较于ByteCache来说,则大上很多。

ByteCache的方式来缓存,最大的问题是当碰到非常大的随机数据时,消耗时间会比较惊人。如下面这种随机长度的字符串缓存测试结果:

Please select test mode:2
Please enter test times:2000
====================================================================================================
TEST RESULT::
====================================================================================================
                    set()                                   bytecache
items               2000                                    2000
add(s)              0.00400018692017                        31.3759999275
read(s)             0.0                                     44.251999855
hits                1999                                    1999
missed              0                                       0
size                131296                                  56
add(s/item)         2.00009346008e-06                       0.0156879999638
read(s/item)        0.0                                     0.0156879999638
====================================================================================================
size (set / bytecache): 2344.57142857
add time (bytecache / set): 7843.63344856
read time (bytecache / set):  N/A
====================================================================================================
...test mutative length & string data end...

 在2000个数据中,添加消耗31s,查找消耗44s,而set接近于0,单条数据也需要16ms(均值)才能完成读/写操作。

 

不过,正如开头说的,在紧迫度不是很高的Scrapy中,这个时间并不会太过于窘迫,更何况在Scrapy中,一般是用来缓存哈希后的数据,这些数据的一个重要特性是定长,定长在本缓存算法中还是表现不错的,在64位长度的时候,均值才0.5ms。而与此同时倒是能在大量缓存的时候,释放出比较客观的内存。

如果有更好的能让速度在上新台阶,也是无比期待的。。。

 

总结:

1. 此方法的目标是用时间换取空间,切勿在时间紧迫度高的地方使用

2. 非常适用于大量定长,且数据本身比较小的情况下使用

3. 接2,非常不建议在大量不定长的数据,而且数据本身比较大的情况下使用

Python在Console下显示文本进度条的方法

MitchellChu 2016-02-13 其他技术
0

在用处理耗时的任务时,往往希望能够了解到任务当前的处理进度,这个时候需要在任务中不断打印出任务的进度信息。一般我们是这样的:

def process_mission():
    """ 任务处理方法 """
    # 这里是任务处理过程
    print('当前处理到第[%d]项' % count)
    # 这里是任务处理过程 

 这种方式会在输出一堆类似下面这样的信息:

当前处理到第1项
当前处理到第2项
当前处理到第3项
当前处理到第4项
当前处理到第5项
....

这种信息有可能非常非常的长,也有可能输出的时候非常非常的快,以至于根本就无法看清楚(输出非常快的时候)。

这时候,我们期待这样的功能:

[==================             ] 25.60%

但如果纯粹的使用print是无法达到效果的,会变成和前面一样,满屏都是这种杠杠。

如何正确显示呢?

Python提供了一个模块,叫:progressbar,当使用这个模块之后,你要显示进度的仅需如下:

import progressbar

# 先定义一个进度条
# http://blog.useasp.net/

pbar = progressbar.ProgressBar(maxval=100, \
    widgets=[progressbar.Bar('=', '[', ']'), ' ', \
    progressbar.Percentage()])
for i in xrange(100):
    # 更新进度条
    pbar.update(i+1)

pbar.finish()
# Ok,到此完结。

 是不是很容易就完成进度条的显示?不过遗憾的是,这个python模块并不是默认的,而是需要安装:

pip install progressbar

如果没有安装pip可以参考这里 。

对于只要一个简单进度条的人来说,安装个python包似乎有点动静太大,那么,自己动手写一个便是了。

下面是Mitchell自己写的一个类似的进度条,不用安装包,简单易用。

# 在使用本方法之前,请先做如下import
# from __future__ import division
# import math
# import sys
# ##blog.useasp.net##

def progressbar(cur, total):
    percent = '{:.2%}'.format(cur / total)
    sys.stdout.write('\r')
    sys.stdout.write("[%-50s] %s" % (
                            '=' * int(math.floor(cur * 50 / total)),
                            percent))
    sys.stdout.flush()

说明:

cur, total:一个是当前值,一个是总值。cur在任务的处理过程中会不断向total靠近,直到两者相等,任务结束

需要注意的是,这两个可能为整型,因此要引入division

在使用的时候,只需要调用即可在控制台输出进度条。

# 调用方式举例
progressbar(2, 100)
progressbar(3.9, 10)
progressbar(3283, 27379)

 到此,我们完成了一个会在控制台动态更新的进度条。

本博莫名被百度联盟封禁

MitchellChu 2016-01-12 万象漫谈
0

挂了几年的博客,纯粹属于个人爱好,从来没有想过盈利,在几个月前,好不容易来挂个广告,创造几块钱收入(真心是几块钱,看着能有几块,回头一看,不知道怎么回事就变几毛,有的时候还几分,更有时,唉!),补贴补贴服务器费用。经过一阵折腾,终于把百度联盟的广告挂到了自己的博客上,心想这回咱也高大上了,网站能有了。

于是乎,博客也就一直这么放着了,至于百度的那几块钱,要满百才能提现好像,看了几次,都那么几毛1块的,猴年马月才能提现?看看也就罢了,自己写的东西还有人捧场,并能产生点收入,给自己带来的成就感倒是远大于收入本身。

然,不知道百度哪里抽了,还是我抽了?莫名其妙的就收到了说我作弊的邮件:

 尊敬的联盟会员,您好:

经核查,我们发现您联盟帐号中的合作媒体,存在严重合作异常情况(包含但不限于推广物料违规使用、人工/机器无效点击等行为)。
该异常情况违反了百度联盟会员注册协议中的5.1和5.2条款。
为了维护公正、诚信的业务合作秩序,保护推广服务对象的合法利益,为大家创造一个诚信的环境,现已关闭您的分帐权限,并扣发已产生的分成,即百度联盟已终止与您的合作。     

balabala....

其他没明白,就明白了,说我作弊了,啥,作弊?

非常好奇度娘的思维,真要,能就几块钱的收入么???

 不知道有哪位也碰到过类似情况?

请指教如何处理应对?

 

 

VS清除打开项目时的TFS版本控制提示

MitchellChu 2015-12-15 .NET技术 其他技术
3

对于曾经做过TFS版本控制的项目,在版本控制服务不可用的时候,依然会在每次打开项目的时候都提示:当前项目是版本控制的项目,但是当前版本控制不可用,balabala的信息,如果是需要进行版本控制的项目在临时无法连接到版本控制服务器的时候出现这个提示,也属于正常的,但是如果是不再需要进行版本控制的项目,还这么次次打开都这么提示,就有些受不了——关键是,没有,只能想办法删除了。

TFS版本控制提示

TFS提示信息

在项目中要永久清除TFS版本控制,需要操作三步(请确保操作之前没有在使用当前项目):

  1. 清除(删除)项目下的所有版本控制文件,这些文件有:*.vssscc,*.vspscc

    删除这些版本控制文件比较简单,搜索这些后缀的文件,删除即可;

  2. 修改项目的解决方案文件:*.sln

    先要确认解决方案文件(*.sln)是可修改的,如果是Read-Only的文件,则需要先调整为可修改。
    切勿使用打开文件,使用文本编辑器打开*.sln文件,在文件中,我们将能看到类似下面的代码:

     GlobalSection(TeamFoundationVersionControl) = preSolution
            SccNumberOfProjects = 4
            SccEnterpriseProvider = {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
            SccTeamFoundationServer = here is your project's tfs
            SccLocalPath0 = .
            SccProjectUniqueName1 = project-unique-name1.csproj
            SccProjectName1 = your-project-name
            SccLocalPath1 = project-local-path
            SccProjectUniqueName2 = project-unique-name2.csproj
            SccProjectName2 = project-name-2
            SccLocalPath2 = local-path2
            SccProjectUniqueName3 = project-unique-name3.csproj
            SccProjectName3 = project-name-3
            SccLocalPath3 = local-path3.
        EndGlobalSection

     这段代码是项目启动时加载的TFS配置,我们要剔除TFS,此段代码就必须删掉了,删除之后,保存文件即可。

  3. 修改各个子项目中的信息,这些信息在:*.csproj(C#项目,其他项目文件后缀不同)

    完成上面两步,项目的TFS控制信息已经清理的差不多了,如果是VS2010项目,则用2010打开的时候会有提示,选择永久(完全)删除源代码控制的关联绑定,确认即可完成整个项目的TFS清理工作1

    Mitchell这里使用的是VS2012,打开的是VS2013的项目,在打开项目的时候,虽然完成了上面两步,但在加载项目时,在VS的输出窗口中还是会显示:

    未能找到解决方案的映射。
    未能找到解决方案的映射。
    未能找到解决方案的映射。
    活动解决方案已暂时与源代码管理断开连接,因为服务器不可用。若要尝试重新连接源代码管理,请在服务器可用时关闭并重新打开解决方案。如果要连接到其他服务器,请使用“更改源代码管理”对话框。

     这种问题是因为*.csproj中还有版本控制信息,导致VS还会尝试进行TFS相关操作,要解决这个问题,需要删除*.csproj文件中的相关信息,用文本编辑器打开文件,你能看到类似下面的信息:

      <PropertyGroup>
        <!--其他配置-->
        <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
        <FileAlignment>512</FileAlignment>
        <SccProjectName>???</SccProjectName>
        <SccLocalPath>???</SccLocalPath>
        <SccAuxPath>???</SccAuxPath>
        <SccProvider>???</SccProvider>
      </PropertyGroup>

    上面的Scc*的配置即是版本控制信息。可以简单粗暴的将它删除即可,删除完成后,保存文件。

完成上面的操作之后,我们再次用VS打开项目,就不会再有任何TFS的相关信息提示了,又可以和VS快乐的玩耍了。

当然 如果你觉得这还是麻烦,那么你可以下载Mitchell写的TFSEliminator 工具(需要.NET 4.0支持)

使用也是相当方便,有两种方式:

  1. 1. 命令行:
  2. # 在命令行下,使用:
    # Application ProjectRoot
    # 的格式即可完成对指定目录下的TFS信息的剔除
    # 如:项目在D:\Temp\Test下面
    TFSEliminator.exe D:\Temp\Test
  3. 要直接运行,只需要将程序复制到项目的根目录下,然后双击运行即可。

注意: 命令行会有提示,如果确认路径没有问题,记得按回车或者Y哦~

 

参考:

1. VS2010的相关信息参见的是这里

Sublime Text一个文件内创建多个代码片段(snippets)

MitchellChu 2015-11-30 其他技术
2

在使用IDE做开发的很多时候,为了减少代码的输入,会创建代码的片段,在需要的时候直接呼出即可。这种方法往往能够提高我们的效率,同时也大大降低我们代码的出错几率!在Sublime Text中,同样提供了创建代码片段的功能(Snippets),但ST默认提供的创建代码片段的方式是一个代码片段一个文件,这种方式对于代码片段的管理有些不便——虽然和Emacs一样提供文件夹的方式来集中管理,不过还是有些不够方便,本文就从创建ST默认的代码片段开始,到一个文件多个snippets的过程做个记录。

ST()创建Snippets

ST的Tools->New Snippet...为创建代码片段,点击后,出现代码片段的模板,如下(ST3):

<snippet>
	<content><![CDATA[
Hello, ${1:this} is a ${2:snippet}.
]]></content>
	<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
	<!-- <tabTrigger>hello</tabTrigger> -->
	<!-- Optional: Set a scope to limit where the snippet will trigger -->
	<!-- <scope>source.python</scope> -->
</snippet>

这个即是单个snippet的全貌了。

TIPS

  • content :这个即是,注意:
    <![CDATA[ ]]>
     这个是不能删掉的,表示数据内容。如果要开启Tab触发的话(一般都会开启吧),那么:
  • tabTrigger:这个就需要取消注释了,这个里面填写的是触发字符串,当在ST编辑时,和此内容匹配时,即可用Tab直接呼出content内容来替换当前位置tabTrigger的内容
  • scope: 应用的范围,Mitchell Chu创建比较多的snippet是针对python的,所以,这里我一般是source.python,当然,你也可以是其他内容,比如,针对js的可以是source.js

但根据需要对上面三个内容作出了适当修改后,我们就得到了我们需要的一个代码片段了(snippet),比如像下面这样:

<snippet>
	<content><![CDATA[
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright @ ${1:2015} ${2:Mitchell Chu}
# Blog: http://blog.useasp.net/

]]></content>
	<!-- Optional: Set a tabTrigger to define how to trigger the snippet -->
	<tabTrigger>!python</tabTrigger>
	<!-- Optional: Set a scope to limit where the snippet will trigger -->
	<scope>source.python</scope>
</snippet>

确认无误,我们保存到文件即可完成一个代码片段的添加。

注意:

  • 代码片段的文件一定要保存为:.sublime-snippet 的后缀,否则无法识别,路径放在:.\Data\Packages\User
  • 保存后,需要重启ST,才能加载snippet —— 这个不知是否是个人原因,如果你没办法使用,可以尝试重启ST后再试。

 

管理snippet

像上面这样创建snippet,少量还能应付,但数量变多时,问题就来了,要修改某个代码片段将是一个小复杂的工作,更糟糕的是,如果你使用ST做多语言的开发时,每个语言都有自己的,这个时候你将会发现,如果不加管理,要编辑的话,将可能变成一个噩梦!

要管理snippets也不是不可能的,ST中虽然默认没有给我们提供默认方案,但ST提供了管理支持 —— 文件夹!ST会自动的搜索子文件夹的内容,这就为我们管理snippets提供了方便。

我们可以对于单个语言创建一个文件夹,比如:python相关的snippets我们放到Python的文件夹下,这样管理起来也就自然方便多了。

 

多个代码片段在同一个文件

也许,你对上面的管理方式不够满意,同时你也有信心,会好好的小心谨慎的修改每个snippet,这种要求显然是不能通过上面的方案实现的,这个时候我们需要使用的是:——使用补全功能来达到类似多个snippets在同一个文件的效果。

初始的completions的文件也许是这样的:

{
   "scope": "source.python",

   "completions":
   [
      { "trigger": "doc", "contents": "'''\n${1:TODO DOC HERE}\n'''" }
   ]
}

这是一个针对python文件的completions,当在python文件中输入doc之后,我们就可以出现选项提示,如果确认(Ctrl+SpacebarTab,测试发现Enter亦可),则可以完成contents插入到当前位置(替换掉已经的输入)。

TIPS:

  • scope:这个和snippets中的一样
  • completions:这面定义你需要的各种代码片段
  • trigger: 监控的字符串输入,这个提示在ST3中,像doc,你在输入d的时候,他会自动查找d开头的所有trigger。一般放的是关键字。
  • contents:内容模板,要替换trigger中的内容,内容可以使用$1,$2,$3...的方式来作为placeholder[参见Palceholder节]

 

这还只是一个啊,如何实现多个在一个文件内呢?—— 注意,completions,这个里面只要你愿意,你是可以添加无数个trigger的,比如,小菜Mitchell在使用的时候,就添加了:

"completions":
[
  { "trigger": "!head", "contents": "#!/usr/bin/env python\n# -*- coding:utf-8 -*-\n#\n# Copyright (c) ${1:2015} ${2:Mitchell Chu}\n" },
  { "trigger": "!doc", "contents": "'''\n${1:TODO doc here.}\n'''" },
  { "trigger": "!pdbdebug", "contents": "import pdb\npdb.set_trace()\n" }
]

这样在编辑的时候插入注释,直接doc一下了,要插入文件头,直接head —— 请无视trigger里面的感叹号(注意:trigger里面的\t有特别用处,不能无视哦)。

这样是不是就将所有的代码片段放到一个文件内了呢,是不是感觉瞬间整洁很多了? 恩,至少Mitchell Chu是这么觉得的。

注意:

  • 文件保存的时候,必须要保存为:.sublime-completions的后缀,同时文件要和snippets一样,放到规定的目录下。
  • 如果有特殊字符,记得使用转义,使用两个反斜杆.

 

关于PlaceHolder

在snippet或者completions中,content(s)里面是可以使用占位符的,占位符以$开头,可以一般有两种方式:

  1. $+数字:这个占位符的数字从1开始,可以一直递增,也可以重复,如:
    this message is come from blog.useasp.net, My Name is $1:
    Current Time: $2
    Wellcome, $3
    —— $1
    
    
     在插入到ST中后,光标会停留在$1的位置(注意,这里有两个地方),当你输入内容的时候,这两个占位符的地方都将同时输入。使用Tab来移动到下一个位置。也许我们想为每个位置提供默认值,这个时候可以使用第二种方式:
  2. ${数字:默认内容}:这个占位符中数字和上种方式一致,如:
    this message is come from blog.useasp.net, My Name is ${1:Mitchell Chu}:
    Current Time: ${2:8 PM}
    Wellcome, ${3:Guest}
    —— ${1:Mitchell Chu}
     这个时候,插入的内容是有默认值的,如果不用改变,直接Tab到下一个位置即可。

 

你有什么好玩的技巧呢? 也请分享一下,共同提高下~:P 

 

 参考:

1. Sublime Text Snippets

2. Sublime Text Completions

Python产生token唯一值的算法性能比较

MitchellChu 2015-11-08 其他技术
0

在很多场合的时候,我们都需要产生不重复的字符串来标志操作的唯一性,比如:HTTP请求中,我们需要产生SessionID,在数据存储的时候,我们也可能需要生成唯一的字符串来作为数据的ID以便我们进行索引。本文的由来是在使用tornado的时候,需要使用Session,Session需要有唯一的ID值。为了尽可能快速的生成安全可用的Session ID,而对当前的一些比较通用的生成方法进行了比较。为了方便说明,后继的所有说法均以token作为, 唯一字串等的统一表述。

在网络中比较流行的是使用uuid4().hex的方式来生成token,但另外一种声音是,uuid4().hex的安全性不高,需要使用安全性更高的算法来代替,后继出现了使用os.urandom(24),或者自行随机生成字符串的形式(使用的是os.urandom(16)),再到后来,使用OpenSSL和M2Crypto的方式来生成随机数。OpenSSL和M2Crypto需要Python安装pyOpenSSLM2Crypto。M2Crypto由于接触少,因此没有对M2Crypto进行测试。

 

测试环境:

CPU: Intel Xeon E3 3.30Hz 3.70Hz

Memory: 16GB

System: Windows 7 64-bits

# times:测试次数
# func: 要测试的函数名称
# 此方法是入口方法
# 各个算法以函数的形式定义,接受times参数即可——By MitchellChu
def crash_testx(times, func):
	import time
	print('\r\n--------------------------------------------')
	print("test function: %s" % func.func_name)
	print("begin time: %s" % time.strftime('%Y%m%d %X'))
	begin_time = time.time()
	(crashed_times, hash_data_len) = func(times)
	print("end time: %s" % time.strftime('%Y%m%d %X'))
	print("take time:%s" % (time.time() - begin_time))
	print("test times: %d, crashed times:%d, hash data length:%d" % (times, crashed_times, hash_data_len))
	print('--------------------------------------------\r\n')

 

产生方式(generate method)

长度(hash length)

组合范围

耗时(second/10million)

包含包(import packages)

base64.b64encode(os.urandom(24),['_','-']) 32 64^32 65.9289999008 base64, os
base64.b32encode(os.urandom(20)) 32 32^32 86.3580000401 base64, os
sha1(os.urandom(24)).hexdigest() 40 16^40 29.6259999275 os, hashlib
''.join(random.choice(alphabet_digits) for _ in range(32)) 32 62^32 214.484999895 random,string
binascii.b2a_base64(os.urandom(24))[:-1] 32 64^32 22.5640001297 os, binascii
(binascii.b2a_base64(os.urandom(24))[:-1]).translate(translationstr)* 32 64^32 24.5349998474 os, binascii
(binascii.b2a_base64(os.urandom(24))[:-1]).translate(translationstr)* 32 64^32 398.623999834 os, binascii
(binascii.b2a_base64(os.urandom(24))[:-1]).replace('/','_').replace('+','-')* 32 64^32 27.271999836 os, binascii
uuid4().hex 32 16^32 159.762000084 uuid
binascii.b2a_base64(OpenSSL.rand.bytes(24))[:-1] 32 64^32 61.1059999466 binascii,OpenSSL

* 星号表示的几个使用的都是binascii.b2a_base64来生成64位数据,不同的是:第一个translate中的translationstr是全局生成的,而第二个translate中的translationstr是在每次生成时生成,测试的时间来看,每次生成需要耗费大量时间。最后为了比较,使用了两个replace来进行对照。

从测试的结果来看,性能最佳的为binascii.b2a_base64

binascii.b2a_base64(os.urandom(24))[:-1]

 其次性能非常棒的是

sha1(os.urandom(24)).hexdigest()

从生成的覆盖范围来看,SHA1生成的会少于。但base64中有两个特殊字符(这需要注意传入的字节数),因此在有些时候并不适合。

uuid4().hex在测试中看来确实性能不好,仅略优于自定义token生成的方法(random.choice)。

OpenSSL生成的token安全性具体比os.urandom优多少,并无测试,也并不太清楚具体的实现细节,哪位知道,可以说明下。:P

 

结论:

  1. 可以用base64的地方,选择binascii.b2a_base64是不错的选择 —— 根据W3的Session ID的字串中对identifier的定义,Session ID中使用的是base64,但在Cookie的值内使用需要注意“=”这个特殊字符的存在;
  2. 如果要安全字符(字母数字),SHA1也是一个不错的选择,性能也不错;

 

参考:

 

 

CentOS安装配置vsftp虚拟用户登录(Berkeley DB+PAM)

MitchellChu 2015-09-05 Unix & Linux
0

在使用Linux时,难免要进行各种文件的远程传输,比如:网站的代码,共享资源等,而这其中用的最多的传输方法大概就是FTP了。Linux下可用FTP服务端是非常多的,vsftp, proftp, uw-ftp等,其中vsftp流行度比较广,冲着这个,本人在自己的上也就选择了vsftp,而且,vsftp支持PAM(pluggable authentication modules)下虚拟用户设置,这正是Mitchell Chu比较喜欢的一种方式,接下来就请vsftp君上场。

使用的用户中,支持三种用户模式,分别是:实体用户,匿名用户和虚拟用户(guest,亦称访客身份)。实体用户(Real User)是指用户本身存在于系统中的用户,他们存在于/etc/passwd和/etc/shadow文件中。匿名用户(Anonymous)是指客户端无需提供任何用户身份,ftp为访问者提供一个名为anonymous的特殊用户以供其使用。虚拟用户(Virtual User)个人理解是一种介于实体用户和匿名用户之间的用户。原因是:虚拟用户虽然在系统的实体用户文件中不存在,但是会在系统的其他地方进行记录(本文是Berkeley DB),以供vsftp用来对ftp访问者进行必要的鉴权,而鉴权完毕之后,该用户操作文件之时,将是使用vsftp的运行用户ftp进行的。由于实体用户需要的是系统真实帐户,开放这种权限无疑会增加系统的风险,而虚拟用户使用的是和实体用户不同的用户验证体系,并且,在系统中并不实际存在虚拟用户到实体用户的映射关系,因此降低了FTP给系统带来的风险性,在实际生产环境中,虚拟用户可以更加灵活的进行独立管理,比如:虚拟主机需要给用户提供FTP帐户。有这么些好处之后,让我们看看是如何配置吧。

 

预备式

Berkeley DB 数据库:用来存储虚拟用户的登录信息

pam_userdb.so:用来验证虚拟用户

db4_utils:用来转换虚拟用户到DB数据的工具

 

安装需要的包

CentOS中好像是默认自带vsftp的,因此,无需安装,如果你不确认,可以用which看下:

which vsftpd

# 如果安装好了的话,应该是有类似下面的输出:
# /usr/sbin/vsftpd
# 如果没安装,则类似下面的输出
# /usr/bin/which: no xd in (/home/limituser...

 没有安装的话,请安装:

yum install vsftpd

# 或者: yum -y install vsftpd

当然,清空下yum的缓存亦可:

pushd /etc/yum.repos.d/
rm -rf *
wget http://docs.linuxtone.org/soft/lemp/CentOS-Base.repo
yum clean all

yum -y install vsftpd ## 这句还是重点.

为了简单,我们可以一次性安装所有需要的包:

yum install db4-utils db4 vsftpd

## Mitchell Chu提醒:有的就不用再安装了,请自行增减

 

创建虚拟用户

我们使用Berkeley DB数据库来存储。第一步是创建纯文本来添加用户和密码,用户名和密码各占一行。比如我们要创建:useasp的用户名,密码是blog.useasp.net,并且创建一个admin密码是adminpasswd的用户,那么,纯文本将类似如下:

pushd /etc/vsftpd
cat >vusers.txt
useasp
blog.useasp.net
admin
adminpasswd

创建好vusers.txt后,我们需要的第二步是将纯文本转换为db文件,这时候需要使用到db_load了:

db_load -T -t hash -f vusers.txt vsftpd-virtual-users.db

为了安全起见,记得设置仅root可读写(当前是root):

chmod 600 vsftpd-virtual-users.db

再清理掉原来的纯文本文件:

rm vusers.txt

此时,我们已经将需要登录的用户已经准备好了,接下来就需要去配置vsftpd,让vsftpd能够正确的识别并支持已经设置好虚拟用户了。

 

VSFTPD虚拟用户的配置

找到vsftpd.conf配置文件,添加或修改下面这些配置选项:

# 禁止匿名登录
anonymous_enable=NO
anon_upload_enable=YES 
anon_other_write_enable=YES
# 启用本地用户
local_enable=YES
# 虚拟用户使用本地用户权限
virtual_use_local_privs=YES
# 可写
write_enable=YES
# PAM配置
pam_service_name=vsftpd
# 启用虚拟用户 
guest_enable=YES
# 用户后缀: 配合下面local_root使用,将会用登录的用户名替换掉$USER
user_sub_token=$USER
# 根目录
local_root=/var/ftp/$USER
# 启用chroot,登录后会被定位到指定根目录
chroot_local_user=YES
# 将所有的用户和组显示为ftp
hide_ids=YES

vsftpd的配置文件是在/etc/vsftpd/vsftpd.conf,原有配置项可以保留默认值,如果需要日志,vsftpd有两个日志可供使用,一个是标准xferlog格式的,一个是vsftpd格式的,可读性后者更好,当然,你也可以两个日志都启用,要启用可以设置:xferlog_enable, xferlog_std_format, xferlog_file, vsftpd_log_file等参数以获得需要的日志效果。

 

虚拟用户的PAM配置

要针对虚拟用户启用,我们还需要对PAM尽心配置,在上面的vsftpd配置文件中,我们使用pam_service_name来配置了PAM将使用的配置文件,这个文件安装后是默认存在的,如果你觉得有必要保留原来的配置,可以先行备份一份,而后配置文件内容改为如下:

#%PAM-1.0
auth       required     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-users
account    required     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-users
#session    required     pam_loginuid.so

要启用session可以将上面的#注释掉,如果是在32位系统下,网上有一个配置方法是下面这种:

#%PAM-1.0
auth       sufficient     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-users
account    sufficient     pam_userdb.so db=/etc/vsftpd/vsftpd-virtual-users

 由于本人对Linux下的PAM并不是很了解,并不太了解requiredsufficient的区别,这里不好做过多的论断,有了解的可以指点一下,谢谢!

PAM配置里面,是指明账户保存的所在位置,我们配置文件里面的路径即是前面使用db_load创建的Berkeley DB的文件路径。

 

创建FTP目录

前面vsftpd已经配置了ftp的目录,我们需要先行创建FTP根目录,和用户需要的目录,因为一旦这些用户登录,将会被vsftpd重新定位到指定的home目录下的,vsftpd中我们配置的是/var/ftp,因此我们需要确认此目录是否存在,不存在我们就需要创建,而后,在此目录下创建虚拟用户的根目录,目录名称就是用户名称。

mkdir -p /var/ftp/{useasp,admin}
chown -R ftp:ftp /var/ftp

 为了保证能够顺利读取到文件,我们将根目录下的所有文件都变成ftp这个用户所有——ftp账户系统已经默认设置好了,vsftpd就是使用这个账户来操作的。

 

重启FTP服务,测试FTP

按上面的流程配置完后,我们就可以重新启动vsftpd服务,让新的配置生效——如果你没有办法使用此命令,请参看后面的省却麻烦一节,将vsftpd设置成为开机启动服务:

service vsftpd restart

重启之后,理论上来说,应该就能使用FTP客户端访问FTP服务器了,如果需要测试,你也可以直接在本机访问测试下:

ftp 127.0.0.1

此时应该能够得到正常返回,类似如下:

Connected to 127.0.0.1 (127.0.0.1).
220-Welcome to Mitchell Personal Web Server(MPWS)
220-Please use user name and password to login...
220-if you have any question, please contact MitchellChu<******@useasp.net>
220
Name (127.0.0.1:root):useasp
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp>

 而在系统的日志中应该也能看到登录信息(以下是多个日志文件,和有的如果没有配置vsftpd的日志文件将无法看到):

# tail -f /var/log/secure
Sep  4 23:36:11 CentOS vsftpd[8721]: pam_userdb(vsftpd:auth): user 'useasp' granted access

# tail -f /var/log/vsftpd.log
Fri Sep  4 23:36:15 2015 [pid 8721] [useasp] FTP response: Client "127.0.0.1", "150 Here comes the directory listing."

 到此,我们就已经配置了一个可以正常访问的FTP服务器了。

 

开启防火墙,开放给别人用吧

上面测试正常后,如果你开启了iptables,那么,记得添加规则,让你的21,20端口,以后被动模式时使用的端口范围开放出来吧:

iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 21 -j ACCEPT
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 20 -j ACCEPT
iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 65300:65360 -j ACCEPT
service iptables save

上面的是我的iptables的配置,开放了21和20端口,20端口是ftp-data用的,用来传输数据。

 

省却麻烦

为了不用每次重启之后都爬上机器上开启FTP,我们可以将vsftpd设置为开机启动——如果系统已经有了这个服务,忽略:

chkconfig --levels 345 vsftpd on
service vsftpd start

至此,就差不多得到一个比较完美的了。

 

后记:

上面是配置vsftpd使用虚拟用户的基本流程,但在配置中,我们总是能发现这样或者那样的问题,因此就需要我们不断的去DEBUG整个流程,由于Linux中有的存在,很多问题的根源还是在于SELinux的设置,如果你需要简单快捷,那么使用下面这个命令即可解决大部分问题——网上很多朋友也正是这样解决的:

setenforce 0
# or
setenforce Permissive

当然,如果你和Mitchell Chu一样,也是个不愿意如此简单了事的人儿,那么,咱们继续踏上征程……在按上面的方法配置好整个FTP服务器之后,本人也或多或少碰到如下的这些问题,现在汇集起来,方便后来者(若有时间,会展开来讲,暂且记录下):

  1. 不能定位到各自用户的目录,这个问题的存在,我们可以尝试设置SELinux中的ftp_home_dir来解决:

    setsebool -P ftp_home_dir on
  2. 不能列出FTP目录内容,这个问题也是SELinux引起的,可能的原因是vsftpd对于目录并没有权限,检查下自己的权限是否正确,vsftpd使用的是ftp用户访问,看看自己是否设置错权限了?

  3. 还是不能列出目录文件,再看看目标文件夹的类型对不对,一般需要的是public_content_r_t,如果你不确认,可以使用下面的命令查看:

    ls -alZ

    你将看到类似如下的输出:

    drwxr-xr-x. root root system_u:object_r:public_content_t:s0 .
    drwxr-xr-x. root root system_u:object_r:var_t:s0       ..
    drwxr-xr-x. root root system_u:object_r:var_t:s0 useasp
    drwxr-xr-x. root root system_u:object_r:var_t:s0 admin

    可以看到,默认的是var_t,我们需要设置一下,这里需要用到工具semanage,如果没有,需要安装policycoreutils-python

    yum -y install policycoreutils-python

     因为我使用了自行编译的Python版本,导致semanage不能正常运行,报错:

    Traceback (most recent call last):
      File "/usr/sbin/semanage", line 23, in <module>
        import policycoreutils.default_encoding_utf8
    ImportError: No module named policycoreutils.default_encoding_utf8

    修正后,直接使用semanage来设置:

    restorecon -R -v /var/ftp/{useasp,admin}
  4.  不能上传文件,不能创建文件夹,还是SELinux的问题啊,设置:

    setsebool -P allow_ftpd_anon_write on
  5. 还不能上传文件?试试:

    semanage fcontext -a -t public_content_rw_t "/var/ftp(/.*)?"
    restorecon -R -v /var/ftp/{useasp,admin}

    这个和上面的设置差不多,只是权限更大,请谨慎操作!

 

Windows下git使用代理服务器的设置方法

MitchellChu 2015-08-26 其他技术
0

在我朝独有的无敌GFW关照下(当然,也有可能IP被网站封了),要下载网络上开源的软件是非常困难的一件事情,在这种情况下,使用VPN或者代理服务器就非常有必要了。对于单个应用翻墙来说,个人比较喜欢用

SVN中,使用TortoiseSVN来进行版本控制时,设置代理非常简单,只需要在设置里面添加代理的信息即可。而git在GUI(v0.17.GITGUI)中却无法找到类似的设置,只能求助git bash来设置。

支持四种协议1,而除本地传输外,还有:git://, ssh://, 基于HTTP协议,这些协议又被分为哑协议(HTTP协议)和智能传输协议。对于这些协议,要使用代理的设置也有些差异:

  1. 使用git协议时,设置代理需要配置core.gitproxy
  2. 使用HTTP协议时,设置代理需要配置http.proxy
  3. 而是用ssh协议时,代理需要配置ssh的ProxyCommand参数

由于个人需求仅仅是HTTP的代理(相对来说,HTTP有比较好的通适性,配置git/ssh比较棘手),设置的时候,只需要针对单个设置http.proxy即可,在需要使用代理的项目下面使用git bash如下命令进行设置 ——你的Uri和port可能和我的不同,你懂的。:

git config http.proxy http://127.0.0.1:8088  # 也可以是uri:port形式

 这个是不需要鉴权的代理设置,如果需要鉴权,可能需要添加用户名密码信息:

git config http.proxy http://username:password@127.0.0.1:8088

如果git的所有项目都需要启用代理,那么可以直接启用全局设置:

git config --global http.proxy http://127.0.0.1:8088

为了确认是否已经设置成功,可以使用--get来获取:

git config --get --global http.proxy

这样可以看到你设置在global的http.proxy值。 

需要修改的时候,再次按照上面的方法设置即可,git默认会覆盖原有的配置值。

当我们的网络出现变更时,可能需要删除掉原有的代理配置,此时需要使用--unset来进行配置:

git config --global --unset http.proxy

 在命令之后,指定位置的设置值将会被清空,你可以再次使用--get来查看具体的设置情况。

 如果使用了HTTPS,肯呢个会碰到HTTPS 证书错误的情况,比如提示:SSL certificate problem。。。,此时,可以尝试将sslVerify设置为false

git config --global http.sslVerify false

 恩,到此,可以试试git来获取/更改项目了,此时,项目应该是使用代理来进行通讯的。

 

后记

  1. 如果非必要,一般不使用--global的方式来设置代理,毕竟代理有的时候访问一些项目比直接访问还慢,特别是当代理在国外,项目源在国内的时候,按需使用才是王道。
  2. 不要多次使用不同的参数来设置代理,一般使用文中两种方式酌情选用即可,--global--system--local各级设置后,可能会给自己带来不必要的麻烦。git默认是先到git Repository的配置文件中查找配置文件,如果没有才会到--global设置的文件中查找,因此,单个项目文件中的设置会覆盖--global的设置。
  3. 使用--global来配置的信息保存在当前用户的根目录下的.config文件中,而仓库中的配置保存在项目仓库的根目录下的.git/config文件中。
  4. 如果是Linux的用户,再使用git/ssh协议时,根据网上的说法,需要使用connect工具来做代理的转换。—— 本人Linux下的暂时没有配置git使用代理,暂时无法验证,摘录方法如下:

    GIT协议配置:安装完毕connect之后,你可以在特定的目录中建立一个socks5_proxy_wrapper(或其他的文件名亦可),然后文件内容改为:

    #!/bin/sh
    connect -S 127.0.0.1:8088 "$@"

    而后即可以配置git了,设置gitproxy

    git config core.gitproxy /path/to/socks5_proxy_wrapper
    
    # 路径要改

    抑或export GIT_PROXY_COMMAND

    export GIT_PROXY_COMMAND="/path/to/socks5_proxy_wrapper"

    SSH协议配置,同样需要建立一个文件,假设命名为:socks5_proxy_ssh,文件内容为:

    #!/bin/sh
    ssh -o ProxyCommand="/path/to/socks5_proxy_wrapper %h %p" "$@"

    配置git使用该文件:

    export GIT_SSH="/path/to/socks5_proxy_ssh"

    亦可配置"~/.ssh/config"中的ProxyCommand

    HTTP协议配置,这里直接使用http.proxy设置成socks5即可:

    git config http.proxy socks5://127.0.0.1:8088

    如果要全部使用,可以将上面的socks5_proxy_wrapper文件设置为:

    #!/bin/sh
    connect -H 192.168.1.100:8080 "$@"

    其他保持不变即可。而http.proxy设置同本文设置即可.

 

 参考

  1. git文档中指明服务器上的Git协议有:Local, HTTP, Secure Shell(SSH) and Git.
  2. 使用git config --help查看设置帮助
  3. Linux配置一节是来自segmentfault,权利归作者所有,本文作为个人博客,以备后用,如若不妥请告知。

Linux下以其他用户运行程序

MitchellChu 2015-07-29 Unix & Linux
0

 

#1:runuser命令

命令使用一个替代的用户或者组ID运行一个Shell。这个命令仅在root用户时有用。

仅以会话PAM钩子运行,并且没有密码提示。如果用一个非root用户,并且该用户没有权限设置user ID,这个命令将会因为程序没有setuid而失败。因runuser不会运行认证和账户PAM钩子,它比su更底层。

语法:

runuser -l userNameHere -c 'command'
runuser -l userNameHere -c '/path/to/command arg1 arg2'

举例来说,作为一个root用户,你也许想检查下oracle用户下的shell资源限制,输入:

# runuser -l oracle -c 'ulimit -SHa'

或者监察下nginx或lighttpd web服务器限制:

# runuser -l nginx -c 'ulimit -SHa'

或 

# runuser -l lighttpd -c 'ulimit -SHa'

 有时,root用户由于权限(安全)问题不能浏览NFS挂载的共享:

# ls -l /nfs/wwwroot/cyberciti.biz/http

或者

# cd /nfs/wwwroot/cyberciti.biz/http

可能的输出:

-bash: cd: /nfs/wwwroot/cyberciti.biz/http/: Permission denied

尽管如此,apache用户被允许浏览或访问挂载在/nfs/wwwroot/cyberciti.biz/http/下基于nfs的系统:

# runuser -l apache -c 'ls -l /nfs/wwwroot/cyberciti.biz/http/'

或者

# runuser -l apache -c 'cd /nfs/wwwroot/cyberciti.biz/http/; vi index.php'

使用runuser命令,无需使用密码,并且,只能在root用户下使用。

可用选项:

1. -l: 让shell成为登录shell,用 runuser -l PAM 文件替代默认的

2. -g:指定主要的组

3. -G 追加组

4. -c:命令,要传到shell的单个命令

5. --session-command=COMMAND:使用-c传递单个命令道shell中并且不创建新的会话

6. -m: 不重置环境变量。

 

 

#2:su命令

命令允许你成为一个超级用户或者替代用户(substitute user),欺骗用户(spoof user),设置用户(set user)或者切换用户(switch user)。它允许一个Linxu用户切换当前用户到那些你知道密码的目标用户,切换包括与之关联的运行中的控制台(console)或者Shell,它的语法如下:

su -
su - username

 

切换到root用户

su命令会询问目标用户的密码,在你的shell命令行中输入 su - 来切换到root用户(你必须知道root用户的密码):

vivek@wks01:~$ su -

或者

vivek@wks01:~$ su - root

输出示例:

Password:
root@wks01:/root# logout
vivek@wks01:~$

如果输入了正确的root密码,会话的所有权(这里应该指当前控制台的上下文——译注)将改为root账户。输入logout可以退出一个root登录的shell,输入 whoami或者id命令来验证当前会话的所有者:

whoami

或者

id

 

用root账户运行命令

语法是:

su - root -c "command"
# OR
su - -c "command arg1"

 查看/root目录下的内容,这些原本是普通用户无法访问的,运行:

su - root -c "ls -l /root"

需要注意的是,Linix和一些Unix-like系统有一个wheel用户组,并且只允许这个组内的用户使用su切换到root。

 

使用su命令来让其他用户运行命令

下面这个命令是切换到oracle的账户,并且显示限制清单:

$ su - oracle -c 'ulimit -aHS'

 同样, 如果提供了正确的oracle密码,会话所有权将会变成oracle账户。su命令的日志保存在系统日志中,一般是在/var/log/auth.log(Debian/Ubuntu)或者/var/log/secure(RHEL/CentOS)。

 

 

#3: sudo命令

以另外的用户来执行一个命令,但是它跟着一组关于那些用户可以以那些其他用户执行那些命令的规则(有点绕口——译注)。这个规则在/etc/sudoers这个文件中被定义。不像susudo验证用户是靠用户自己的密码而不是那个要切换的用户密码。当提供一个审计跟踪命令和他们的参数时(原文:...whileproviding an audit trail of the commands and their arguments——译注)sudo允许一个系统管理员给某些用户(或用户组)委派以root或其他用户来运行某些(或全部)命令的权限。这允许无需在用户之间共享密码就可以在指定宿主上将指定命令委派给指定用户。语法如下:

sudo command

参照下面的这些链接来获取更多信息:

1. 在操作系统如何配置和使用sudo工具1

2. sudo项目主页2

 

 

GUI工具注意事项(su和sudo的前端GUI)

命令是su的前台,sudo的前端。他们的主要用途是用来运行需要root权限但不需要运行一个X terminal emulator和使用直接使用su的图形化命令。语法如下:

gksu [-u <user>] [options] <command>
gksudo [-u <user>] [options] <command>
 

仅输入gksu,将会显示下面这个弹窗:

Linux gksu: Run Command As Root User Using Gnome GUI

图1:运行中的gksu

然后,你将会被要求输入root用户的密码:

Gnome gksu Command

图2:Gnome gksu对目标用户的验证框

你也可以直接运行下面代码:

gksu -u root 'ls /root'

或,以oracle用户运行命令:

gksu -u oracle 'ulimit -aHS'

或作为root登录:

gksu -u root -l

 

 

总结:runuser VS su VS sudo

命令 root 到 用户 用户 到 root 任意用户 到 任意用户 AnyUser 认证方式 日志文件 备注
runuser Y N N None N/A As runuser doesn't run auth and account PAM hooks, it runs with lower overhead than su.
su Y Y Y Target user's password /var/log/auth.log or /var/log/secure You must share your password or root password with other users.
sudo Y Y Y Authenticates users against their own password rather than that of the target user. /var/log/auth.log or /var/log/secure Allows a system administrator to delegate authority to give certain users (or groups of users) the ability to run some (or all) commands as root or another user while providing an audit trail of the commands.

 请查看man帮助页面来获取更多关于susudogksu,和gksudo命令的相关信息。

 

说明:

本文是nixCraft一篇文章的翻译,非常感谢nixCraft提供如此棒的教程,原文可以通过参考的原文连接查看,在翻译中省略了头尾一些非相关信息,同时由于翻译时间有限,是通过边看边译的形式完成此篇博文,错误肯定非常多,还请大家多多指正。

 

参考:

 1.  在Linux操作系统下如何配置和使用sudo(英文页面

 2.  sudo项目主页(英文页面

 3. 本篇博客原文(英文页面

Linux非root用户程序使用小于1024端口

MitchellChu 2015-07-09 Unix & Linux
0

下,默认端口1024下的是要在root下才能使用的,在其他用户下,如果尝试使用将会报错。在有的时候,我们可能考虑程序运行在root帐户下,可能会给Linux系统带来安全风险。那如何能够让非root用户运行的程序能够对外启用小于1024的端口呢?本文尝试给出一些方法:

 

第一种方法:

SetUID

为用户的应用程序在执行位设置user ID能够使程序可以有root权限来运行,这个方法让程序能够像在root下运行有同样的效果,不过需要非常小心,这种方法同样会带来,特别是当要执行的程序本身存在安全风险。使用的方法是:

chown root.root /path/to/application
#使用SetUID
chmod u+s /path/to/application

我们可以看到在系统下,/usr/bin/passwd这种文件,就使用了SetUID,使得每个系统能的用户都能用passwd来修改密码——这是要修改/etc/passwd的文件(而这个只有root有权限)。

既然要使用非root用户运行程序,目的就是要降低程序本身给系统带来的安全风险,因此,本方法使用的时候需要特别谨慎。

 

第二种方法:

CAP_NET_BIND_SERVICE

从2.1开始,Linux内核有了能力的概念,这使得普通用户也能够做只有超级用户才能完成的工作,这包括使用端口1

获取CAP_NET_BIND_SERVICE能力,即使服务程序运行在非root帐户下,也能够banding到低端口。使用的方法:

#设置CAP_NET_BIND_SERVICE
setcap cap_net_bind_service =+ep /path/to/application

Note:

1. 这个方法并不是所有Linux系统通适,内核在2.1之前的并没有提供,因此你需要检查要使用此方法所在系统是否支持(Linux must support capacity);

2. 另外需要注意的是,如果要运行的程序文件是一个脚本,这个方法是没有办法正常工作的(Script won't work)。

 

第三种方法:

Port Forwarding

如果要运行的程序有权限监听其他端口,那么这个方法是可以使用的,首先让程序运行在非root帐户下,并绑定高于1024的端口,在确保能正常工作的时候,将低端口通过端口转发,将低端口转到高端口,从而实现非root运行的程序。要使用此方法可以使用下面的方式:

# Enable the IP FORWARD kernel parameter.
sysctl -w net.ipv4.ip_forward=1

# Use iptables rules to redirect packets
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to:8088

第一步使用sysctl确保启用IP FORWARD功能(此功能在Red Hat/CentOS默认是被禁用的),注意,代码中使用的sysctl设置是临时性设置,重启之后将会被重置,如果要长久保存,需要在/etc/sysctl.conf文件内修改:

# Default value is 0, need change to 1.
# net.ipv4.ip_forward = 0
net.ipv4.ip_forward = 1

然后从文件中加载新的配置

# load new sysctl.conf
sysctl -p /etc/sysctl.conf

# or sysctl -p
# default filename is /etc/sysctl.conf

 第二步就是使用iptables的规则来实现端口转发到程序所在的端口,示例中我们要将80端口转发到8088。

此种方法能够比较好的达到我们的目的,我们的程序可以通过非root用户来运行,并能够对外提供号的服务。

 

第四种方式:

RINETD2

这种方法使用的也是端口转发,此工具可以将本地端口映射到远程端口,但此功能对于我们当前的功能来说,有点鸡肋,毕竟我们新增了一个额外的程序,这将可能会增加我们系统的风险性。在此不做推荐。

 

参考说明:

1.  Linux capabilities文档,参见其中CAP_NET_BIND_SERVIC,文中的内核是否从2.1开始有这个功能并不完全确定,此信息来自网上。

2.  官网地址:RINETD

 

log4net配置后不运行,没有日志输出

MitchellChu 2015-05-25 .NET技术
0

偷懒,新建项目中使用下载了log4net的配置文件(就那不带版本号的log4net.detail里面有说明是log4net xml),但设置好之后,log4net竟然奇葩的罢工了,没有任何报错。该配置方式是使用独立的log4net.xml文件来进行配置。

加载使用:

var log4net_config = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location),"log4net.xml");
XmlConfigurator.Configure(new FileInfo(log4net_config));

 检视过整个配置之后,发现这个Nuget来的配置文件有些地方出现纰漏:

其一是文件名称,log4net.xml并不是那么好用的,因为自己也会生成一个同名的文件,如果顽固的使用xml文件,那么你需要配合第二点,并确保输出目录中的这个文件确实是配置文件,个人建议改成log4net.config更为妥当;

其二是项目中该文件属性,复制到输出目录需要设置成始终复制或较新复制,生成操作更应该不是无,请选择内容;

按照上面的修改之后,使用下面的代码加载后,log4net又活过来了。

var log4net_config = Path.Combine(Path.GetDirectoryName(GetType().Assembly.Location),"log4net.config"); // here is diff.
XmlConfigurator.Configure(new FileInfo(log4net_config));

 当然,为了简单,你也可以在AssemblyInfo.cs中使用:

// 注意:是在AssemblyInfo.cs文件中添加
[assembly:log4net.Config.XmlConfigurator(ConfigFile="log4net.config",Watch=true)];

 恩,就这样,log4net又能和我们一起愉快的玩耍了。

 

获取本机系统内已经安装的浏览器列表

MitchellChu 2015-01-15 .NET技术 其他技术
0

最近突然碰到个比较棘手的问题,客户需要罗列出机器上所有可用的列表,至于作用么,就是让用户可以选择自己喜欢的浏览器来浏览指定信息。对于混杂的浏览器市场,这个需求确实够喝上一壶的了。

带着泪流满面的表情进入了无穷无尽的方案寻找中,不过到现在还是没有一个完满的答案。本文就记录下已经得到的一些信息吧。

对于一些标准的浏览器(有国内的么?自然是没有,谢谢),他们都会将自己的信息保存到这个注册表项下面,里面提供了丰富多彩的内容,基本上你想要的,他都能告诉你。需要注意的是,在64位系统中,会有两个位置可以找到。

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Clients\StartMenuInternet
HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet

上面这两个路径下,就包含了机器上所有的浏览器信息(再次排除国内浏览器)。如果是在32位系统中,只有后一个路径可以使用。

知道位置之后,在程序中要获取浏览器列表信息就比较简单了,参见以下C#()代码:

        public static void BrowsersData()
        {
            RegistryKey browsersKey;
            browsersKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\WOW6432Node\Clients\StartMenuInternet");
            if (browsersKey == null)
                browsersKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet");
            using (browsersKey)
            {
                string[] subKeys = browsersKey.GetSubKeyNames();
                string locationPath = @"\shell\open\command";
                foreach (string key in subKeys)
                {
                    RegistryKey appLocationKey = browsersKey.OpenSubKey(key + locationPath);
                    Console.WriteLine("{0}:{1}", key, (string)appLocationKey.GetValue(null));
                    appLocationKey.Dispose();
                }
            }
        }

先说明,上述方法对国内绝大部分浏览器无效。国内浏览器不知道是出于何种原因,均没有对相应的项进行添加。难道是不屑与国际标准为伍的节奏么?

测试可以正常使用的浏览器有:IE,FireFox,Chrome均可正常使用

国内不支持的浏览器有:360浏览器,搜狗浏览器,腾讯浏览器均无法支持

国内支持的浏览器:百度Player浏览器(百度浏览器没测试了,被国内浏览器整的没信心了), 淘宝浏览器(这个未确认版本)

对于国内的浏览器如何处理呢,一般只能从安装程序的列表中获取了,但这个获取要自己找匹配了,比如360浏览器,在添加删除那个列表中你可以找名字匹配的程序即可。不过这里会有一个问题,就是要收集所有浏览器信息了,好端端的,又要让程序狗累上一宿来搜罗这些浏览器信息了。

 

后记:搜狗浏览器关于不能在注册表中找到的问题,据搜狗的技术支持说现在在Win8上是支持了,但未确认。

 

参考:

1. How to Register an Internet Browser or Email Client With the Windows Start Menu

关于博主

  一枚成分复杂的网络IT分子,常年游弋于电子商务,属于互联网行业分类中的杂牌军。当前正在待业中...