Using SRC Language

来源:互联网 发布:hp8570p windows 7系统 编辑:程序博客网 时间:2024/06/10 09:21
更新至版本1.1

 

SRCSend Recv Cmds)语言是一种描述各种命令格式和字段的简单语言,主要用于快速开发网络测试程序,同时可以作为命令文档来查阅。关于SRC语言的详细语法,请参考“SRC Language”,本文主要介绍如何使用SRC语言和编译器。

 

SRC编译器

完整的SRC编译器发布版本包括:

s   可执行文件srcc

s   静态库libsrc.a

s   动态库libsrc.so

s   头文件SRC_language.h

对于没有调用“FUN”函数的SRC源文件,可以使用srcc程序直接编译并运行。

 

使用srcc

srcc程序的使用方式为:

srcc FILE

其中,FILESRC源文件的文件名。目前,srcc一次只能读取一个SRC源文件进行处理。

 

使用src

如果命令需要使用FUN函数,那么必须使用src库。

使用src库的代码需要包含头文件“SRC_language.h”,里面声明了如下的函数和变量:

s     SRC_version

当前SRC编译器的版本信息,例如“1.0.67”;

s     void SRC_init()

初始化SRC编译环境。在使用其他库函数之前,请先调用SRC_init()函数。

s     bool SRC_compile(conststd::string & filename)

编译SRC源文件“filename”,返回值表示是否成功。如果返回值为false,应该退出程序,并按照错误提示修改SRC源代码。

s     bool SRC_run()

运行编译的SRC源代码,包括发送和接收命令数据,返回值表示是否成功。如果返回false,应该退出程序,根据错误提示检查SRC源代码或对方服务器的可用性。

s     boolSRC_register_function(const std::string & func_name,

    bool (*func_ptr)(std::vector<char> &src,std::vector<char> & dst))

注册自定义的函数。SRC_register_function函数接受2个参数:第一个是字符串,表示自定义函数的名字,这个名字在SRC源代码里可以作为FUN函数的第一个参数;第二个参数是函数指针,指向用户自定义的函数,函数的格式是:

bool func(std::vector<char>& src, std::vector<char> & dst)

其中,参数src是由SRC库提供的当前命令的数据,而参数dst是函数进行处理后返回给SRC库的数据。函数返回值表示是否处理成功,注意:

1.             如果自定义函数返回trueSRC库会使用参数dst里的数据作为命令数据,并继续后面的处理;

2.             如果自定义函数返回falseSRC库报告一个运行时错误,如果这个错误不足以终止程序,那么SRC使用参数src里的数据作为命令数据,继续后面的处理。

下面是一个使用src库的例子。

 

#include <SRC_language.h>

    //安装SRC库之后,SRC_language.h会在/usr/local/include目录下

 

bool func(std::vector<char>& src, std::vector<char> & dst)

{  //自定义函数

    dst.swap(src);

    return true;

}

 

int main(int argc, const char** argv)

{

    SRC_init();

    if(!SRC_register_function("my_func",func))

       return 1;

    if(!SRC_compile(argv[1]))

        return 1;

    if(!SRC_run())

        return 1;

    return 0;

}

 

这里的用户自定义函数func不对数据进行任何修改,只是简单的转移给dst。在SRC源代码里,用户可以通过:

FUN(my_func)

调用自定义函数func。当然,实际中的用户自定义函数可以做更多的事情。

编译上述代码,在链接时加入“-lsrc”标志,例如:

g++ -o test.out test.cpp -lsrc

 

应用SRC语言

编写SRC代码其实是一件很简单的事情,你只要对照着命令的最初说明,写出所有的字段就可以了。而且我相信,当你习惯了SRC语言的简洁明快,你就在也不会想着自己写测试程序了。

我们从一个简单的例子开始。

 

1:简单命令

假设我们要测试一个服务器,发送的命令格式如下:

类型

变量名

名称

U16

magic

魔数(0x11DE

U8

version

协议版本号(1

U16

type

包类型(0

U32

seq_num

包序号

U32

body_len

后续包体长度

U32

program_version

程序版本

 

在正常情况下,服务器会回复一个如下格式的命令:

类型

变量名

名称

U16

magic

魔数(0x11DE

U8

version

协议版本号

U16

type

包类型(1

U32

seq_num

包序号

U32

body_len

后续包体长度

U32

error_code

错误码,当为0时才有后面的字段

U64

tablet_id

子表ID

String

table_name

表名称

String

start_row

起始行关键字

String

end_row

结束行关键字

U16

status

状态(位)

String

host_name

主机名

U16

port

端口号

U64

start_code

起始码(启动时间)

 

虽然这2个命令看起来很复杂,但是在SRC语言里,它们都是最简单的命令。我们根据命令的字段说明,可以直接写出以下的SRC代码:

TCP("192.168.13.89",20080)    //指定服务器地址和端口

NBO                               //指定命令编码解码的字节序

 

CMD Query SEND                  //发送的命令,以及每个字段的测试值

  U16 magic := 0x11DE

  U8 version := 1

  U16 type := 0

  U32 seq_num := 1234

  U32 body_len

  BEGIN(body_len)

  U32 program_version := 1

END CMD

 

CMD Resp RECV            //服务器应该回复的命令

  U16 magic

  U8 version

  U16 type

  U32 seq_num

  U32 body_len

  U32 error_code == 0  //断言声明

  U64 tablet_id

  STR table_name

  STR start_row

  STR end_row

  U16 status

  STR host_name

  U16 port

  U64 start_code

END CMD

根据协议说明,只有当回复命令的error_code字段等于0的时候,才有后面的字段,所以使用了断言声明来保证。

下面是使用srcc程序编译运行这段代码后的结果:

 

  SEND command 'Query' data =

0000h: 11 DE 01 00 00 00 0004 D2 00 00 00 04 00 00 00 ; ................

0010h: 01                                                       ; .

 

  RECV command 'Resp'

magic = 4574

version = 1

type = 1

seq_num = 1234

body_len = 71

error_code = 0

tablet_id = 0

table_name =(19).METADATA_1ST_LEVEL

start_row = (0)

end_row = (0)

status = 1

host_name = (12)58.61.39.244

port = 20088

start_code = 1227369904024166

  RECV data =

0000h: 11 DE 01 00 01 00 0004 D2 00 00 00 47 00 00 00 ; ............G...

0010h: 00 00 00 00 00 00 0000 00 00 00 00 13 2E 4D 45 ; ..............ME

0020h: 54 41 44 41 54 41 5F31 53 54 5F 4C 45 56 45 4C ; TADATA_1ST_LEVEL

0030h: 00 00 00 00 00 00 0000 00 01 00 00 00 0C 35 38 ; ..............58

0040h: 2E 36 31 2E 33 39 2E32 34 34 4E 78 00 04 5C 49 ; .61.39.244Nx../I

0050h: 53 23 2A 66                                            ; S#*f

 

srcc程序打印出了实际发送的数据,服务器回应的数据,以及回应命令里每个字段的值。

 

2HTTP头信息

有些命令使用HTTP协议进行传输,于是需要在命令数据的前面加上HTTP头信息,而这些信息里,“Content-Length:”字段是难点。

假设这次我们需要测试的服务器,接受如下格式的查询命令:

类型

变量名

名称

U16

version

协议版本号(>=100100以上使用加密协议传输)

U16

cmdtype

命令类型(161

U32

seq

包序号

U32

body_len

后续包体长度

String

file_hash

文件的hash标识

String

client_hash

客户端的hash标识

String

peer_id

客户端标识

 

回应的命令是如下格式:

类型

变量名

名称

U16

version

协议版本号(>=1

U16

cmdtype

命令类型(162

U32

seq

包序号

U32

body_len

后续包体长度

String

file_hash

文件的hash标识

String

client_hash

客户端的hash标识

String数组

peer_id_array

peer资源列表

 

命令虽然比前一个例子简单,但是这次的命令需要使用HTTP协议传输,即需要编码和解码HTTP头信息。对于编码HTTP头信息,SRC语言采用非常简单的方法解决这个问题,即自由变量流输出声明SRC代码如下:

TCP("127.0.0.1",9531)     //指定服务器地址和端口

NBO                          //指定命令编码解码的字节序

 

CMD QueryCommand SEND    //发送的命令

//HTTP wrap

RAW http1 := "POSThttp://127.0.0.1:12345/ HTTP/1.1/r/n/

Content-Length: "

DEF U32 pack_len    //使用自由变量计算整个数据包的长度

RAW pack_len_str <<pack_len  //使用流输出变量将长度写入HTTP头信息里

RAW http2 :="/r/nContent-Type: application/octet-stream/r/n/

Connection: Close/r/n/r/n"

BEGIN(pack_len)    //开始计算整个数据包的长度

//cmd data

U16 ver := 100

U16 cmd_type:(161)

U32 seq = 1234

U32 len(0)

BEGIN(len)

STR file_hash :="http://www.xunlei.com"

STR client_hash :=UNHEX("1A2B3C4D5E6F")

STR peer_id :="ABCDEF"

END CMD

发送命令的确比前一个例子复杂了些,总共增加了5条语句,来实现添加HTTP头信息,但是相比通过C++程序实现,这里的复杂度简直是小儿科。

服务器返回的命令同样也会使用HTTP传输,所以我们还需要解码HTTP头信息。SRC语言使用断言声明流输入声明解决这个问题。

CMD RespCommand RECV

//http wrap

RAW http1 == "HTTP/1.1200 OK/r/n/

Content-Length: "             //使用断言声明读取RAW字符串

RAW pack_len_str >>U32    //使用流输入声明读取数值字符串

RAW http2 =="/r/nContent-Type: Application/

/octet-stream/r/nConnection: Close/r/n/r/n"

//cmd data

U16 ver >= 100

U16 cmd_type == 162

U32 seq

U32 len

len < 32k

U8  result

!result

STR fileHash;

STR clientHash;

STR[] peer_id_array;    //array

END CMD

接收命令使用3条语句,来解码HTTP头信息。

下面是使用srcc程序编译运行这段代码后的结果:

 

  SEND command 'QueryCommand' data =

0000h: 50 4F 53 54 20 68 7474 70 3A 2F 2F 31 32 37 2E ; POST http://127.

0010h: 30 2E 30 2E 31 3A 3132 33 34 35 2F 20 48 54 54 ; 0.0.1:12345/ HTT

0020h: 50 2F 31 2E 31 0D 0A43 6F 6E 74 65 6E 74 2D 4C ; P/1.1..Content-L

0030h: 65 6E 67 74 68 3A 2035 37 0D 0A 43 6F 6E 74 65 ; ength: 57..Conte

0040h: 6E 74 2D 54 79 70 653A 20 61 70 70 6C 69 63 61 ; nt-Type: applica

0050h: 74 69 6F 6E 2F 6F 6374 65 74 2D 73 74 72 65 61 ; tion/octet-strea

0060h: 6D 0D 0A 43 6F 6E 6E65 63 74 69 6F 6E 3A 20 43 ; m..Connection: C

0070h: 6C 6F 73 65 0D 0A 0D0A 00 64 00 A1 00 00 04 D2 ; lose.....d......

0080h: 00 00 00 2D 00 00 0015 68 74 74 70 3A 2F 2F 77 ; ...-....http://w

0090h: 77 77 2E 78 75 6E 6C65 69 2E 63 6F 6D 00 00 00 ; ww.xunlei.com...

00a0h: 06 1A 2B 3C 4D 5E 6F00 00 00 06 41 42 43 44 45 ; ..+<M^o....ABCDE

00b0h: 46                                                       ; F

 

  RECV command 'RespCommand'

http1 = (33)HTTP/1.1 200OK/r/nContent-Length:

pack_len_str = (2)88

http2 = (63)/r/nContent-Type:Application/octet-stream/r/nConnection: Close/r/n/r/n

ver = 100

cmd_type = 162

seq = 1234

len = 76

result = 0

fileHash =(21)http://www.xunlei.com

clientHash = (6)/032+<M^o

peer_id_array.size() = 3

peer_id_array[0] = (6)ABCDEF

peer_id_array[1] =(9)ABCDEFabc

peer_id_array[2] =(9)ABCDEFefg

  RECV data =

0000h: 48 54 54 50 2F 31 2E31 20 32 30 30 20 4F 4B 0D ; HTTP/1.1 200 OK.

0010h: 0A 43 6F 6E 74 65 6E74 2D 4C 65 6E 67 74 68 3A ; .Content-Length:

0020h: 20 38 38 0D 0A 43 6F6E 74 65 6E 74 2D 54 79 70 ;  88..Content-Typ

0030h: 65 3A 20 41 70 70 6C69 63 61 74 69 6F 6E 2F 6F ; e: Application/o

0040h: 63 74 65 74 2D 73 7472 65 61 6D 0D 0A 43 6F 6E ; ctet-stream..Con

0050h: 6E 65 63 74 69 6F 6E3A 20 43 6C 6F 73 65 0D 0A ; nection: Close..

0060h: 0D 0A 00 64 00 A2 0000 04 D2 00 00 00 4C 00 00 ; ...d.........L..

0070h: 00 00 15 68 74 74 703A 2F 2F 77 77 77 2E 78 75 ; ...http://www.xu

0080h: 6E 6C 65 69 2E 63 6F6D 00 00 00 06 1A 2B 3C 4D ; nlei.com.....+<M

0090h: 5E 6F 00 00 00 03 0000 00 06 41 42 43 44 45 46 ; ^o........ABCDEF

00a0h: 00 00 00 09 41 42 4344 45 46 61 62 63 00 00 00 ; ....ABCDEFabc...

00b0h: 09 41 42 43 44 45 4665 66 67                       ; .ABCDEFefg

 

忽略其他数据,我们只关心发送命令里的“Content-Length:”字段部分是否正确,事实说明,“Content-Length: 57”是正确的。同时,服务器的正确回复和srcc的正确解码,也证明了SRC语言的能力。

 

3:加密解密

虽然到目前为止,SRC语言已经支持了大部分命令的编码和解码,但是还不够,SRC语言需要支持更多的命令,包括加密和解密的命令。

对命令数据进行加密或者解密,或者其他处理,都可以抽象为同一类型的操作:对输入数据进行处理,并把结果放进输出数据里。所以SRC语言使用了如下形式的C++函数来代表这一类型的操作:

bool func(std::vector<char>& src, std::vector<char> & dst)

其中函数参数和返回值的意义在“使用src库”一节里已有详细介绍。要使用自定义的函数,必须使用SRC编译器提供的库,这是因为:

1.      srcc编译程序srcc没有内置任何自定义函数;

2.      自定义函数往往涉及用户机密,不应该包含在SRC公共库里;

3.      只有用户才最清楚自定义函数的细节

用户根据自己的需求,写好自定义函数,然后使用SRC库生成新的SRC编译器衍生品,就可以编译运行调用自定义函数的SRC代码了。当然,这个衍生品也包含原始SRC编译器的一切功能。

这次我们需要测试的命令,与“例2HTTP头信息”里的相同,只是这次我们把版本号字段version的值设置为101。根据协议说明,当版本号字段大于100时,需要采用加密协议传输。

加密和解密函数的细节不必追究,这里给出函数的大概模样:

boolaes_encrypt(std::vector<char> & src,std::vector<char> &dst)

{

    ……   //加密函数实现

}

 

boolaes_decrypt(std::vector<char> & src,std::vector<char> &dst)

{

    ……   //解密函数实现

}

定义了这2个函数,我们的主程序main里还需要做如下事情:

#include "SRC_language.h"   //包含SRC头文件

 

int main(int argc, const char** argv)

{

    SRC_init();                      //初始化SRC编译环境

   if(!SRC_register_function("aes_encrypt",aes_encrypt))

//注册加密函数

        return 1;

   if(!SRC_register_function("aes_decrypt",aes_decrypt))

                                       //注册解密函数

        return 1;

    if(!SRC_compile(argv[1]))     //编译SRC源文件

        return 1;

    if(!SRC_run())                  //运行代码,发送和接收命令

        return 1;

    return 0;

}

 

将这段代码编译,与SRC库链接成新的程序后,我们得到了SRC编译程序的一个衍生品,这个衍生品支持“aes_encrypt”和“aes_decrypt”这2个自定义函数。

接下来我们需要写出发送和接收加密命令的SRC源代码,也许你认为可能会很复杂,恰恰相反,这回的SRC源代码比起例2,总共只有三处修改:

 

TCP("127.0.0.1",9531)     //指定服务器地址和端口

NBO                          //指定命令编码解码的字节序

 

CMD QueryCommand SEND    //发送的命令

//HTTP wrap

RAW http1 := "POSThttp://127.0.0.1:12345/ HTTP/1.1/r/n/

Content-Length: "

DEF U32 pack_len    //使用自由变量计算整个数据包的长度

RAW pack_len_str <<pack_len  //使用流输出变量将长度写入HTTP头信息里

RAW http2 :="/r/nContent-Type: application/octet-stream/r/n/

Connection: Close/r/n/r/n"

BEGIN(pack_len)    //开始计算整个数据包的长度

//cmd data

U16 ver := 101       //这是第一处修改,将版本号设置为101

U16 cmd_type:(161)

U32 seq = 1234

U32 len(0)

BEGIN(len)

STR file_hash :="http://www.xunlei.com"

STR client_hash :=UNHEX("1A2B3C4D5E6F")

STR peer_id :="ABCDEF"

FUN(aes_encrypt)   //这是第二处修改,调用自定义加密函数

END CMD

 

CMD RespCommand RECV   //接收的命令

//http wrap

RAW http1 == "HTTP/1.1200 OK/r/n/

Content-Length: "             //使用断言声明读取RAW字符串

RAW pack_len_str >>U32    //使用流输入声明读取数值字符串

RAW http2 =="/r/nContent-Type: Application/

/octet-stream/r/nConnection: Close/r/n/r/n"

//cmd data

U16 ver >= 100

U16 cmd_type == 162

U32 seq

U32 len

len < 32k

FUN(aes_decrypt,len)  //第三处修改,接收后续len字节数据,并调用解密函数

U8  result

!result

STR fileHash;

STR clientHash;

STR[] peer_id_array;    //array

END CMD

 

运行这段SRC代码的结果与例2大致相同,这里不再赘述。

 

4:结构体数组

1.1版本开始,SRC语言增加了一个重要的特性:支持结构体数组。有一些命令,不能仅仅当作简单字段的集合,它有自己的逻辑结构,特别是这些逻辑结构内部又会有更小的逻辑结构。SRC语言可以通过一层层解构这些结构,把有些命令还原成为简单字段的集合。但是当命令里含有逻辑结构的数组时,这种解构就变得不可能了。所以必须有一种描述这种结构体数组的方法,这就是SRC语言里的“BEGIN ARRAY”函数和“END ARRAY”函数。

我们用一个复杂的命令,来展示SRC语言的真正实力。发送的命令格式如下:

字段

类型

描述

ProtocolVersion

U32

协议版本号

Seq

U32

命令的序列号

CommandLength

U32

命令长度,指跟在这个字段后面的所有命令字段长度的总和

QUERY_PLUS

U8

59

Peer ID

STR

 

CID

STR

三段CID

File size

U64

文件大小

GCID

STR

Gcid

Peer capability flag

U8

 

Interal_ip

U32

内部IP地址

NAT_TYPE

U32

网络类型,类型检测

Level_resource

U8

期望等级资源个数

 

服务器的回复命令格式如下:

字段

类型

引入版本

描述

ProtocolVersion

U32

53

协议版本号

Seq

U32

53

命令的序列号

CommandLength

U32

53

命令长度,指跟在这个字段后面的所有命令字段长度的总和

QUERYRESP

U8

53

60

Result

U8

53

是否成功:

0:成功;其他值代表错误码

失败时忽略其后任何字段

CID

STR

53

三段CID

File size

U64

53

文件大小

GCID

STR

53

全文CID

Level_resource

U8

53

实际等级资源个数

PeerResource

ARRAY

53

资源数组

 

数组每个元素的格式如下:

自定义类型PeerResource

字段

类型

引入版本

描述

Len

U32

53

数组元素长,指后面字段的长度和

PeerID

STR

53

资源的peerID

File_name

STR

53

资源名称

U32ernalIP

U32

53

资源的内部IP

Port

U16

53

资源监听的Tcp端口

CapabilityFlag

UU8

53

 

 

所有对命令需要使用HTTP协议传输,并且使用加密解密算法。此外,最难部分是回复命令最后的PeerResource结构体数组。

我不再重复写出使用SRC库的C++代码,直接给出命令的SRC源代码,除了前面说过的HTTP头定义方式,自定义函数的使用方式,真正的革新只有2条语句:

STR server_ip := "192.168.13.112"

U16 server_port := 80

 

TCP(server_ip,server_port)

HBO

 

CMD Query SEND     //发送的命令

//http wrap

  RAW http1 := "POSThttp://127.0.0.1:12345/ HTTP/1.1/r/n/

Content-Length: "

  DEF U32 pack_len

  RAW pack_len_str << pack_len  //stream output

  RAW http2 := "/r/nContent-Type:application/octet-stream/

/r/nConnection:Close/r/n/r/n"

  BEGIN(pack_len)

//cmd data

  U32 ProtocolVersion := 53    //协议版本号

  U32 Seq := 1234              //命令的序列号

  U32 CmdLength                //命令长度

  BEGIN(CmdLength)

  U8 CmdType := 59         //命令类型

  STR PeerID := "3456789012345"

  STR Cid := UNHEX("B2F4F2869EAEEFEC231FAF81E46AD085ACB0742F")

  U64 FileSz := 0

  STR Gcid := UNHEX("")

  U8 PeerCapability := 106

  U32 Inter_ip := IP NBO("192.168.85.45")  //内部IP地址

  U32 NAT_TYPE := 39                       //网络类型,类型检测

  U8 Level_resource :=  100            //期望等级资源个数

  FUN(aes_encrypt)

END CMD

 

发送命令没有什么新意,全是前面介绍过的内容。

 

CMD Resp RECV        //接收的命令

//http wrap

  RAW http1 == "HTTP/1.1 200OK/r/nContent-Length: "

  RAW pack_len_str >> U32  //stream input

  RAW http2 == "/r/nContent-Type:application/octet-stream/

/r/nConnection: Close/r/n/r/n"

//cmd data

  U32 ProtocolVersion

  U32 Seq

  U32 CmdLength

  FUN(aes_decrypt,CmdLength)

  U8 CmdType == 60

  U8 Result == 0

  STR Cid

  U64 FileSz

  STR Gcid

  U8 Level_resource

  BEGIN ARRAY         //革新之一:读取数组元素个数,并标识结构体的起始位置

    U32 rc_len_1

    STR PeerID

    STR Filename

    U32 InterIP

    PRINT("             ",IP NBO(InterIP))

    U16 Port

    U8 CapabilityFlag

  END ARRAY           //革新之二:标识结构体的结束位置。如果需要,重复读取结构体

END CMD

 

使用2条语句,解决了复杂的命令结构体数组,这就是SRC语言遵循的设计原则:用最少的代码,解决最多的问题。这段SRC代码需要使用包含了自定义函数“aes_encrypt”和“aes_decrypt”的SRC编译器衍生品来执行,如果回复的命令里,真的有结构体数组元素,那么SRC编译器会打印出如下的信息:

……     //省略前面的信息

  ARRAY_SIZE = 28

rc_len_1[0] = 31

PeerID[0] =(16)00016C035AC3V024

Filename[0] = (0)

InterIP[0] = 1677830336

             192.168.1.100

Port[0] = 80

CapabilityFlag[0] = 107

rc_len_1[1] = 31

PeerID[1] =(16)00016C063E674AM4

Filename[1] = (0)

InterIP[1] = 1749973885

             125.123.78.104

Port[1] = 23516

CapabilityFlag[1] = 106

rc_len_1[2] = 31

PeerID[2] =(16)00016C9A04A8AKM4

Filename[2] = (0)

InterIP[2] = 2641227388

             124.238.109.157

Port[2] = 80

CapabilityFlag[2] = 107

……     //省略后面的信息

 

SRC编译器先打印出数组的总长度,然后一次显示每个元素的每个字段,并以下标值区分。

 

后记

本文介绍了SRC语言和编译器的使用,通过实例展示了SRC语言的能力。如果你还在使用C++PythonJava或者其他语言编写网络测试程序,那么不妨试试SRC。我相信一旦你知道了SRC语言的魅力,就会在一切可能的情况下尽量使用它。

目前SRC编译器是开源项目,所有源代码和发布版本文件都可从以下网址获取:

http://code.google.com/p/client-model/

最后欢迎大家提出宝贵的建议和意见,请将邮件发送至daidodo AT gmail.com,谢谢!