基于Bro 2.5.3 文档,按照原文结构整理,非原文翻译,建议阅读英文文档后阅读,如有错误请不吝指出。
原文地址:https://www.bro.org/sphinx/scripting/index.html
时间 修改内容
2018.02.24 完成全文
   
   
一、理解Bro脚本语言
Bro主要提供两项能力:
  • 解析流量并生成event,整个过程不对使用者开放,也不对生成的event进行分析。
  • 分析event并产生输出,Bro自实现了一套脚本语言,并内置了常用功能,使用者可以根据需要实现分析脚本。注意,Bro脚本语言是基于event驱动的。
Bro脚本主要由三部分组成,导入和导出在parse-time执行,业务逻辑在run-time执行:
  • 导入:@load+相对路径,结尾无分号。Bro搜索路径+相对路径=导入文件绝对路径。相对路径为目录,则导入该路径下的__load__.bro文件,该文件多包含一组@load指令,类似python包的__init__.py文件;相对路径为文件,可忽略.bro后缀。导入将递归执行,重复导入将被忽略。
  • 导出:export{scope name: type = value;},export代码块结束无分号,声明及初始化语句后有。用于声明或重定义公用变量,可供当前和其他脚本使用。需要重定义的一般为常量,常量允许在parse-time重定义,run-time不允许修改,与变量不同。
  • 业务逻辑:event/hook/function等可执行代码块,实现业务功能。
二、了解event queue和event handler
关于event queue(事件队列):
  • event queue是Bro底层(event生成器)和Bro脚本共用的有序队列(先进先出)。
  • event生成器根据解析的流量生成event推入event queue,相当于生产者。这部分功能没有Bro脚本参与,不对使用者开放,基本没有其他有效输出。
  • Bro脚本从event queue取出event并触发对应event handler完成既定处理,相当于消费者。
关于event handler(事件处理器):
  • Bro脚本声明并初始化handler,handler被event触发后接收event携带的信息,并基于这些信息进行决策,也可能修改它们。
  • Bro在內建函数脚本和*.bif.bro脚本中使用event关键字声明event handler,涵盖全部內建event类型,event将触发对应名称的event handler。
  • handler包含用户对event的自定义处理,因此,哪些需要初始化、如何初始化一般由用户根据业务场景自定义。相同类型的handler可以被初始化多次并指定优先级,Bro将在parse-time合并handler,优先级高低与数值大小正相关,默认为0,接受负数。
  • Logging Framework可以配置生成自定义event,用户需要自行声明并初始化log_x event handler,以便完成处理。
三、了解connection record
  • record提供了一个key-value模式的数据类型框架,使用户可以通过type和record关键字自定义数据类型。文档中的record类型多泛指这类自定义数据类型,connection就是其中一种。
  • 一条connection数据对应一个连接,连接的生命周期内可能触发多个event handler,connection数据也在这个过程中被不断修改和完善。connection类型就是用于跟踪记录一个连接在其生命周期内的状态变化。
四、数据类型和数据结构
4.1 了解scope(作用域):
  • Bro脚本有两种等价的参数声明方式:

    • SCOPE name: TYPE;
    • SCOPE name = EXPRESSION;
  • SCOPE支持三种关键字:global(全局变量)、const(全局常量)和local(局部变量)。
  • global一般定义在parse-time,作用于整个命名空间,只能在run-time修改。
  • const一般定义在parse-time,作用于整个命名空间,有&redef属性可以在parse-time修改。
  • local只能定义在run-time,作用于当前代码块,只能在run-time的当前代码块修改。注意:event和hook可能由多代码块组成。
  • 当前命名空间由当前文件+已导入且module name相同的其他文件组成,所有已导入且module name不同的文件的export区域将根据module name合并后成为当前命名空间的子空间,并通过module name引用。
  • Bro启动时将处理脚本中除可执行类型(event/hook/function)以外内容,称为parse-time;然后等待事件触发相应可执行类型,完成处理,即所谓的事件驱动,该阶段称为run-time。
  • 相同作用域内,global、const和local均不允许重名。
4.2 数据类型
4.2.1 基本数据类型—数字类型—int类型
int类型相关总结请参考下表:
int
简要说明: 有符号整形
显式声明及初始化: scope name: int; name = return_int_expression;
scope name: int = return_int_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_int_expression;
数据格式:
算数运算:
比较运算:
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.2 基本数据类型—数字类型—count类型
count类型相关总结与int类型基本一致,请参考下表:
count
简要说明: 无符号整形
显式声明及初始化: scope name: count; name = return_count_expression;
scope name: count = return_count_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_count_expression;
数据格式:
算数运算:
比较运算:
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.3 基本数据类型—数字类型—double类型
double类型相关总结与int类型基本一致,请参考下表:
double
简要说明:
显式声明及初始化: scope name: double; name = return_double_expression;
scope name: double = return_double_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_double_expression;
数据格式:
算数运算:
比较运算:
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.4 基本数据类型—string类型
string类型相关总结请参考下表:
string
简要说明: 字符串类型,与python字符串类型非常类似
支持负索引;具备容器部分特征
注意使用双引号描述字符串
显式声明及初始化: scope name: string; name = return_str_expression;
scope name: string = return_str_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_str_expression;
数据格式: "hello world"
算数运算: 支持加法运算,用于字符串连接,其他不支持
比较运算: 支持,判断相等时,注意 str1 == str2 和 my_pattern == str
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 支持,使用||操作符
成员是否存在: 支持,使用in或!in,注意 str1 in str2 和 my_pattern in str
索引成员: 支持,使用从0开始递增的数字索引,支持负索引
分片: 支持,使用[from : to]操作符,from和to为索引,支持负索引、索引越界、索引缺失
遍历操作: 支持,使用for (...in...)
內建函数:
4.2.5 基本数据类型—bool类型
bool类型相关总结请参考下表:
bool
简要说明: 布尔类型,使用T表示真,使用F表示假
显式声明及初始化: scope name: bool; name = return_bool_expression;
scope name: bool = return_bool_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_bool_expression;
数据格式: T或F
算数运算: 不支持
比较运算: 不支持
逻辑运算: 支持,使用&&和||操作符
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.6 基本数据类型—网络数据类型—addr类型
addr类型相关总结请参考下表:
addr
简要说明: 表示网络层地址,支持IPv4和IPv6,Bro自定义数据类型之一
显式声明及初始化: scope name: addr; name = return_addr_expression;
scope name: addr = return_addr_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_addr_expression;
数据格式: IPv4:10.136.17.35
IPv6:
域名:www.baidu.com
# 域名将进行自动转换,因域名可能对应多个IP,转换为set[addr]
# Bro运行过程中,域名-IP映射关系不会更新,建议用于域名稳定的场景
# 因域名自动转换为set[addr],使用显式声明会导致声明和初始化数据类型不一致的报错
算数运算: 不支持
比较运算: 支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.7 基本数据类型—网络数据类型—port类型
port类型相关总结请参考下表:
port
简要说明: 表示传输层端口,支持UDP/TCP/ICMP/UNKNOWN协议,Bro自定义数据类型之一
显式声明及初始化: scope name: port; name = return_port_expression;
scope name: port = return_port_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_port_expression;
数据格式: 端口号/协议
UDP:514/udp
TCP:80/tcp
ICMP:/icmp,没有端口概念,Bro将ICMP类型和编码设置为来源和目的端口
UNKNOWN:/unknown
算数运算: 不支持
比较运算: 支持,默认unknown < tcp < udp < icmp
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.8 基本数据类型—网络数据类型—subnet类型
subnet类型相关总结请参考下表:
subnet
简要说明: 表示子网,使用CIDR记法,支持容器类型部分特征,Bro自定义数据类型之一
显式声明及初始化: scope name: subnet; name = return_subnet_expression;
scope name: subnet = return_subnet_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_subnet_expression;
数据格式: 10.136.17.35/24
算数运算: 不支持
比较运算: 仅支持==和!=
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 支持,使用||操作符
成员是否存在: 支持,使用in或!in,用于判断IP是否属于该子网
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.9 基本数据类型—时间类型—time类型
time类型相关总结请参考下表:
time
简要说明: 表示时间戳,Bro自定义数据类型之一
显式声明及初始化: scope name: time; name = return_time_expression;
scope name: time = return_time_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_time_expression;
数据格式: 无法直接初始化,需要借助current_time()或network_time()函数
算数运算: 支持,注意time类型相减返回interval类型,注意time类型和interval类型的加减法
比较运算: 支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数: current_time()  # 返回操作系统当前时间
network_time()  # 返回最后一个已处理数据包内记录的时间,未曾处理数据包则返回当前时间
strftime("%Y-%m-%d %H:%M:%S", current_time())  # 将time转换为可读字符串,注意时区
4.2.10 基本数据类型—时间类型—interval类型
interval类型相关总结请参考下表:
interval
简要说明: 表示相对时间,Bro自定义数据类型之一
显式声明及初始化: scope name: interval; name = return_interval_expression;
scope name: interval = return_interval_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_interval_expression;
数据格式: 数字+0或1个空格+单位
数字支持整数、浮点数、负数
单位支持:微秒(usec)、毫秒(msec)、秒(sec)、分( min)、时(hr)和天(day)
单位单数复数均可,即:print 1hr == 1 hrs;  # return T
算数运算: 支持,注意time类型相减返回interval类型,注意time类型和interval类型的加减法
比较运算: 支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 容器类型操作,不具备
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
4.2.11 基本数据类型—时间类型—pattern类型
pattern类型相关总结请参考下表:
pattern
简要说明: 表示正则表达式,用于对文本快速检索,具备容器类型的部分特征,Bro自定义数据类型之一
显式声明及初始化: scope name: pattern; name = return_pattern_expression;
scope name: pattern = return_pattern_expression;  # 相比隐式声明,可读性和可维护性更好
隐式声明及初始化: scope name = return_pattern_expression;
数据格式: /regular expression/
算数运算: 不支持
比较运算: 仅支持文本完整匹配,使用==或!=操作符
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 容器类型操作,不具备
成员是否存在: 支持文本嵌入式匹配,使用in或!in,正则表达式必须位于左侧
索引成员: 容器类型操作,不具备
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数: split(str, my_pattern)  # 返回数字索引的table,索引由1开始,有序保存分割后的字符串
4.2.12 容器类型—set类型
set类型相关总结请参考下表:
set
简要说明: 表示成员唯一的集合,类似于python中的set,但要求成员类型一致
显式声明及初始化: scope name: set[int]; name = set(1, 2, 3);
scope name: set[int] = set(1, 2, 3);  # 可读性和可维护性更好
隐式声明及初始化: scope name = set(1, 2, 3);
数据格式:  
算数运算: 不支持
比较运算: 不支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 支持,使用add关键字:local s = set(1, 2); add s[3]; print s;  # return {1, 2, 3}
删除成员: 支持,使用delete关键字,用法同add
成员数量: 支持,使用||操作符
成员是否存在: 支持,使用in或!in
索引成员: 不支持
分片: 不支持
遍历操作: 支持,使用for (...in...)
內建函数:
4.2.13 容器类型—table类型
table类型相关总结请参考下表:
table
简要说明: 表示key-value间的映射,key唯一,类似python的dict,但key类型一致且value类型一致
显式声明及初始化: scope name: table[string] of string; name = table(["a"]="b", ["c"]="b");
scope name: table[string] of string =table(["a"]="b", ["c"]="b");  # 可读性和可维护性更好
隐式声明及初始化: scope name = table(["a"]="b", ["c"]="b");
数据格式: 支持多个基本数据类型组合作为key,如:local t: table[int, string] of int = table([1, "2"]=3);
后续初始化和赋值时需要注意key需要遵循声明时的格式
算数运算: 不支持
比较运算: 不支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 支持,使用赋值语句即可
删除成员: 支持,使用delete关键字,用法同set
成员数量: 支持,使用||操作符
成员是否存在: 支持,使用in或!in,注意,仅针对key进行检测,如:
local t = table([1, "2"] = 3); print [1, "2"] in t;  # return T
索引成员: 支持
分片: 不支持
遍历操作: 支持,使用for (...in...),注意,仅针对key进行遍历,对组合key的遍历只能整体遍历,如:
for ([i, j] in table([1, "2"] = 3)) {
    print i;
}
# return 1
內建函数:
4.2.14 容器类型—vector类型
vector类型相关总结请参考下表:
vector
简要说明: 表示有序数组,类似python的list,但要求成员类型一致
显式声明及初始化: scope name: vector of int; name = vector(1, 1, 2);
scope name: vector of int = vector(1, 1, 2);  # 可读性和可维护性更好
隐式声明及初始化: scope name =  vector(1, 1, 2);
数据格式: 索引由0开始递增,连续不间断
算数运算: 不支持
比较运算: 不支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 支持,使用赋值语句即可,注意索引连续
删除成员: 不支持
成员数量: 支持,使用||操作符
成员是否存在: 支持,使用in或!in,注意,仅针对索引进行检测
索引成员: 支持
分片: 不支持
遍历操作: 支持,使用for (...in...),注意,仅针对索引进行遍历
內建函数:
4.2.15 自定义类型—type和record关键字
type关键字可以用于定义数据类型别名,提高代码可读性和可维护性,如:
type string_array: table[count] of string;
type和record关键字结合使用可以用于自定义数据类型,相关总结请参考下表:
自定义数据类型
简要说明: 使用type和record关键字自定义数据类型,类似于C语言的结构体
可以任意定义数据类型中每个成员key和value的数据类型,但使用时必须按定义初始化或赋值
类型自定义 type Service: record {
    name: string;
    ports: set[port];
};
自定义数据类型时允许嵌套包含其他自定义的数据类型
类型定义需在Bro解析阶段完成,即类型定义必须位于可执行代码(event/hook/function)外部
显式声明及初始化: scope name: Service; name = Service($name="dns", $ports=set(53/tcp, 53/udp));
scope name: Service = Service($name="dns", $ports=set(53/tcp, 53/udp));
后者可读性和可维护性更好
初始化时在key前添加$符号
隐式声明及初始化: scope name = Service($name="dns", $ports=set(53/tcp, 53/udp));
数据格式:
算数运算: 不支持
比较运算: 不支持
逻辑运算: 不可自动转换为bool类型,因此不可参与逻辑运算
添加成员: 容器类型操作,不具备
删除成员: 容器类型操作,不具备
成员数量: 支持,使用||操作符
成员是否存在: 容器类型操作,不具备
索引成员: 访问自定义类型数据的成员使用$符号
分片: 容器类型操作,不具备
遍历操作: 容器类型操作,不具备
內建函数:
五、如何自定义日志
  • 日志生成主要涉及Log Stream、Filter和Writer三部分,具体含义如下:

    • Log Stream:定义字段种类、字段类型、日志类型、日志名等日志处理过程中的基本规则,以便用户根据这些要求提供源数据。
    • Filter:对源进行过滤、修改、重定向等处理,默认Filter不进行任何处理。
    • Writer:决定日志输出方式和格式,默认writer将日志输出为tab分隔的文本文件,也支持ES等输出方式。
  • 使用Logging Framework的步骤如下:
    • Bro解析阶段:注册Log Stream ID、自定义数据类型Info描述日志内容
    • Bro运行初始化阶段:创建Log Stream、修改Log Stream的Filter
    • Bro运行阶段:写入Log Steam。
  • Logging Framework可以配置生成自定义的event,用户需要自行声明并初始化log_x event handler,并在其t中进行后置处理,避免了解析文本日志再分析的冗余操作,提高分析实时性。
六、如何触发通知
使用Notice Framework的步骤如下:
  • Bro解析阶段:注册Notice Type。
  • Bro运行阶段:根据Notice::Info生成notice数据,通过NOTICE函数传入Notice Framework。
  • Notice Framework:通过hook修改notice数据并触发相应通知行为(可以在Bro解析阶段定义Notice Policy shortcut简化该步骤)。

2.5 Bro脚本入门相关推荐

  1. 用python写脚本看什么书-终于知晓python编写脚本入门教程

    PyQt5是基于Digia公司强大的图形程式框架Qt5的python接口,由一组python模块构成.PyQt5本身拥有超过620个类和6000函数及方法.在可以运行于多个平台.PyQt5拥有双重协议 ...

  2. Linux Shell脚本入门--wget 命令用法详解

    Linux Shell脚本入门--wget 命令用法详解 wget是在Linux下开发的开放源代码的软件,作者是Hrvoje Niksic,后来被移植到包括Windows在内的各个平台上.它有以下功能 ...

  3. Linux之Shell脚本入门

    一.Shell概述 Shell是一个命令行解释器,它接受应用程序/用户命令,然后调用操作系统内核. Shell还是一个功能强大的编程语言,易编写.易调试.灵活性强. 二.Shell脚本入门 1.脚本格 ...

  4. Linux Shell脚本入门教程系列之(十六) Shell输入输出重定向

    本文是Linux Shell系列教程的第(十六)篇,更多Linux Shell教程请看:Linux Shell系列教程 Shell中的输出和输入的重定向是在使用中经常用到的一个功能,非常实用,今天就为 ...

  5. Linux Shell脚本入门教程系列之(十五) Shell函数简介

    本文是Linux Shell脚本系列教程的第(十五)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 上一篇之后,函数可以将一个复杂功能划分成若干模块,从而使程序结构更加清晰 ...

  6. Linux Shell脚本入门教程系列之(十四) Shell Select教程

    本文是Linux Shell脚本系列教程的第(十四)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 在上一篇文章:Linux Shell系列教程之(十三)Shell分支语句 ...

  7. Linux Shell脚本入门教程系列之(十三)Shell分支语句case … esac教程

    本文是Linux Shell脚本系列教程的第(十三)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 上一篇之 后,分支语句非常实用,基本上高级语言都支持分支语句(pytho ...

  8. Linux Shell脚本入门教程系列之(十二)Shell until循环

    本文是Linux Shell脚本系列教程的第(十二)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 在上两篇文章Linux Shell系列教程之(十)Shell for循环 ...

  9. Linux Shell脚本入门教程系列之(十一)Shell while循环

    本文是Linux Shell脚本系列教程的第(十一)篇,更多Linux Shell教程请看:Linux Shell脚本系列教程 在上一篇Linux Shell系列教程之(十)Shell for循环中, ...

最新文章

  1. Pycharm快捷键及一些常用设置
  2. TensorFlowSharp入门使用C#编写TensorFlow人工智能应用
  3. 使用Spring MVC HandlerExceptionResolver处理异常
  4. gevent.hub.LoopExit: ('This operation would block forever'
  5. view里文书删除时报错的解决案
  6. java的时间变化_通过java记录数据持续变化时间代码解析
  7. Redis的session管理和Memcached的session管理不同
  8. 基于POI的读写Excel文件的工具类
  9. 【渝粤教育】国家开放大学2019年春季 2508学前儿童语言教育 参考试题
  10. Java Thread类最终同步的void join(long time_in_ms)方法,带有示例
  11. android 引用非 android 工程,Unity3D调用android方法(非插件方式)
  12. 火狐浏览器如何更改字体 火狐浏览器字体更改方法分享
  13. 使用Python往Elasticsearch插入数据
  14. 开源计算机集群监控Ganglia应用视频
  15. The content of element type configuration must match (properties?,settings?,typeAliases?,typeHand...
  16. window下Jekyll+github搭建自己的博客
  17. 《CUDA C编程权威指南》——2.4节设备管理
  18. socket通信问题
  19. 修改Android模拟器存储位置,更改AndroidAVD模拟器创建路径位置的方法
  20. 【SAMMY】DOS下操作隐藏文件、文件夹

热门文章

  1. Android 进阶17:Fragment FragmentManager FragmentTransaction 深入理解
  2. 苹果手机文件转换html,手机html文件怎么打开?1分钟打开苹果html文件
  3. 如何在万米高空畅享5G?
  4. java 获取对象属性值为空或者非空的属性名称
  5. Linux软件包管理-rpm、yum
  6. 山东电网计算机专业录取率,国家电网太难进?网友:这个二本高校进山东电网的人数仅次于山大...
  7. 再谈Android的许可证
  8. 苹果手机来电铃声设置方法
  9. ajax动态加载div,JQuery/AJAX:使用动态内容加载外部DIV使用动态内容
  10. hdu 4481 Time travel(高斯求期望)