0%

找到C:\Program Files\Sublime Text 3\Packages\C++.sublime-package
复制到一个有写权限的目录,重命名为C++.sublime-package.zip
打开压缩包找到C Single File.sublime-buildC++ Single File.sublime-build

分别对应的C和C的单文件构建命令,以C为例,打开编辑,找到其中的

1
"shell_cmd": "g++ ***"

有两条,应该一条是编译一条是编译并运行,分别增加需要的编译参数即可,比如在g++的后面加上--std=c++17
把修改更新进压缩包,重命名回C++.sublime-package并覆盖回原来的位置即可。

使用gdb启动程序

1
gdb <可执行文件名>

启动后执行运行命令run可以简写为r

程序就会从入口开始运行,也可以带上命令行参数运行run [参数1] [参数2] ...

参看当前函数调用堆栈

1
backtrace

可以简写为bt,在程序崩溃后查看当前崩溃的位置和调用堆栈,这估计是gdb最常用的命令。

GDB常用的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
i b  // info breakpoint
b 10 // set breakpoint at line 10
b main.cpp:10 // set breakpoint at line 10 in file main.cpp
b func // set breakpoint at function "func"
b A::A // 设置成员函数的断点
b func(int) // 设置函数断点时指定函数类型,以便在有重载时区分
c // continue
s // 单步执行并在函数调用时进入函数 step in
n // 但不是执行并在函数调用时不进入函数而当成一步完成 step over
finish // 执行完当前函数跳到上层调用处 step out
p x // print the value of x
i ar // info args 查看函数参数的值
i lo // info locals 查看所有局部变量的值
bt // backtrace 查看调用栈
f 1 // 设置栈针到level 1,断点停的地方是level 0,level 1就是上一层,根据bt返回的结果设置,切换栈针后再用i ar和i lo查看当前栈针的变量信息
i thr // info thread 查看线程信息
thr 2 // thread 2 切换当前线程到id:2,根据i thr的返回结果设置
q // quit GDB

配置core dump

如果程序不是通过gdb启动运行的我们也想查看它崩溃时的调用堆栈,则可以通过core dump文件,它会保留崩溃时的现场。首先我们确保运行的可执行文件无论是Debug版还是Release版应该携带了调试符号,即编译选项中加入了-g或-ggdb。然后通过下面的方式启用core dump文件生成:

确保apportsystemd-coredump两个服务存在

1
2
dpkg -l | grep apport
dpkg -l | grep systemd-coredump

如果不存在则安装

1
2
apt install apport 
apt install systemd-coredump

如果存在则确保在运行

1
2
systemctl status apport
systemctl status systemd-coredump.socket

如果没有运行则启动

1
2
systemctl start apport
systemctl start systemd-coredump.socket

默认的core dump文件会生成在/var/lib/systemd/coredump/目录下,为了方便调试我们修改到可执行文件同目录

运行程序前在shell窗口或者shell脚本内先执行

1
2
ulimit -c unlimited
echo "core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern

之后运行的程序崩溃时就会在程序启动的“当前目录”生成core dump文件。文件名是core.<可执行文件名>.<进程ID>

使用core dump

所谓使用core dump,就是通过gdb和core dump文件恢复崩溃时的现场,以便查看崩溃时的函数调用堆栈或者变量值。

用法:

1
gdb <可执行文件名> <core dump文件名>

然后就可以使用

1
bt

查看崩溃现场的调用堆栈了。

主动生成运行中进程的core dump

这种需求主要发生在进程卡死,想知道卡在何处时。

1
gcore -o <core dump文件名> <pid>

可以主动生成core dump文件,这个操作不会杀死进程,如果有需要可手动杀死。

然后参照[使用core dump](#使用core dump)的步骤去查看调用堆栈就可以了

C++11引入了新的随机数生成器mt19937

mt是因为这个伪随机数产生器基于Mersenne Twister算法。
19937是因为产生随的机数的周期长,可达到2^19937-1

虽然周期很长,但这是在种子固定后依次生成2^19937-1个数才能保证不重复,实际的应用是需要重启或者多开的,每次重启种子会重设,迭代次数又重新开始,如果我们把种子设成固定值显然每次生成的序列和前一次都会是重复的,如果我们用时间作为种子,这样可以让每次的序列都从一个随机位置开始迭代,但是这样也就无法保证本次周期内生成的数不会和之前的某次生成的重复,或许不巧的情况下生成了10个就重复了,也是有极小概率的。

为了解决这个问题,我们可以更进一步的,在用时间作为随机种子的基础上再在前面拼接当前时间,比如拼接“%Y%m%d%H%M%S”精确到秒,因为这串时间必然是递增的,即使后面不巧重复了,拼接后的整体也是唯一的,因此只要确保在一秒内没有重复即可,而在一秒内除非程序重启,否则由mt19937的周期保证不会重复。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <chrono>
#include <sstream>
#include <random>
#include <mutex>
#include <iomanip>

inline
std::string gen_uuid() {
static std::mutex s_mutex;
std::lock_guard lock(s_mutex);
static std::mt19937 seed_engine(
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock().now().time_since_epoch()).count()
);
std::stringstream ss;
int64_t t = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock().now().time_since_epoch()).count();
auto tm = std::localtime(&t);
std::uniform_int_distribution<std::size_t> random_gen;
std::size_t value = random_gen(seed_engine);
ss << std::put_time(tm, "%Y%m%d%H%M%S") << std::hex << std::setw(16) << std::setfill('0') << value;
return ss.str();
}

注意到函数整体用mutex加了锁,首先我不确定random_gen(seed_engine)是否线程安全,其次我确定std::localtime不是线程安全的,因为其返回一个std::tm结构体的指针,可能是指向std::localtime内的静态成员变量,我不清楚为什么STL这样设计这个函数。

进一步改进方式,如果考虑到程序多开,可以给每个程序一个ID,在生成时传递gen_uuid(app_id)然后拼接到生成的随机字符串中。

Python版本,后面通过{:016x}截断16位16进制数是为了和C++生成的保持长度一致

1
2
3
4
5
6
7

import datetime
import uuid

def gen_uuid():
return "{}{:016x}".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S"), (uuid.uuid4().int&(0xFFFFFFFFFFFFFFFF)) )

Kafka在默认配置下,保证消息至少被消费一次,即不漏,但不保证不重,下面代码实现了去重,以及从任意时间订阅历史消息。

utils/kafka-util.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# -*- coding: utf-8 -*-
import kafka
import time
import datetime
import uuid
from queue import Queue


class KafkaProducer(kafka.KafkaProducer):
def __init__(self, server):
super().__init__(
bootstrap_servers=[server],
api_version=(2,3,1),
api_version_auto_timeout_ms=5000
)

# topic可以认为是消息的信道,和subscribe时对应
# key并没有明确的意义,可以根据需要自定义,例如表示消息的类型在后面再根据key进行不同的处理,kafka底层保证相同key的消息在kafka集群时会在相同的分区上处理,从而保证相同key的消息的有序性
def send(self, topic, key, data):
assert(isinstance(data, bytes))
# 用时间+UUID拼成32位唯一值用于去重和在调试时查看发送时间
timestamp = "{}{}".format(datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:-3], uuid.uuid4().hex[:15])
timestamp = timestamp[:32]
value = timestamp.encode() + data
return super().send(topic, key=key.encode(), value=value)


class KafkaConsumer(kafka.KafkaConsumer):
def __init__(self, server, group_id=None):
super().__init__(
bootstrap_servers=[server],
group_id=group_id,
auto_offset_reset='latest', # earliest, latest
enable_auto_commit=True,
api_version=(2,3,1),
api_version_auto_timeout_ms=5000
)
self.server = server
self.q = Queue()
self.s = set()
self.DUP_DETECT_SIZE = 1000 # 去重检测大小

# 从最新消息开始订阅
def subscribe(self, topic):
if topic not in super().topics():
# 创建一个临时的producer发送一个空消息的以便把topic创建出来,key是空的,consumer会丢弃掉
# 这并不是必要的,我觉得可能是一个BUG,先订阅一个不存在的topic,之后再send消息到这topic上,consumer可能收不到
# 后续版本可能已经修复了,不清楚是kafka-python的还是kafka的
producer = kafka.KafkaProducer(
bootstrap_servers=[self.server],
api_version=(2,3,1),
api_version_auto_timeout_ms=5000
)
producer.send(topic, key=b"", value=b"")
producer.flush()
super().subscribe(topic)

# 实现从一个历史时间点进行消息订阅(能订阅到的消息取决于Kafka服务器配置的保留策略,基于目前的配置可以保证72小时内的消息可以重复消费)
def subscribe_from_datetime(self, topic, dt):
if topic not in super().topics():
# 创建一个临时的producer发送一个空消息的以便把topic创建出来,key是空的,consumer会丢弃掉
producer = kafka.KafkaProducer(
bootstrap_servers=[self.server],
api_version=(2,3,1),
api_version_auto_timeout_ms=5000
)
producer.send(topic, key=b"", value=b"")
producer.flush()
if type(dt) is int or type(dt) is float:
ts = dt
elif isinstance(dt, datetime.datetime):
ts = dt.timestamp()
else:
ts = time.mktime(time.strptime(f"{dt}", r"%Y-%m-%d %H:%M:%S"))
offset = self._get_offset_for_time(topic, ts)
partition = 0
tp = kafka.TopicPartition(topic, partition)
super().assign([tp])
super().seek(tp, offset)

def __iter__(self):
return self

# 重新封装阻塞取消息方式,增加去重
# 迭代的元素类型是三元组(key, data, timestamp)
def __next__(self):
while True:
message = super().__next__()
msg_type = message.key
if not msg_type or len(message.value) < 32:
continue
timestamp = message.value[:32].decode()
if timestamp in self.s: # 重复的消息
continue
if len(self.s) >= self.DUP_DETECT_SIZE:
e = self.q.get()
self.s.remove(e)
self.s.add(timestamp)
self.q.put(timestamp)

data = message.value[32:]
return (msg_type.decode(), data, timestamp)

# 重新封装非阻塞取消息方式,增加去重
# 返回三元组(key, data, timestamp)构成的list
def get_messages(self, max_records=20):
r = super().poll(timeout_ms=max_records*25, max_records=max_records)
ret = []
for messages in r.values():
for message in messages:
msg_type = message.key
if not msg_type or len(message.value) < 32:
continue
timestamp = message.value[:32].decode()
if timestamp in self.s: # 重复的消息
continue
if len(self.s) >= self.DUP_DETECT_SIZE:
e = self.q.get()
self.s.remove(e)
self.s.add(timestamp)
self.q.put(timestamp)

data = message.value[32:]
ret.append((msg_type.decode(), data, timestamp))
return ret


def _get_latest_offset(self, topic):
partition = 0
tp = kafka.TopicPartition(topic, partition)
super().assign([tp])
off_set_dict = super().end_offsets([tp])
return list(off_set_dict.values())[0]

def _get_offset_for_time(self, topic, ts):
partition = 0
tp = kafka.TopicPartition(topic, partition)
super().assign([tp])
offset_dict = super().offsets_for_times({tp: int(ts*1000)})
offset = list(offset_dict.values())[0]
if offset is None:
return self.get_latest_offset(topic)
else:
return offset.offset

使用时可以配合json或msgpack之类的序列化方式,如果使用json,下面的jsonable函数可能会对你有用,它可以让日期、时间、自定义类型对象支持通过json序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import time
import datetime
from utils.kafka_util import KafkaProducer
import json
import decimal

def to_jsonable(o):
if o is None:
return o
if isinstance(o, int) or isinstance(o, float) or isinstance(o, str) or isinstance(o, bool):
return o
if isinstance(o, list) or isinstance(o, tuple) or isinstance(o, set):
return [to_jsonable(e) for e in o]
if isinstance(o, dict):
return {k: to_jsonable(v) for (k,v) in o.items()}
if isinstance(o, datetime.datetime):
return o.strftime('%Y-%m-%d %H:%M:%S')
if isinstance(o, datetime.date):
return o.strftime('%Y-%m-%d')
if isinstance(o, datetime.time):
return o.strftime('%H:%M:%S')
if isinstance(o, decimal.Decimal):
return float(o)
try:
return { k: to_jsonable(v) for k,v in vars(o).items()}
except Exception as e:
return o

producer = KafkaProducer(server="192.168.1.99")

class A:
def __init__(self):
self.x = 1
self.y = 3.14

class B:
def __init__(self):
self.x = A()
self.y = datetime.datetime.now()

producer.send("topic-test", "obj", json.dumps(to_jsonable(B())).encode() )
time.sleep(1)

如果在key中记录发送对象的类型,则接收时就可以想办法进行还原

1
2
3
4
5
6
7
8
9
10
11
12
13
from utils.kafka_util import KafkaConsumer
import time
import json


consumer = KafkaConsumer(server="192.168.1.99")
consumer.subscribe("topic-test")
while True:
messages = consumer.get_messages()
for (key, data, timestamp) in messages:
print(key, json.loads(data), timestamp)
time.sleep(0.1)

最常用的编码转换就是GB2312 -> UTF-8的转换了,GB2312是简体中文Windows的默认编码,在记事本另存为时选择的ANSI就是GB2312编码(ANSI在不同版本的操作系统中指代不同编码,仅简体中文系统中表示GB2312),GBK是GB2312的超集,GB18030是GBK的超集,相比GB2312扩充的内容包括繁体字、日韩语中的汉字、少数民族的汉字等不常用汉字。UTF-8是Linux的默认编码,UTF-8和GB2312两种编码都兼容ASCII编码,UTF-8的编码设计更灵活,是变长的编码,理论上可以无限扩充下去,可以把简体字繁体字日文韩文以及未来可能出现的新文字和符号都定义在内。

现在我们只考虑GB2312和UTF-8都能表达的简体中文以及ASCII的部分的相互转换,这也是最常用的转换。

在Linux上我们可以使用iconv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once

#include <string>
#include <iconv.h>

class EncodingConvertor
{
public:
// "UTF-8", "GBK", "GB2312", "GB18030"
EncodingConvertor(const char* toEncoding, const char* fromEncoding);

~EncodingConvertor();

std::string Convert(const char* in, size_t in_len);
std::string Convert(const char* in);
std::string Convert(const std::string& in);

private:
iconv_t _iconv;
};

std::string toUTF8(const std::string& s);
std::string toGB2312(const std::string& s);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include "EncodingConvertor.h"
#include <cstring>

using namespace std;

EncodingConvertor::EncodingConvertor(const char* toEncoding, const char* fromEncoding) {
_iconv = iconv_open(toEncoding, fromEncoding);
}

EncodingConvertor::~EncodingConvertor() {
iconv_close(_iconv);
}


std::string EncodingConvertor::Convert(const char* in, size_t inlen) {
constexpr size_t BLOCK_SIZE = 1024; // iconv可以分块转换,对于特别长的字符串(特别大的文件)
size_t outbuflen = std::max(inlen * 2, BLOCK_SIZE);
const char* pin = in;
char* outbuf = (char*)malloc(outbuflen);
char* pout = outbuf;

int ret = 0;
do {
size_t perlen = BLOCK_SIZE;
if (pout + perlen > outbuf + outbuflen - 1) {
// 如果outbuf后面剩余空间不足一个转换块大小,就重新分配空间
int pout_offset = pout - outbuf;
outbuflen = std::max(outbuflen * 2, BLOCK_SIZE);
outbuf = (char*)realloc(outbuf, outbuflen);
pout = outbuf + pout_offset; // outbuf可能指向新地址,所以要把pout偏移过去
}
ret = iconv(_iconv, (char**)&pin, &inlen, &pout, &perlen);

if (perlen == BLOCK_SIZE) {
// 转移前后perlen没有变化应该是出错了
outbuf[0] = 0;
break;
}
} while (ret == -1);
*pout = 0;
std::string r(outbuf, pout - outbuf);
free(outbuf);
return r;
}

std::string EncodingConvertor::Convert(const char* in) {
return this->Convert(in, strlen(in));
}

std::string EncodingConvertor::Convert(const std::string& in) {
return this->Convert(in.c_str(), in.length());
}

static bool isUTF8(const char* s) {
const unsigned char* rawtext = (const unsigned char*)s;
int i, rawtextlen = 0;
int goodbytes = 0, asciibytes = 0;

// Maybe also use UTF8 Byte Order Mark: EF BB BF

// Check to see if characters fit into acceptable ranges
rawtextlen = strlen(s);
for (i = 0; i < rawtextlen; i++) {
if ((rawtext[i] & 0x7F) == rawtext[i]) { // One byte
asciibytes++;
// Ignore ASCII, can throw off count
} else {
int m_rawInt0 = (int)(unsigned char)(rawtext[i]);
int m_rawInt1 = (int)(unsigned char)(rawtext[i + 1]);
int m_rawInt2 = (int)(unsigned char)(rawtext[i + 2]);

if (256 - 64 <= m_rawInt0 && m_rawInt0 <= 256 - 33 && // Two bytes
i + 1 < rawtextlen &&
256 - 128 <= m_rawInt1 && m_rawInt1 <= 256 - 65) {
goodbytes += 2;
i++;
} else if (256 - 32 <= m_rawInt0 && m_rawInt0 <= 256 - 17 && // Three bytes
i + 2 < rawtextlen &&
256 - 128 <= m_rawInt1 && m_rawInt1 <= 256 - 65 &&
256 - 128 <= m_rawInt2 && m_rawInt2 <= 256 - 65) {
goodbytes += 3;
i += 2;
}
}
}

// 全都是ASCII码
if (asciibytes == rawtextlen) { return true; }

// 非ASCII码的均符合UTF-8规则
return (goodbytes == rawtextlen - asciibytes);
}

std::string toUTF8(const std::string& s) {
if(isUTF8(s.c_str())) return s;
return EncodingConvertor("UTF-8", "GB2312").Convert(s);
}

std::string toGB2312(const std::string& s) {
if(isUTF8(s.c_str())) return EncodingConvertor("GB2312", "UTF-8").Convert(s);
else return s;
}

在Windows可以通过WideCharToMultiByteMultiByteToWideChar两个方法完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
wchar_t * ANSIToUnicode( const char* str ) {
int textlen ;
wchar_t * result;
textlen = MultiByteToWideChar( CP_ACP, 0, str,-1, NULL,0 );
result = (wchar_t *)malloc((textlen+1)*sizeof(wchar_t));
memset(result,0,(textlen+1)*sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0,str,-1,(LPWSTR)result,textlen );
return result;
}

char * UnicodeToANSI( const wchar_t *str ) {
char * result;
int textlen;
// wide char to multi char
textlen = WideCharToMultiByte( CP_ACP, 0, str, -1, NULL, 0, NULL, NULL );
result =(char *)malloc((textlen+1)*sizeof(char));
memset( result, 0, sizeof(char) * ( textlen + 1 ) );
WideCharToMultiByte( CP_ACP, 0, str, -1, result, textlen, NULL, NULL );
return result;
}

wchar_t * UTF8ToUnicode( const char* str ) {
int textlen ;
wchar_t * result;
textlen = MultiByteToWideChar( CP_UTF8, 0, str,-1, NULL,0 );
result = (wchar_t *)malloc((textlen+1)*sizeof(wchar_t));
memset(result,0,(textlen+1)*sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0,str,-1,(LPWSTR)result,textlen );
return result;
}

char * UnicodeToUTF8( const wchar_t *str ) {
char * result;
int textlen;
// wide char to multi char
textlen = WideCharToMultiByte( CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL );
result =(char *)malloc((textlen+1)*sizeof(char));
memset(result, 0, sizeof(char) * ( textlen + 1 ) );
WideCharToMultiByte( CP_UTF8, 0, str, -1, result, textlen, NULL, NULL );
return result;
}

docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo 的地方是它不会将 .md 转成 .html 文件,所有转换工作都是在运行时进行。

需要到docsify-cli来启动web服务器

我在ubuntu18.04的环境下用node v16.19.1 (npm v9.5.1) 可以安装成功,其他环境需要自己试一下

使用nvm安装node

1
nvm install v16

升级npm

1
npm install -g npm@9.5.1

安装docsify-cli

1
npm i docsify-cli -g

初始化文档目录

1
docsify init .

启动web服务器

1
docsify serve .

默认启动在3000端口,可以通过nginx做反向代理

参考

docsify

主页模板

默认生成的index.html不太好用,这里给一个好用的模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>docsify</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0"
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
title="vue"
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/dark.css"
title="dark"
disabled
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/buble.css"
title="buble"
disabled
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/pure.css"
title="pure"
disabled
/>
<link
rel="stylesheet"
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/dolphin.css"
title="dolphin"
disabled
/>
<style>
nav.app-nav li ul {
min-width: 100px;
}

#carbonads {
box-shadow: none !important;
width: auto !important;
}
</style>
</head>

<body>
<div id="app">Loading ...</div>
<script src="//cdn.jsdelivr.net/npm/docsify-plugin-carbon@1"></script>
<script>

// Docsify configuration
window.$docsify = {
auto2top: true,
coverpage: false,
executeScript: true,
loadSidebar: true,
loadNavbar: false,
mergeNavbar: false,
maxLevel: 4,
subMaxLevel: 4,
name: 'docsify',
search: 'auto',
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify@4/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-markdown.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-nginx.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-php.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/vue@2/dist/vue.min.js"></script>
<!-- <script src="//cdn.jsdelivr.net/npm/vue@3/dist/vue.global.prod.js"></script> -->
</body>
</html>

自动生成_sidebar.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# -*- coding: utf-8 -*-
import sys
import os
from functools import cmp_to_key


def mycmp_filename(name1, name2):
if name1.upper() == "README.MD":
return -1
elif name2.upper() == "README.MD":
return 1
elif name1 < name2:
return -1
elif name2 < name1:
return 1
return 0


def gen(e, path, level):
content = ""
name, ext = os.path.splitext(e)
if os.path.isfile(path):
if not name.startswith('_') and ext.lower() == ".md":
if os.path.getsize(path):
content += "{}- [{}]({})\n".format(" "*level, name, path.replace('\\', '/') )
elif os.path.isdir(path):
if name.startswith('_'):
return ""
content2 = ""
for e2 in sorted(os.listdir(path), key=cmp_to_key(mycmp_filename)):
content2 += gen(e2, os.path.join(path, e2), level+1)
if content2:
if name:
content += "{}- {}\n".format(" "*level, name)
content += content2
else:
content = ""
return content


def main():
with open("_sidebar.md", "w", encoding="utf-8") as fp:
fp.write("<!-- docs/_sidebar.md -->\n")
fp.write("{}\n".format(gen("", ".", -1)))

if __name__ == '__main__':
main()

json.loads时报异常

1
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x** in position **: invalid continuation byte

一般是因为编码中有中文,并且和默认的解码方式(utf-8)不匹配造成的,在中国来说通常用最常见的非utf-8编码就是gb2312。(如果你知道里面包含了日语那么则应该尝试按Shift_JIS解码而不是gb2312,等等),另外如果实在解不出,有时候实在解不出或许也可以丢弃,比如注释中的文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import re
import json

# 删除非ascii的编码
def omit_unascii(data):
new_data = b''
p = 0
m = re.search(b"[^\x00-\x7F]+", data[p:])
while m:
new_data += data[p:p+m.start()]
p += m.end()
m = re.search(b"[^\x00-\x7F]+", data[p:])
new_data += data[p:]
return new_data


def json_loads(msg):
try:
# 先尝试正常解析(按UTF8解码)
obj = json.loads(msg)
except Exception as e:
try:
# 尝试先按GBK解码再解析
obj = json.loads(msg.decode("gb2312", "ignore"))
except Exception as e:
try:
# 尝试删除所有非ascii编码后再解析
obj = json.loads(omit_unascii(msg))
except Exception as e:
raise e
return obj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# -*- coding: utf-8 -*-
import pymongo
import threading


class _HashedSeq(list):
__slots__ = 'hashvalue'

def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)

def __hash__(self):
return self.hashvalue


def _make_key(args, kw, typed=False):
key = args
if kw:
kwd_mark = (object(),)
key += kwd_mark
for item in kw.items():
key += item
if typed:
key += tuple(type(v) for v in args)
if kw:
key += tuple(type(v) for v in kw.values())
elif len(key) == 1 and type(key[0]) in {int, str}:
return key[0]
return _HashedSeq(key)


local = threading.local()

class MongoDB:
@staticmethod
def instance(*args, **kw):
if not hasattr(local, "mongodb") or local.mongodb is None:
local.mongodb = {}
key = _make_key(args, kw, typed=False)
if key not in local.mongodb:
local.mongodb[key] = MongoDB(*args, **kw)
return local.mongodb[key]

def __init__(self, host="127.0.0.1", port=27017, dbname=None, username=None, password=None, **kw):
self.client = pymongo.MongoClient(f"mongodb://{host}:{port}/{dbname}", username=username, password=password, **kw)
if dbname:
self.db = self.client[dbname]
else:
self.db = self.client.test

def close(self):
return self.client.close()

def __getattr__(self, key):
return self.db[key]

def __getitem__(self, key):
return self.db[key]

def has_collection(self, name):
return name in self.db.list_collection_names()


# 常用运算符
# 逻辑运算
# {$or:[expression1,expression2,...]}
# {$and:[expression1,expression2,...]}
# {$not:expression1}}
# 比较运算
# {field:{$eq:value}} ==
# {field:{$ne:value}} !=
# {field:{$lt:value}} <
# {field:{$lte:value}} <=
# {field:{$gt:value}} >
# {field:{$gte:value}} >=
# {field:{$in:[value1,value2,...]} in
# {field:{$nin:[value1,value2,...]} not in
# 正则匹配
# {field:{"$regex": "正则表达式"}

# query: 查询条件, ex: {"type": "stock"}
# fields: 提取字段, ex: ["code", "sec_name"]
# sort: 排序字段和排序方式, ex: [("code", -1)] 表示按code排倒序
# limit: 之取前limit个, ex: 10
# **kw: 查询条件,把query展开成参数来写,作用是相同的
def get_securities(query={}, fields=None, sort=None, limit=None, **kw):
q = gen_query("securities", query, fields, sort, **kw)
return [doc for doc in (q.limit(limit) if limit else q)]

def gen_query(collection, query={}, fields=None, sort=None, **kw):
mongo = MongoDB.instance(host="192.168.1.99", dbname="ftresearch", username="ftresearch", password="******")
q = mongo[collection].find(dict(**query, **kw), to_projection_dict(fields))
if sort:
q = q.sort(to_sort_list(sort))
return q


def to_projection_dict(fields):
if fields is None:
return {"_id": 0}
elif isinstance(fields, dict):
return fields
else:
ret = {"_id": 0}
for k in fields:
ret[k] = 1
return ret


def to_sort_list(sort):
if not sort:
return None
ret = []
if isinstance(sort, list):
for e in sort:
if isinstance(e, str):
ret.append((e, 1))
else:
ret.append(e)
else:
if isinstance(sort, str):
ret.append((sort, 1))
else:
ret.append(sort)
return ret


if __name__ == '__main__':
# 查询以68编码开头的所有股票,只取code, sec_name字段
arr = get_securities(code={"$regex": "^68"}, type="stock", fields=["code", "sec_name"])
for e in arr:
print(e)

# 查询所有ST状态和*ST状态的未退市的股票信息,按股票代码排倒序,取前10只
arr = get_securities(
query={
"type": "stock",
"contract_state": "Active",
"special_type": {"$in": ["ST", "StarST"]},
},
fields=["code", "sec_name", "exchange", "special_type"],
sort=[("code", -1)],
limit=10)
for e in arr:
print(e)

# 取2023-01-04日当天在交易状态的股指期货合约代码
codes = [e["code"]
for e in get_securities(
fields=["code"],
type="future",
product={"$in": ["IC", "IF", "IH", "IM"]},
listed_date={"$lte":"2023-01-04"},
de_listed_date={"$gte":"2023-01-04"}
)
]
print(codes)

字段名称 样例 获取命令(Linux)
局域网IP LIP=172.19.51.148; ifconfig -a
网卡物理地址 MAC=00163E066936; ifconfig -a
硬盘序列号 HD=TF667AY92GHRVL; lsblk --nodeps -no serial /dev/sda
PC终端设备名 PCN=hostname; hostname
CPU序列号 CPU=bfebfbff21040652; dmidecode -t 4 | grep ID |sort -u |awk -F': ' '{print $2}'
硬盘分区信息 PI=SDA^EXT4^512G; df -hT|grep '/$'|awk '{print $1"^"$2"^"$3}'
系统盘卷标号 VOL=0004-2CC4; fdisk -l|grep -e "Disk identifier" -e "Disk /dev/"

公司对接东方证券和中泰证券的行情都是使用的盛立EFH接口,在东方证券已经部署在实盘正常使用,在中泰证券部署时发现收不到行情。

中泰证券多了个行情接入认证程序,其实怀疑是否这边有问题,但是通过

1
tcpdump -i 网口 udp

或明确指定组播来源

1
tcpdump -i 网口 host 组播IP

可以抓到组播服务器发出的数据包,说明组播到达了网口,那也说明行情接入认证是成功的。

于是深入代码调试,发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ...
while (true) {
if (m_thrade_quit_flag) {
return NULL;
}

socklen_t len = sizeof(sockaddr_in);

// 始终返回-1
n_rcved = recvfrom(m_sock, line, RCV_BUF_SIZE, 0, (struct sockaddr*)&muticast_addr, &len);
if ( n_rcved < 0) {
// 在此处打印 errno 是 11,即 EAGAIN
continue;
}
else if (0 == n_rcved) {
continue;
}
else {
report_user(EVENT_RECEIVE, m_id, line, n_rcved);
}
}
// ...

因为m_sock被设置成了异步的,所以在没有数据时立刻返回-1,errno==EAGAIN就是正常现象。

搜索recvfrom 始终返回-1 tcpdump可以抓到包发现有人说是被防火墙拦截了。

1
2
service firewalld.service stop
systemctl disable firewalld.service

执行后果然可以收到了,说明tcpdump的处理在防火墙之前,可以抓到被防火墙拦截的数据。