随笔-103  评论-224  文章-30  trackbacks-0
 
目录


下载:基于Rust构建WebAssembly
posted @ 2021-12-13 15:21 春秋十二月 阅读(552) | 评论 (0)编辑 收藏
背景
 
由于实际使用中RSA公钥通常很短,而私钥和模位长度一样,导致解密(或签名)时大数指数模运算比较慢,故可使用中国剩余定理约简模数和解密指数,以加快运算

描述

 x为密文,n为模,p和q为大素数且满足n=pq,d为私钥,设
   x≡ x mod p,x≡ x mod q                      (1)
   d≡ d mod (p-1),dq ≡ d mod (q-1)          (2)
   yp = xp^dp mod p,yq = xq^dmod q        (3)
 则有 xd ≡ ((qcp)yp + (pcq)yq) mod n,其中 c≡ q-1 mod p , cq ≡ p-1 mod q

证明
 由(1)式可得
   xpd ≡ xd mod p,xqd ≡ xd mod q                (4)
 根据中国剩余定理可得
   xd ≡ ((qcp)xp+ (pcq)xqd) mod n,下面只要证明yp和xpd一样同余于xd模p,yq和xqd一样同余于xd模q
 根据(2)式及费小马定理可得
   xp^d≡ xpd mod p,xq^d≡ xqd mod q, 再结合(4)得
   xp^d≡ xd mod p,xq^d≡ xd mod q,故
   yp = xd mod p,yq = xd mod q 证毕
posted @ 2021-10-01 17:32 春秋十二月 阅读(944) | 评论 (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 春秋十二月 阅读(824) | 评论 (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 春秋十二月 阅读(3713) | 评论 (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 春秋十二月 阅读(694) | 评论 (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 春秋十二月 阅读(1297) | 评论 (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)中自带了vshadowbetest工具源码,经过笔者修正一些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 春秋十二月 阅读(850) | 评论 (0)编辑 收藏
阅读《MySQL Innodb无锁化设计的日志系统》(https://zhuanlan.zhihu.com/p/53037796)后的心得:
与oracle日志子系统异曲同工的差异
 1. 空洞:对于并发会话copy重做日志造成的空洞,oracle是由lgwr判断并等待持有redo copy闩锁的会话释放后,这时空洞已被填充,可以刷到磁盘了;mysql则是由log writer线程监测到空洞被填充后,再写入一段连续最大lsn的日志到磁盘
 2. io方式:oracle的lgwr是direct io;mysql的log writer是写到os的page cache,后由独立的log flusher线程刷盘,比oracle多了一个过程
 3. 唤醒会话:oracle由lgwr扫描所有等待的会话,只唤醒满足写入条件(事务提交log已刷盘)的会话;mysql则由独立的log flush notifier通过满足条件对应的分片消息队列来唤醒,比oracle多了一个过程
总结:mysql通过原子变量来管理全局log buffer的几个内存位置来实现无锁化,而原子操作在多核上仍不利于线性扩展。oracle的闩锁也存在类似问题,但通过私有redo缓存和多个全局log buffer(相关闩锁量与cpu核数正比),来提升了扩展性。故整体上oracle更优

阅读《MySQL/InnoDB数据克隆插件(clone plugin)实现剖析》(https://zhuanlan.zhihu.com/p/76255304)后的心得:
与oracle老式热备异曲同工的差异
 1. page追踪:oracle老式热备实际当每行更新时将整个关联的page记录在redo日志中;mysql热备则是记录变化page的id在单独一个地方,用于page copy阶段从buffer pool读取并发送页数据到备库
 2. redo归档:oracle老式热备在拷贝数据文件的全过程,只要数据文件被修改就会有redo归档;mysql热备则仅在page copy阶段启用redo归档,可看做是临时的
 3. 一致性恢复:oracle老式热备存在数据块分离现象,对此应用被冻结scn及日志序列号后的redo log来恢复;mysql则通过page copy及应用clone lsn后的redo log来恢复
总结:oracle老式热备必须处于归档模式,由于记录整块而非行变化,因此重做日志写放大而增加了cpu和io的开销,由于可能判断并修复分离的块,因此延长了恢复时间;mysql通过page追踪和临时redo归档来减少应用redo的体量而缩短了恢复时间。故mysql热备整体更优,但相对oracle的现代rman备份则并不更优
posted @ 2020-04-21 11:19 春秋十二月 阅读(5808) | 评论 (0)编辑 收藏
描述
   nginx是一款著名的高性能开源Web与反向代理服务器,支持windows和linux操作系统,因为在windows系统上还不支持SCM(服务控制管理),所以只能以控制台方式运行,但这样并不是在后台运行,也不能在系统登录前启动。针对这些问题,本方法通过改进源码,使nginx良好地支持了SCM,方便了部署运行

特点
   最大地复用了nginx源码;支持SCM,并兼容控制台运行方式;统一处理异常退出而报告服务停止

实现

   变换原主函数
      将原来的main函数更名为ngx_main,并增加第3个参数is_scm来标识运行方式,非0表示服务方式,0表示控制台方式,流程如下
                                    
      图上红色部分为插入的逻辑,其它部分为nginx原来的逻辑。由于服务初始化须将错误记录在log(日志)中,所以应在初始化log模块后调用

   增加主函数
      这个主函数也就是程序入口main,可被控制台或SCM调用,当被SCM调用时,注册服务以及启动服务控制调度程序,流程如下
                                       
      如果以命令行启动nginx 也就是master进程(管理进程),或nginx产生worker进程(工作进程)时,那么以控制台方式调用main,进而以is_scm为0调用ngx_main,当ngx_main返回时,就表示master或worker进程退出了   

   服务主函数
      由SCM生成的一个逻辑线程调用,流程如下
                                         
      这里的逻辑线程代替了nginx的master进程,到这里就表明已经以SCM方式运行了,所以以is_scm为1调用ngx_main,当ngx_main返回时,就表明master进程退出了,应该更新服务状态为已停止,然后返回表明当前服务结束了

   服务初始化
      由ngx_main调用,见变换原主函数流程图,流程如下
                                          
      由于在nginx实现中,有多处出现异常错误而直接退出,因此首先注册了进程退出处理器,在其内报告服务状态为已停止,这样只要当进程退出了,在SCM上就能看到已停止的状态了

   服务控制处理器
      由SCM的主线程调用,流程如下
                                         
   
   调用关系
      下图左边为master进程调用模块与函数,右边为worker进程调用模块与函数,委托主函数是ngx_main
            
posted @ 2019-11-20 19:45 春秋十二月 阅读(659) | 评论 (0)编辑 收藏
部署图
   
   传统的vss备份架构由于备份应用部署在应用服务器内,因此比较耗应用服务器的CPU和IO,特别是拷贝大量的文件,为了降低对应用服务器的干扰,可采用server-free架构,将耗时的拷贝移到另一机器即备份服务器实现,而应用服务器只负责占用资源及耗时很少的打快照。这种架构运用了vss可传输卷影拷贝的特性,要求快照处于共享存储中,适用于Windows Server 2003 sp1以上版本

协作流程图
   
   VSS快照代理端的SetContext要求设置成VSS_CTX_APP_BACKUP | VSS_VOLSNAP_ATTR_TRANSPORTABLE
posted @ 2019-11-06 18:01 春秋十二月 阅读(699) | 评论 (0)编辑 收藏
仅列出标题
共11页: 1 2 3 4 5 6 7 8 9 Last