定理:集合Z[n]由所有i=0,1,…, n-1整数组成,其中满足gcd(i,n)=1的元素与乘法模n操作形成了交换群G,且单位元为e=1。
证明:设a、b属于G,有gcd(a,n)=1,gcd(b,n)=1,则gcd(a*b,n)=gcd(b,n)=1,即(a*b) mod n封闭,显然单位元为1;根据扩展欧几里德算法得a*x+n*y=1,x为a的逆元,则1=gcd(a,n)=gcd(a*x,n)=gcd(x,n),故x也在G中
posted @
2023-09-06 22:34 春秋十二月 阅读(276) |
评论 (0) |
编辑 收藏
记输出为[G`, G, p, q, g],其中p为大素数,G`为模p的有限循环整数群,阶为p-1;q为大素数,为G的阶,G为G`的子群(模亦是p),生成元为g(G`的一个元素),另外满足如下条件:
1. 1<q的位长<p的位长,p、q随机选取,p同余于1 mod q,即q整除p-1,q为p-1的素因子
2. 1<g<=p-1,随机选取,测试它的(p-1)/q次幂是否等于1 mod p,若等于则重新选取,直到不等于
3. 上面选定的g,遍历1到q的幂模p,就得到G的各元素
数学基础:一个有限群,对每个元素它的阶整除群的阶,它的群阶幂次方等于单位元;一个有限循环群,它的生成元个数为群阶的欧拉数,若群阶是素数,则所有非1的元素都是生成元
结论:这种计算子群的方法由于保证阶为素数且只要超过160位长,就可避免针对阶为合数的质因子分解并利用中国剩余定理求离散对数的已知最好攻击,具有中长期安全强度
posted @
2023-09-06 22:28 春秋十二月 阅读(341) |
评论 (0) |
编辑 收藏
定理:令K[x]是由次数小于8、系数为0或1的多项式组成的环,m(x)=x^8+x^4+x^3+x+1为不可约多项式,则K[x]/(m(x))(模m(x)剩余类环)同构于元素个数为256的有限域F
证明:
1. 构造映射H: P->Z,P表示K[x]中的多项式,Z表示小于256的非负整数,定义函数h(p)=z(mod 256)。显然H为双射;依初等数论同余性质有h(p1+p2)=(z1+z2)mod 256=z1(mod 256)+z2(mod 256)=h(p1)+h(p2),h(p1*p2)=z1*z2(mod 256)=z1(mod 256)*z2(mod 256)=h(p1)*h(p2),故H保持加法乘法封闭性。这点保证支持任意明文/密文的运算
2. 由一元多项式环的性质得多项式乘法可以交换,即f(x)•g(x)=g(x)•f(x),满足域的交换条件。其乘法单位元是常数项1,满足域的单位元条件
3. 因非零多项式f(x)与m(x)互素,由一元多项式环的互素定理知存在g(x)、k(x)使得f(x)•g(x)+m(x)•k(x)=1(系数模2),即f(x)•g(x)模m(x)余1(这里1表示单位元),故f(x)存在逆元,由群定义知逆元必唯一,满足域的逆元条件。另aes规定零多项式的逆元为其自身。这点保证s盒及列混合操作可逆
posted @
2023-09-06 22:22 春秋十二月 阅读(1412) |
评论 (0) |
编辑 收藏
posted @
2021-12-13 15:21 春秋十二月 阅读(660) |
评论 (0) |
编辑 收藏
背景
由于实际使用中RSA公钥通常很短,而私钥和模位长度一样,导致解密(或签名)时大数指数模运算比较慢,故可使用中国剩余定理约简模数和解密指数,以加快运算
描述
x为密文,n为模,p和q为大素数且满足n=pq,d为私钥,设
x
p ≡ x mod p,x
q ≡ x mod q
(1)
d
p ≡ d mod (p-1),d
q ≡ d mod (q-1)
(2)
y
p = x
p^d
p mod p,y
q = x
q^d
q mod q
(3)
则有 x
d ≡ ((qc
p)y
p + (pc
q)y
q) mod n,其中 c
p ≡ q
-1 mod p , c
q ≡ p
-1 mod q
证明
由(1)式可得
x
pd ≡ x
d mod p,x
qd ≡ x
d mod q
(4)
根据中国剩余定理可得
x
d ≡ ((qc
p)x
pd + (pc
q)x
qd) mod n,下面只要证明y
p和x
pd一样同余于x
d模p,y
q和x
qd一样同余于x
d模q
根据
(2)式及费小马定理可得
x
p^d
p ≡ x
pd mod p,x
q^d
q ≡ x
qd mod q, 再结合(4)得
x
p^d
p ≡ x
d mod p,x
q^d
q ≡ x
d mod q,故
y
p = x
d mod p,y
q = x
d mod q 证毕
posted @
2021-10-01 17:32 春秋十二月 阅读(1373) |
评论 (0) |
编辑 收藏
算法描述
如果对于任意0<=a<p和0<=b<q(p和q皆是素数),那么当x<p*q时,存在一个唯一的x,使得x≡a mod p 且 x≡b mod q,则
x =(((a - b)*u) mod p)*q + b,其中u满足u*q≡1 mod p。
算法证明
1.先推导x的解
因x≡a mod p 且 x≡b mod q
故令x = k1*p + a 且 x = k2*q + b (1)
即 k1*p + a = k2*q + b
=> a - b = k2*q - k1*p (2)
又因u*q≡1 mod p,故令u*q = 1 + k3*p (3)
由(2)和(3)式
=> a - b = k2 * (1+k3*p)/u - k1*p
两边同时乘u
=>(a - b) * u = k2*(1+k3*p) - k1*p*u
两边同时模p
=> ((a - b) * u) mod p = (k2 mod p) mod p (4)
又因x < p*q,故b + k2*q < p*q
=> b <(p - k2) * q
因0<b<q,故p > k2
=> (k2 mod p) mod p = k2
故(4)式即
((a - b) * u) mod p = k2 (5)
将(5)代入(1)式可得
x = (((a - b)*u) mod p)*q + b
2. 再证明x是唯一解
假设x1是另一解,即 x1≡a mod p 且 x1≡b mod q,得
x1 - x ≡ 0 mod p 即 p | x1 - x
x1 - x ≡ 0 mod q 即 q | x1 - x
又因p和q皆为素数,故p*q | x1 - x,得
x1 - x ≡ 0 mod (p*q)
故 x1 mod (p*q) = x mod (p*q) 证毕
posted @
2021-09-19 16:01 春秋十二月 阅读(1012) |
评论 (0) |
编辑 收藏
主要思路
1. 首次连接时调用redisConnectWithTimeout或redisConnectUnixWithTimeout连接Redis服务端,若成功则保存返回的redisContext,假设为ctx
2. 发送命令数据后获取响应,如果是pipeling模式则调用redisGetReply获取响应,再检查redisContext中的错误码,如果为网络出错或关闭,则不置位ctx
REDIS_CONNECTED标志
3. 在下次发送数据时,先检查ctx否置位了
REDIS_CONNECTED标志,若没有则调用redisReconnect重连Redis服务端
实现代码
自动连接
1 int redis_auto_connect(CBED_REDIS *redis)
2 {
3 if(NULL==redis->ctx){
4 redisContext *ctx;
5 if(redis->type == CONN_TCP)
6 ctx = redisConnectWithTimeout(redis->conn.tcp.ip, redis->conn.tcp.port, redis->timeout_conn);
7 else
8 ctx = redisConnectUnixWithTimeout(redis->conn.unix.path, redis->timeout_conn);
9
10 if(NULL==ctx){
11 zlog_fatal(c_redis, "redis allocate context fail");
12 return -1;
13
14 }else if(ctx->err){
15 zlog_fatal(c_redis, "redis connection %s:%d error: %s",
16 redis->type==CONN_TCP?redis->conn.tcp.ip:redis->conn.unix.path,
17 redis->type==CONN_TCP?redis->conn.tcp.port:0, ctx->errstr);
18 redisFree(ctx);
19 return -1;
20 }
21
22 if(REDIS_ERR==redisSetTimeout(ctx, redis->timeout_rw)){
23 zlog_fatal(c_redis, "redis set rw timeout error: %s", ctx->errstr);
24 redisFree(ctx);
25 return -1;
26 }
27
28 redis->ctx = ctx;
29 if(redis_auth(redis)){
30 redisFree(ctx);
31 redis->ctx = NULL;
32 return -1;
33 }
34
35 } else if(!(redis->ctx->flags & REDIS_CONNECTED)){
36 int retry = redis->reconn_max, n = 0;
37 do {
38 if(REDIS_OK==redisReconnect(redis->ctx)){
39 return redis_auth(redis);
40 }
41
42 zlog_warn(c_redis, "redis reconnect %d error: %s", ++n, redis->ctx->errstr);
43 sleep(redis->reconn_interval); //reconn_interval default is 3 seconds
44
45 }while(--retry > 0);
46
47 zlog_error(c_redis, "redis reconnect exceed max num %d", redis->reconn_max);
48 return -1;
49 }
50
51 return 0;
52 }
发送时检查错误码
1 static int redis_bulk_get_reply(CBED_REDIS *redis)
2 {
3 redisReply *r;
4 int i = 0;
5 int num = redis->cmd_num;
6 redis->cmd_num = 0;
7
8 for(; i<num; ++i){
9 if(REDIS_OK==redisGetReply(redis->ctx, (void**)&r)){
10 if(r->type == REDIS_REPLY_ERROR){
11 zlog_error(c_redis, "redis get reply error: %.*s", r->len, r->str);
12 freeReplyObject(r);
13 return -1;
14 }
15 freeReplyObject(r);
16
17 }else{
18 if(redis->ctx->err==REDIS_ERR_IO||redis->ctx->err==REDIS_ERR_EOF)
19 redis->ctx->flags &= ~REDIS_CONNECTED;
20 zlog_fatal(c_redis, "redis get reply fail: %s", redis->ctx->errstr);
21 return -1;
22 }
23 }
24
25 return 0;
26 }
27
28 int redis_send(CBED_REDIS *redis, unsigned char *data, unsigned int size, int force)
29 {
30 if(redis_auto_connect(redis))
31 return -1;
32
33 int i;
34
35 if(redis->max_cmd_num > 1){ //pipelining
36 for(i=0; i<redis->queue_num; ++i){
37 if(REDIS_ERR == redisAppendCommand(redis->ctx, "RPUSH %s %b", redis->queue[i], data, size)) {
38 zlog_fatal(c_redis, "redis append command rpush %s len %u fail: %s", redis->queue[i], size, redis->ctx->errstr);
39 return -1;
40 }
41
42 ++redis->cmd_num;
43 if((!force && redis->cmd_num==redis->max_cmd_num) || force){
44 if(redis_bulk_get_reply(redis))
45 return -1;
46 }
47 }
48
49 }else{
50 for(i=0; i<redis->queue_num; ++i){
51 redisReply *r = redisCommand(redis->ctx, "RPUSH %s %b", redis->queue[i], data, size);
52 if(NULL==r){
53 if(redis->ctx->err==REDIS_ERR_IO||redis->ctx->err==REDIS_ERR_EOF)
54 redis->ctx->flags &= ~REDIS_CONNECTED;
55 zlog_fatal(c_redis, "redis command rpush %s len %u fail: %s", redis->queue[i], size, redis->ctx->errstr);
56 return -1;
57 }
58
59 if(r->type == REDIS_REPLY_ERROR){
60 zlog_error(c_redis, "redis reply rpush %s len %u error: %.*s", redis->queue[i], size, r->len, r->str);
61 freeReplyObject(r);
62 return -1;
63 }
64
65 freeReplyObject(r);
66 }
67 }
68
69 return 0;
70 }
posted @
2021-02-25 15:51 春秋十二月 阅读(6286) |
评论 (0) |
编辑 收藏
存储格式
Oracle Number数据类型是变长的,占0~22字节,不像编程语言中的2/4字节整数或4/8字节浮点数,关于它的存储格式与解析,DSI上有详细的描述,如下所示
符号位/指数字节描述如下
数字字节描述如下
正数或零值的计算
负数值的计算
解析实现
由于Oracle Number的精度高达38位,远超出了基本定长整数或浮点数表达的数值范围,因此解析实际上是大整数/实数的四则运算,为避免造轮子,本文使用了
GMP开源库(
https://gmplib.org/),用于任意精度的算术运算,操作有符号整数、有理数和浮点数,除了在GMP机器上运行的可用内存所暗示的精度之外,对精度没有实际的限制。解析实现的核心函数是
orcl_raw2number
1 #include <stdio.h>
2 #include <assert.h>
3 #include <gmp.h>
4
5 #define MAX_PREC 256
6
7 static mpf_t s_base100;
8 static mpf_t s_one;
9
10 static void init_mpf_globals()
11 {
12 mpf_init_set_ui(s_base100, 100);
13 mpf_init_set_ui(s_one, 1);
14 }
15
16 static void clear_mpf_globals()
17 {
18 mpf_clear(s_base100);
19 mpf_clear(s_one);
20 }
21
22 static void orcl_raw2number(unsigned char *data, unsigned int len, mpf_t result)
23 {
24 unsigned int sign = *data, digit, i;
25 int exp = sign>=128 ? sign-193 : 62-sign;
26 int exp_val;
27 mpf_t tmp;
28
29 mpf_init2(tmp, MAX_PREC);
30 mpf_init2(result, MAX_PREC);
31
32 if(sign & 0x80){
33 for(i=1; i<len; ++i){
34 digit = data[i] - 1;
35 assert(0<=digit && digit<=99);
36
37 exp_val = exp - i + 1;
38 if(exp_val < 0){
39 mpf_pow_ui(tmp, s_base100, -exp_val);
40 mpf_div(tmp, s_one, tmp);
41 }else
42 mpf_pow_ui(tmp, s_base100, exp_val);
43
44 mpf_mul_ui(tmp, tmp, digit);
45 mpf_add(result, result, tmp);
46 }
47
48 }else{
49 --len; //ignore the last byte
50 for(i=1; i<len; ++i){
51 digit = 101 - data[i];
52 assert(0<=digit && digit<=99);
53
54 exp_val = exp - i + 1;
55 if(exp_val < 0){
56 mpf_pow_ui(tmp, s_base100, -exp_val);
57 mpf_div(tmp, s_one, tmp);
58 }else
59 mpf_pow_ui(tmp, s_base100, exp_val);
60
61 mpf_mul_ui(tmp, tmp, digit);
62 mpf_add(result, result, tmp);
63 }
64
65 mpf_neg(result, result);
66 }
67
68 mpf_clear(tmp);
69 }
测试用例
测试了123456.789、-123456.789、Oracle Number实际最大最小值、Oracle Number理论最大最小值
1 int main(int argc,
char *argv[])
2 {
3 int n = 19;
4 char buf[256];
5 mpf_t r;
6
7 init_mpf_globals();
8
9 //123456.789
10 unsigned
char data[] = {0xc3,0xd,0x23,0x39,0x4f,0x5b};
11 orcl_raw2number(data,
sizeof(data), r);
12 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
13 printf("result: %s\n", buf);
14 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
15 mpf_clear(r);
16
17 //-123456.789
18 unsigned
char data2[] = {0x3c,0x59,0x43,0x2d,0x17,0xb,0x66};
19 orcl_raw2number(data2,
sizeof(data2), r);
20 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
21 printf("result: %s\n", buf);
22 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
23 mpf_clear(r);
24
25 //0
26 unsigned
char zero[] = {0x80};
27 orcl_raw2number(zero,
sizeof(zero), r);
28 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
29 printf("result: %s\n", buf);
30 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
31 mpf_clear(r);
32
33 //test actual max value:99999(the number of 9 is 38)
34 unsigned
char max_data[] = {0xd3,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64,0x64};
35 orcl_raw2number(max_data,
sizeof(max_data), r);
36 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
37 printf("result: %s\n", buf);
38 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
39 mpf_clear(r);
40
41 //test actual min value:-99999(the number of 9 is 38)
42 unsigned
char min_data[] = {0x2c,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x66};
43 orcl_raw2number(min_data,
sizeof(min_data), r);
44 gmp_snprintf(buf,
sizeof(buf), "%Ff\n\t%.*Ff(%d digits)", r, n, r, n);
45 printf("result: %s\n", buf);
46 printf("\t"); mpf_out_str(NULL, 10, 0, r); printf("\n");
47 mpf_clear(r);
48
49 clear_mpf_globals();
50
51 //test max oracle number value
52 mpf_init2(r, 256);
53
54 mpf_set_str(r, "1e125", 10);
55 mpf_out_str(NULL, 10, 0, r); printf("\n");
56 gmp_printf("%Ff\n", r);
57
58 //test min oracle number value
59 mpf_set_str(r, "-1e125", 10);
60 mpf_out_str(NULL, 10, 0, r); printf("\n");
61 gmp_printf("%Ff\n", r);
62
63 mpf_clear(r);
64
65 return 0;
66 }
输出如下
posted @
2020-05-08 12:23 春秋十二月 阅读(918) |
评论 (0) |
编辑 收藏
场景说明
选择ENet代替TCP用于弱网环境(通常丢包率高)的数据传输,提高可靠性及传输效率。为了说明怎样正确有效地应用ENet,本文按照TCP C/S同步通信的流程作了对应的接口封装实现,取库名为
rudp。
接口对照
左边为rudp库的API,右边为标准的Berkeley套接字API。rudp库所有API前缀为rudp,rudp_listen和rudp_accept仅用于服务端,rudp_connect和rudp_disconnect仅用于客户端;其它接口共用于两端,其中rudp_send调用rudp_sendmsg实现,rudp_recv调用rudp_recvmsg实现。
具体实现
所有接口遵循Berkeley套接字接口的语义,为简单起见,错误描述输出到标准错误流。
◆
监听,成功返回0,失败返回-1
1 int rudp_listen(const char *ip, int port, ENetHost **host)
2 {
3 ENetAddress address;
4
5 if(!strcmp(ip, "*"))
6 ip = "0.0.0.0";
7
8 if(enet_address_set_host_ip(&address, ip)){
9 fprintf(stderr, "enet_address_set_host_ip %s fail", ip);
10 return -1;
11 }
12
13 address.port = port;
14
15 assert(host);
16 *host = enet_host_create(&address, 1, 1, 0, 0);
17 if(NULL==*host){
18 fprintf(stderr, "enet_host_create %s:%d fail", address.host, address.port);
19 return -1;
20 }
21
22 int size = 1024*1024*1024;
23 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, size)){
24 fprintf(stderr, "enet set server socket rcvbuf %d bytes fail", size);
25 }
26
27 return 0;
28 }
◆
接受连接,成功返回0,失败返回-1
1 int rudp_accept(ENetHost *host, unsigned int timeout, ENetPeer **peer)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.type != ENET_EVENT_TYPE_CONNECT){
9 if(event.type == ENET_EVENT_TYPE_RECEIVE)
10 enet_packet_destroy(event.packet);
11 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
12 return -1;
13 }
14
15 assert(peer);
16 *peer = event.peer;
17
18 }else if(0==ret){
19 fprintf(stderr, "enet_host_service timeout %d", timeout);
20 return -1;
21
22 }else{
23 fprintf(stderr, "enet_host_service fail");
24 return -1;
25 }
26
27 return 0;
28 }
◆
建立连接,成功返回0,失败返回-1,conn_timeout是连接超时,rw_timeout是收发超时,单位为毫秒
1 int rudp_connect(const char *srv_ip, int srv_port, unsigned int conn_timeout, unsigned int rw_timeout, ENetHost **host, ENetPeer **peer)
2 {
3 assert(host);
4 *host = enet_host_create(NULL, 1, 1, 0, 0);
5 if(NULL==*host){
6 fprintf(stderr, "enet_host_create fail");
7 goto fail;
8 }
9 if(enet_socket_set_option((*host)->socket, ENET_SOCKOPT_RCVBUF, 1024*1024*1024)){
10 fprintf(stderr, "enet set server socket rcvbuf 1M bytes fail");
11 }
12
13 ENetAddress srv_addr;
14 if(enet_address_set_host_ip(&srv_addr, srv_ip)){
15 fprintf(stderr, "enet_address_set_host_ip %s fail", srv_ip);
16 goto fail;
17 }
18 srv_addr.port = srv_port;
19
20 assert(peer);
21 *peer = enet_host_connect(*host, &srv_addr, 1, 0);
22 if(*peer==NULL){
23 fprintf(stderr, "enet_host_connect %s:%d fail", srv_ip, srv_port);
24 goto fail;
25 }
26
27 enet_peer_timeout(*peer, 0, rw_timeout, rw_timeout);
28
29 int cnt = 0;
30 ENetEvent event;
31
32 while(1){
33 ret = enet_host_service(*host, &event, 1);
34 if(ret == 0){
35 if(++cnt >= conn_timeout){
36 fprintf(stderr, "enet_host_service timeout %d", conn_timeout);
37 goto fail;
38 }
39
40 }else if(ret > 0){
41 if(event.type != ENET_EVENT_TYPE_CONNECT){
42 fprintf(stderr, "enet_host_service event type %d is not connect", event.type);
43 goto fail;
44 }
45 break; //connect successfully
46
47 }else{
48 fprintf(stderr, "enet_host_service fail");
49 goto fail;
50 }
51 }
52
53 #ifdef _DEBUG
54 char local_ip[16], foreign_ip[16];
55 ENetAddress local_addr;
56
57 enet_socket_get_address((*host)->socket, &local_addr);
58 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
59 enet_address_get_host_ip(&(*peer)->address, foreign_ip, sizeof(foreign_ip));
60
61 printf("%s:%d connected to %s:%d", local_ip, loca_addr.port, foreign_ip, (*peer)->address.port);
62 #endif
63
64 return 0;
65
66 fail:
67 if(*host) enet_host_destroy(*host);
68 return -1;
69 }
◆
断开连接,若成功则返回0,超时返回1,出错返回-1。先进行优雅关闭,如失败再强制关闭
1 int rudp_disconnect(ENetHost *host, ENetPeer *peer)
2 {
3 int ret;
4
5 #ifdef _DEBUG
6 char local_ip[16], foreign_ip[16];
7 ENetAddress local_addr;
8
9 enet_socket_get_address(host->socket, &local_addr);
10 enet_address_get_host_ip(&local_addr, local_ip, sizeof(local_ip));
11 enet_address_get_host_ip(&peer->address, foreign_ip, sizeof(foreign_ip));
12
13 printf("%s:%d is disconnected from %s:%d", local_ip, local_addr.port, foreign_ip, peer->address.port);
14 #endif
15
16 ENetEvent event;
17 enet_peer_disconnect(peer, 0);
18
19 while((ret = enet_host_service(host, &event, peer->roundTripTime)) > 0){
20 switch (event.type){
21 case ENET_EVENT_TYPE_RECEIVE:
22 enet_packet_destroy (event.packet);
23 break;
24
25 case ENET_EVENT_TYPE_DISCONNECT:
26 ret = 0;
27 goto disconn_ok;
28 }
29 }
30
31 ret = 0==ret ? 1 : -1;
32
33 fprintf(stderr, "enet_host_service with timeout %d %s", peer->roundTripTime, 1==ret?"timeout":"failure");
34
35 enet_peer_reset(conn->peer);
36
37 disconn_ok:
38 enet_host_destroy(host);
39 return ret;
40 }
◆
发送数据,若成功则返回已发送数据的长度,否则返回-1
1 int rudp_sendmsg(ENetHost *host, ENetPeer *peer, ENetPacket *packet)
2 {
3 int ret;
4
5 if(enet_peer_send(peer, 0, packet)){
6 fprintf(stderr, "enet send packet %lu bytes to peer fail", packet->dataLength);
7 return -1;
8 }
9
10 ret = enet_host_service(host, NULL, peer->roundTripTime);
11 if(ret >= 0){
12 if(peer->state == ENET_PEER_STATE_ZOMBIE){
13 fprintf(stderr, "enet peer state is zombie");
14 return -1;
15 }
16 return packet->dataLength;
17
18 }else{
19 fprintf(stderr, "enet host service %u millsecond failure", peer->roundTripTime);
20 return -1;
21 }
22 }
23
24 int rudp_send(ENetHost *host, ENetPeer *peer, const void *buf, size_t len)
25 {
26 int ret;
27
28 ENetPacket *packet = enet_packet_create(buf, len, ENET_PACKET_FLAG_RELIABLE);
29 if(NULL==packet){
30 fprintf(stderr, "enet create packet %lu bytes fail", sizeof(int)+len);
31 return -1;
32 }
33
34 return rudp_sendmsg(host, peer, packet);
35 }
发送数据时需根据对端状态判断是否断线,并且packet标志设为可靠
◆
接收数据,若成功则返回已接收数据的长度,否则返回-1
1 int rudp_recvmsg(ENetHost *host, ENetPeer *peer, ENetPacket **packet, unsigned int timeout)
2 {
3 int ret;
4 ENetEvent event;
5
6 ret = enet_host_service(host, &event, timeout);
7 if(ret > 0){
8 if(event.peer != peer){
9 fprintf(stderr, "enet receive peer is not matched");
10 goto fail;
11 }
12 if(event.type != ENET_EVENT_TYPE_RECEIVE){
13 fprintf(stderr, "enet receive event type %d is not ENET_EVENT_TYPE_RECEIVE", event.type);
14 goto fail;
15 }
16
17 *packet = event.packet;
18 return (*packet)->dataLength;
19
20 fail:
21 enet_packet_destroy(event.packet);
22 return -1;
23
24 }else {
25 fprintf(stderr, "enet receive %u millsecond %s", timeout, ret?"failure":"timeout");
26 return -1;
27 }
28 }
29
30 int rudp_recv(ENetHost *host, ENetPeer *peer, void *buf, size_t maxlen, unsigned int timeout)
31 {
32 ENetPacket *packet;
33
34 if(-1==rudp_recvmsg(host, peer, &packet, timeout))
35 return -1;
36
37 if(packet->dataLength > maxlen) {
38 fprintf(stderr, "enet packet data length %d is greater than maxlen %lu", packet->dataLength, maxlen);
39 return -1;
40 }
41
42 memcpy(buf, packet->data, packet->dataLength);
43 enet_packet_destroy(packet);
44
45 return packet->dataLength;
46 }
◆
等待所有确认,若成功返回0,超时返回1,失败返回-1
1 int rudp_wait_allack(ENetHost *host, ENetPeer *peer, unsigned int timeout)
2 {
3 int ret, cnt = 0;
4
5 while((ret = enet_host_service(host, NULL, 1)) >= 0){
6 if(enet_peer_is_empty_sent_reliable_commands(peer, 0,
7 ENET_PROTOCOL_COMMAND_SEND_RELIABLE|ENET_PROTOCOL_COMMAND_SEND_FRAGMENT))
8 return 0;
9
10 if(peer->state == ENET_PEER_STATE_ZOMBIE){
11 fprintf(stderr, "enet peer state is zombie");
12 return -1;
13 }
14
15 if(0==ret && ++cnt>=timeout){
16 return 1;
17 }
18 }
19
20 fprintf(stderr, "enet host service fail");
21 return -1;
22 }
等待已发送数据的所有确认时,需根据对端状态判断是否断线
示例流程
左边为客户端,压缩并传输文件;右边为服务端,接收并解压存储文件。
客户端【读文件块并压缩】这个环节,需要显式创建可靠packet,并将压缩后的块拷贝到其中
posted @
2020-05-04 19:08 春秋十二月 阅读(2198) |
评论 (0) |
编辑 收藏
为什么用VSS
VSS是Windows系统的卷影像拷贝服务,用于解决如下问题:
◆ 许多备份工具涉及打开文件
◆ 但是若一个应用程序已经以独占方式打开文件并进行访问时,备份工具则不能访问该文件
◆ 即使备份工具能够访问已打开的文件,也可能造成备份文件的不一致性
在实际数据灾备中,主流厂商实现SQL Server的热备并不会使用数据库自带的
backup database/
backup log命令,因为这种方式在应急容灾(此时源数据库已宕机)挂载数据时要先还原,而还原要连接数据库运行
restore database/
restore log命令,这样就需要部署一台机器装上SQL Server专用于还原,不仅增大了成本而且延长了
RTO;而使用VSS,备份的就是SQL Server的数据文件及日志文件,在应急容灾挂载时可直接打开并用于增删改查,无须还原,免去了机器成本并降低了RTO(只存在数据库挂载时的事务恢复时间)。
VSS架构
VSS包括Requestor、Writer、Provider和VSS核心模块四部分,如下图所示
Requestor在本文中表示热备份应用程序;Writer主要功能是保证数据的一致性,使得那些能够感知影像拷贝的应用程序能够接收到冻结(freeze)和解冻(thaw)通知,以确保其文件的备份拷贝是内在一致的,在本文中即指SQL Server自带的
SQL Writer;Provider主要功能是创建影像拷贝即打快照,允许将ISV特定的存储方案与影像拷贝服务集成起来,在本文中即
volsnap.sys存储过滤型驱动程序,位于文件系统和卷管理器之间;VSS核心模块即图中的卷影像拷贝服务,主要功能是协调各个模块的协作运行,并提供创建及管理卷影像拷贝的API接口。
VSS原理示例
无论何时,当卷影像拷贝驱动程序看到一个针对原始卷的写操作时,它把将要被修改的扇区的内容复制到一个与影像卷相关联的、由页面文件支持的内存区中
◆ 对于已修改扇区的影像卷读操作,从该内存区中读取数据
◆ 对于未修改扇区的影像卷读操作,从原始卷中读取
备份应用程序、Provider和SQL Writer的局限
◆ 只能备份Windows系统支持的本地文件系统上的文件,不支持远程共享或交叉挂载的文件系统
◆ 对于系统提供者(Windows系统默认自带的Provider,使用写时拷贝技术),被拷贝的源卷不必是NTFS卷,但影像卷必须是NTFS卷
◆ SQL Writer支持全量备份及恢复、支持差异备份及恢复和Copy Only备份,但不支持备份连续事务日志、文件和文件组,不支持页恢复
怎样使用VSS
微软官网提供的VSS SDK 7.2(
https://www.microsoft.com/en-us/download/details.aspx?id=23490)中自带了
vshadow和
betest工具源码,经过笔者修正一些bug(win 10 + vs2010),并为了备份配置方便将原来的文本换成xml格式,成功地实现了SQL Server的全量热备及恢复、差量热备及恢复
vshadow用法
以管理员身份在ms-dos窗口下执行vshadow.exe /?,可得到所有的帮助
示例
可用vshadow -wm获取当前系统所有写者的元数据,再从中查找SQL Server Writer的写者ID及它下面COM组件的逻辑路径和名称
betest用法
以管理员身份在ms-dos窗口下执行betest.exe /?,可得到所有的帮助
示例
1. 全量备份SQL Server
betest.exe /v
/b /t
FULL /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/v -- 输出详细信息,可选的
/b -- 备份
/t -- 备份类型
/s -- 备份/恢复组件XML格式文档,内含写者及其下组件的元数据(非常重要)
/d -- 备份目录
/c -- 相关写者的配置文件,文件内含写者ID及其下COM组件的逻辑全路径名
全量恢复SQL Server
betest.exe /v
/r /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/r -- 恢复
其它选项说明同上,下同
2. 差异备份SQL Server
betest.exe /v /b /t
DIFFERENTIAL /s backupdiff.xml
/pre backupfull.xml /d f:\backupdiff /c SQLWriter.xml
/pre -- 表示前次基准的全量备份生成的组件XML格式文档
差异恢复SQL Server
a) betest.exe /v /r
/AdditionalRestores /s backupfull.xml /d f:\backupfull /c SQLWriter.xml
/AdditionRestores -- 用于差异恢复的选项,表示全量后面需要紧跟差异恢复才能完成数据库恢复
b) betest.exe /v /r /s backupdiff.xml /d f:\backupdiff /c SQLWriter.xml
注意,此时/s跟的是差异备份生成的backupdiff.xml文件,/d跟的是差异备份目录
3. SQL Writer配置
xml格式说明
writer节点
id属性 --- 写者唯一ID
server_name属性 --- SQLServer服务名
stop_restore_start属性(可选) --- 表示恢复时是否先停止数据库服务再启动,yes表示先停再启,no则反之,这个用于恢复系统数据库master,因为master不支持在线恢复
component节点
pathname属性 --- 逻辑路径名
file节点
src_path节点 --- SQL Server文件所在路径的匹配模式
alternate_path节点 --- 恢复时的备选路径,用于合成差异增量
示例
<?xml version="1.0" encoding="utf-8"?>
<betest>
<writer id="{a65faa63-5ea8-4ebc-9dbd-a0c4db26912a}" service_name="MSSQLSERVER" stop_restore_start="no">
<component pathname="DESKTOP-JUP320L\master">
<file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file>
</component>
<component pathname="DESKTOP-JUP320L\model">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
<component pathname="DESKTOP-JUP320L\test">
<!--file>
<src_path>E:\*...</src_path>
<alternate_path>f:\sqlserver\</alternate_path>
</file-->
</component>
</writer>
</betest>
posted @
2020-05-02 16:31 春秋十二月 阅读(1423) |
评论 (0) |
编辑 收藏