随笔-103  评论-224  文章-30  trackbacks-0
  置顶随笔
模板
    1. 空基类优化
    2. 元编程技术
        2.1. 选择API
        2.2. 计算最值
        2.3. 类型选择
    3. 封装GCC原子操作
    4. 定制类对象的内存管理

算法
    1. 排序
        1.1. 改进的快速排序
        1.2. 原位统计排序     
    2. 多叉树
        2.1. 深度优先存储
        2.2. 迭代器的设计
        2.3. 前序遍历
        2.4. 后序遍历
        2.5. 兄弟遍历
        2.6. 叶子遍历
        2.7. 深度遍历 
    3. 优先级队列
        3.1. 原理
        3.2. 内幕
        3.3. 外观
    4. RSA加解密的证明
    5. DSA数字签名的推导

GUI
 
    1. MFC中的WM_COMMAND传递
    2. ATL和WTL中的消息反射
    3. 工作线程与消息循环
    4. 多窗口的组合与分离
        4.1. 接口
        4.2. 实现

跨平台
    1. 用户态自旋锁
    2. 互斥锁
    3. 信号量
    4. socket管道
    5. 锁框架的设计与实现

网络
    1. 运用状态机异步接收变长包
    2. 基于OpenSSL实现的安全连接
    3. TCP/IP FAQ
        3.1. 链路层、网络层和传输层
        3.2. 插口层和应用层
    4. Linux套接字与虚拟文件系统
        4.1. 初始化和创建
        4.2. 操作和销毁
    5. Linux ICMP消息的产生与转换
    6. nginx iocp
        6.1. tcp异步连接
        6.2. udp异步接收
        6.3. scm服务控制
    7. TCP分组丢失时的状态变迁
    8. 基于ENet实现可靠UDP通信的同步模型

Shell应用
    1. 自动生成并安装服务脚本
    2. nginx升级与恢复
    3. 使用awk定位反汇编输出
    4. 自动化批量编译
posted @ 2014-04-10 16:04 春秋十二月 阅读(1791) | 评论 (0)编辑 收藏
  2021年12月13日
目录


下载:基于Rust构建WebAssembly
posted @ 2021-12-13 15:21 春秋十二月 阅读(562) | 评论 (0)编辑 收藏
  2021年10月1日
背景
 
由于实际使用中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 春秋十二月 阅读(998) | 评论 (0)编辑 收藏
  2021年9月19日
算法描述
  如果对于任意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 春秋十二月 阅读(847) | 评论 (0)编辑 收藏
  2021年2月25日
主要思路
 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 春秋十二月 阅读(4055) | 评论 (0)编辑 收藏
  2020年5月8日
存储格式
   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 春秋十二月 阅读(728) | 评论 (0)编辑 收藏
  2020年5月4日
场景说明
   选择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 春秋十二月 阅读(1394) | 评论 (0)编辑 收藏
  2020年5月2日
为什么用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 春秋十二月 阅读(936) | 评论 (0)编辑 收藏
  2020年4月21日
阅读《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 春秋十二月 阅读(5826) | 评论 (0)编辑 收藏
  2019年11月20日
描述
   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 春秋十二月 阅读(690) | 评论 (0)编辑 收藏
  2019年11月6日
部署图
   
   传统的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 春秋十二月 阅读(719) | 评论 (0)编辑 收藏
仅列出标题  下一页