昨天升级 Ubuntu ,不知为何将网卡配置覆盖了,导致一时间无法上网,然后看了一些网络配置的文章,感觉自己需要补习一下相关知识,所以有了这篇文章。
下面就按照命令分别展开吧
这个命令是查看本地网络端口最常见的命令了,略
/etc/network/interfaces
文件中保存着本地网络网卡的相关配置
配置 DHCP 自动获取 IP
auto eth0
iface eth0 inet dhcp
假如要配置静态 IP
auto eth0 # 要设置的网卡
iface eth0 inet static # 设置静态 IP;如果是使用自动 IP 用 dhcp,后面的不用设置,一般少用
address xxx.xxx.xxx.xxx # IP 地址
netmask xxx.xxx.xxx.xxx # 子网掩码
gateway xxx.xxx.xxx.xxx # 网关
修改保存,之后使用 sudo /etc/init.d/networking restart
来使其生效。
DNS 相关的配置在 /etc/resolv.conf
文件中。如果希望永久生效可以修改 /etc/resolvconf/resolv.conf.d/base
文件中
nameserver 8.8.8.8 # 希望修改成的 DNS
nameserver 8.8.4.4
然后使用 sudo /etc/init.d/resolvconf restart
来使得 DNS 生效。
/etc/resolv.conf
配置文件是客户端 DNS 配置,一般在该文件中配置了 DNS 服务器的 IP 地址和域名。
配置的参数格式非常简单,由关键字开头,后面接着是空格分隔的几个参数。resolv.conf
配置中主要的关键字有四个:
一个基本的配置:
domain some-example.com
nameserver 8.8.8.8
nameserver 8.8.4.4
search exmaple.com example1.com
解释:
example.com
时,在 DNS 解析的时候,无法对输入解析的时候,比如查询 blog,DNS 客户端会使用 search 指定的值加上需要查询的名称,即 blog.example.com
来进行解析,解析失败的时候会依次往后 blog.example1.com 查询当设定了 domain 时,配置的地址会自动成为 search
的第一个搜索域名。
当去 ping
一个域名时,如果访问的域名无法被 DNS 解析,resolver 会将该域名加上 search 参数后面配置的内容,重新请求 DNS,知道被正确解析或尝试完 search 指定的所有列表为止。
高级加密标准 (AES,Advanced Encryption Standard) 为最常见的对称加密算法。对称加密算法也就是加密和解密用相同的密钥。
将明文 P 使用加密密钥 K 加密成密文 C ,传输,然后在使用 AES 解密函数使用相同的密钥 K 解密,产生明文 P
设 AES 加密函数为 E,则 C = E(K, P)
, 其中 P 为明文,K 为密钥,C 为密文。也就是说,把明文 P 和密钥 K 作为加密函数的参数输入,则加密函数 E 会输出密文 C。
加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。
AES 为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。
Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。
pip install alembic
初始化,使用如下命令会创建环境到 migrations
文件夹下,通常情况下使用 migrations
文件夹来存储 alembic 环境,如果想使用别的名字,相应替换为别的名字即可。注意下面命令中的 migrations 将会是存储所有迁移脚本的目录名字
alembic init migrations
初始化过程会创建迁移环境和 alembic.ini
文件。创建成功后可以看到如下结构:
alembic
├── README
├── env.py
├── script.py.mako
└── versions
alembic.ini
在这个环境中可以找到 env.py
和 script.py.mako
模板还有 versions 文件夹。versions/
目录会存储之后的所有迁移脚本。 env.py
文件用来定义和实例化 SQLAlchemy 引擎,连接引擎并进行事务,保证当 Alembic 执行命令时被合理的调用。 script.py.mako
模板在创建迁移时被使用,他定义了迁移的基本模板。
在 init
生成之后需要修改 env.py
如下的两个配置,才能生效。改变 sqlalchemy.url
值,配置数据库连接。
sqlalchemy.url = driver://user:pass@localhost/dbname
为了让 Alembic 追踪到数据模型的变化,需要将 target_metadata
赋值为数据库的 metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
使用alembic revision -m "comment"
来创建数据库版本。命令会产生一个数据库迁移脚本。
升级数据库使用 alembic upgrade
,降级使用 alembic downgrade
,更新到最新版则使用 alembic upgrade head
。
查看数据库就会发现 alembic 会自动产生一个 alembic_version
的表,只有一个字段和值 version_num
,记录当前数据库版本。
Android 中实现消息推送的主流方案有下面几种。
方案 | 原理 | 优点 | 缺点 |
---|---|---|---|
Cloud to Device Messaging,云端推送,是 Android 系统级别的消息推送服务(Google 出品) | Push | 简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据 | 依赖于 Google 官方提供的 C2DM 服务器,需要用户手机安装 Google 服务 |
轮询 | 基于 Pull 方式 | 实时性好 | 成本大,需要自己实现与服务器之间的通信 ; 到达率不确定,考虑轮询的频率:太低可能导致消息的延迟;太高,更费资源 |
SMS 信令推送 | Push | 完全的实时操作 | 成本高 |
第三方平台 | Push 小米推送、华为推送 友盟推送、极光推送、云巴(基于 MQTT) 阿里云移动推送、腾讯信鸽推送、百度云推送 | 成本低,抵达率高 | 安全性低,服务会被杀 |
iotop 是一个用来监控磁盘 I/O 的类似 top 的工具,Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20. (使用 python -V
和 uname -r
来查看)
使用这个命令最主要的一个原因就是快速找到 IO 占用比较高的程序,以便进行排查。
sudo apt install iotop
sudo yum install iotop
wget http://guichaz.free.fr/iotop/files/iotop-0.6.tar.bz2
tar -xjvf iotop-0.6.tar.bz2
cd iotop-0.6/
./setup.py install
直接使用,可以查看到对应进程的 IO 磁盘读写信息
iotop
只显示有实际 I/O 的进程或者线程
iotop -o
只显示进程的 I/O 数据:
iotop -P
显示某一个 PID 的 IO:
iotop -p PID
显示某一个用户的 I/O:
iotop -u USER
显示累计 IO 数据:
iotop -a
调整刷新时间为 10 秒:
iotop -d 10
直接启动 iotop
会进入交互模式,使用如下快捷键可以控制显示。
a
用来切换累积使用量和 IO 读写速率。r
改变排序顺序o
只显示有 IO 输出的进程q
退出InfluxDB 数据库是用 Go 语言实现的一个开源分布式时序、事件和指标数据库。InfluxDB 提供类 SQL 语法。
需要注意的是 InfluxDB 单节点是免费的,但是集群版是要收费的。
sudo apt install influxdb
正因为 InfluxDB 是一个时序数据库,在实际使用的时候有些概念需要提前知道。InfluxDB 数据库中的每一个数据都有一列 time
保存时间戳 (RFC3339 形式显示)。
time | butterflies | honeybees | location | scientist |
---|---|---|---|---|
2015-08-18T00:00:00Z | 12 | 23 | 1 | langstroth |
2015-08-18T00:00:00Z | 1 | 30 | 1 | perpetua |
以该数据说明,butterflies
和 honeybees
是 Field keys
用来保存元数据,每一个 key 都对应这 value,下面的数字都是要存储的值。
location
和 scientist
是 Tags
,用来存储元数据。location 有两种取值,scientist 也有两种取值。所以组合可以有四种 tag 集合。tag 是可选的。但是推荐给数据加上 tags。和 field 不同,tags 都是索引,这意味着在查询时可以更快。
measurement
可以理解成 tags, fields 和 time 列的容器。measurement 的名字是数据的描述,和关系型数据库中的表可以做对应。在 InfluxDB 中可以创建多个数据库,不同数据库中的数据文件是隔离存放的,存放在磁盘上的不同目录
每一个 measurement
可以属于不同的 retention policies
(存储策略),一个 retention policy
描述了 InfluxDB 保存数据多久(DURATION)和多少副本被保存到 cluster 中(REPLICATION)。(Replication factors 不对单节点开放)
在 InfluxDB 中 series
是一组数据的集合,这些数据共享一个 retention policy
,measurement
和 tag set
。理解 series 的概念才能设计好数据库的 schema。
最后 point
是在相同 series
中相同 timestamp 的数据。
如果需要使用 shell 需要安装
sudo apt install influxdb-client
然后在终端输入
influx
进入,默认的端口是 8086。输入 help
可以查看命令列表。
influx 启动命令常用参数
influx -database 'name' # 数据库
influx -host 'hostname' # 默认是 localhost
influx -password 'password' # 如果有密码
influx -port 'port' # 端口
更多参数可以使用 influx --help
查看。
查看数据库
show databases
创建和删除数据库
create database test
drop database test
切换使用数据库
use test
显示所有表
show measurements
删除表
drop measurement test_table
插入数据
insert [measurement],[tag],[field],[time]
insert cpu,host=serverA,regin=us value=0.64
解释:
host
和 region
是 tagvalue
是 0.64这里还要注意 measurement 和 tag 之间不能有空格,空格后面是真正要存储的值,所以如果要存两个 field,那么就需要
insert temperature,machine=unit42,tpye=assembly external=25,internal=37
注意 type=assembly 和 external 之间的空格。
查询数据
select "host","region","value" from "cpu"
select * from "temperature"
更多的查询可以参考这里
查看用户
show users
创建用户
create user "username" with password "password"
create user "username" with password "password" with all privileges
删除用户
drop user "username"
安装下面的客户端
pip install influxdb
其实这个 client 也只不过是在 influxdb 提供的 HTTP 接口外封装了一层 API,如果熟悉 HTTP 的接口,可以直接调用 influxdb 提供的接口。
curl -sL -I localhost:8086/ping
比如:
import requests
posturl = 'http://localhost:8086/query'
data = {'q': 'create DATABASE mydb'}
response = requests.post(posturl, data=data)
类似于 curl 命令如下:
curl -GET http://localhost:8086/query --data-urlencode "q=CREATE DATABASE mydb"
import requests
posturl = 'http://localhost:8086/write?db=mydb'
data="cpu_load_short,host=server01,region=us-west value=0.69"
response = requests.post(posturl, data=data)
类似于 curl 命令如下:
curl -i -XPOST 'http://localhost:8086/write?db=mydb' --data-binary 'cpu_load_short,host=server01,region=us-west value=0.64 '
如果使用 python client:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import psutil
import time
from influxdb import InfluxDBClient
host = "localhost"
port = 8086
user = "root"
password = "root"
dbname = "test"
def read_info():
cpu_time_info = psutil.cpu_percent(1, True)
data_list = [
{
'measurement': 'cpu',
'tags': {
'cpu': 'i7'
},
'fields': {
'cpu_info_user': cpu_time_info[0],
'cpu_info_system': cpu_time_info[1],
'cpu_info_idle': cpu_time_info[2],
'cpu_info_interrupt': cpu_time_info[3],
'cpu_info_dpc': cpu_time_info[4]
}
}
]
return data_list
def parse_db(dbs):
l = []
for db in dbs:
l.append(db['name'])
return l
if __name__ == '__main__':
client = InfluxDBClient(host, port, user, password) # 初始化
dbs = client.get_list_database()
if dbname not in parse_db(dbs):
client.create_database(dbname)
for i in range(20):
client.write_points(read_info())
time.sleep(2)
大致是这样,但是这个 client 似乎还有 bug,在使用的时候就发现 HTTP 方法不支持,Method Not Allowed 这样的错误,需要自己手动改一改。
记录一下反编译一周 CP Android 3.26.0.451 版本,并拿到请求 sign 加密方法的过程。反编译的过程基本上可以划分为几个步骤
关于第一步 Android 反编译的教程和工具 可以参考之前的文章。
在反编译得到混淆过后的代码之后,这个时候不能盲目的去看,之前可以抓包看下应用内发出去的请求 path,找到关注的 path,比如在这里,找到一个请求的 path,然后沿着这个请求的 path 找到了发起请求的通用方法 prepareRequest()
,看到这里就能清晰的看到请求发出去的时候带的几个通用参数 user_id
, sm_device_id
,然后签名部分 timestamp
,Token
,sign
,最关键的部分就是这里的 sign
的生成方式。
然后到这里看到生成 sign 的方法,第一眼看过去就看到了希望 SHA-1
他使用的是这个哈希算法,这个算法也主要就是用于签名校验。
然后可以沿着这个思路去看各个参数的值,比如这里他用到了 RequestData.buildQueryString()
这个方法
大致看一下这个代码大概能猜到是将请求参数的 key value 拼接起来连成字符串返回。这个时候大致思路已经清晰,所以我用 Java 大致实现了一下 sign 生成的代码。
import com.jutils.base.RequestData;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class WeekCPTest {
private static String sha1(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest instance = MessageDigest.getInstance("SHA-1");
byte[] bytes = str.getBytes("UTF-8");
instance.update(bytes, 0, bytes.length);
return O000000o(instance.digest());
}
private static String O000000o(byte[] bArr) {
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bArr) {
int i = (b >>> 4) & 15;
int i2 = 0;
while (true) {
char c = (i < 0 || i > 9) ? (char) ((i - 10) + 97) : (char) (i + 48);
stringBuilder.append(c);
int i3 = b & 15;
i = i2 + 1;
if (i2 >= 1) {
break;
}
i2 = i;
i = i3;
}
}
return stringBuilder.toString();
}
public static String makeSign(Map<String, Object> map, Map<String, Object> map2, String str) {
Map treeMap = new TreeMap();
if (!(map == null || map.isEmpty())) {
treeMap.putAll(map);
}
if (!(map2 == null || map2.isEmpty())) {
treeMap.putAll(map2);
}
treeMap.remove("token");
treeMap.remove("sign");
try {
return sha1(RequestData.buildQueryString(treeMap, null, false) + str);
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
public static void main(String[] args) {
HashMap<String, Object> query = new HashMap<>();
query.put("d", "a");
query.put("user_id", "5624198");
query.put("timestamp", "1533102328");
query.put("sm_device_id", "201807021511598922958a5fbc8a5de09cb9019d34a48b01c51f66d9435c31");
// query.put("Token", "1576511275532288_5624198_1559013109_58f16d8bd82677acc30d87542f5504b0");
query.put("start", "80");
query.put("last_object_id", "1742");
query.put("keyword", "");
query.put("num", "20");
String s = makeSign(query, new HashMap<>(), "025d25f5a69eb2818b6811ff6edb51b4");
System.out.println(s);
}
}
然后使用抓包工具 抓出一个请求,将参数凭借起来使用这个 Test 跑一下,发现是一致的。这个时候再使用 Python 实现一下。
def __sha1(self, str):
m = hashlib.sha1()
m.update(str)
return m.hexdigest()
def __sign(self, params, secret_key, data={}):
"""
通过请求参数和 secret_key 生成 sign
:param params: 请求参数
:param secret_key: 加密 key 和 用户绑定,在登录或者刷新 token 中获取
:return:
"""
to_sign = ""
merged_dict = params.copy()
merged_dict.update(data)
if "sign" in merged_dict.keys():
merged_dict.pop("sign")
if "token" in merged_dict.keys():
merged_dict.pop("token")
for key in sorted(merged_dict.iterkeys()):
value = merged_dict.get(key)
logger.info("params %s %s" % (key, value))
pair = str(key) + "=" + str(merged_dict.get(key))
if to_sign == '':
to_sign = to_sign + pair
else:
to_sign = to_sign + '&' + pair
return self.__sha1(to_sign + secret_key)
然后这个时候就发现其实破解一周 CP 的难点不在 sign 而在拿到 secret_key,这个 secret_key 只有在登录和刷新 token 的接口中才会返回。
HTTP 请求的 Header 是不区分大小写的!,一直以为 HTTP 请求的请求头是有区分大小的,知道今天调试发现 Spring 将 header 全部处理成小写,然后有人提了 Bug 58464 然后看到 Stackoverflow 上面有人回答。
HTTP/1.1 和 HTTP/2 都是 case-insensitivt
都是不区分大小写的。
今天在修改 hostname 使用 sudo hostnamectl set-hostname ds
命令时遇到问题:
Failed to create bus connection: No such file or directory
查了一通之后发现缺少 dbus
sudo apt-get install dbus
安装 dbus 然后再修改即可,使用 hostnamectl
方式来修改 hostname 不需要重启,直接推出登录,然后就可以实现了。
D-Bus 是一种高级的进程间通信机制,它由 freedesktop.org 项目提供,使用 GPL 许可证发行。D-Bus 最主要的用途是在 Linux 桌面环境为进程提供通信,同时能将 Linux 桌面环境和 Linux 内核事件作为消息传递到进程。D-Bus 的主要概率为总线,注册后的进程可通过总线接收或传递消息,进程也可注册后等待内核事件响应,例如等待网络状态的转变或者计算机发出关机指令。目前,D-Bus 已被大多数 Linux 发行版所采用,开发者可使用 D-Bus 实现各种复杂的进程间通信任务。