• C语言,基于单向链表实现,变长动态数据缓冲区(线程安全) ---- 类似java的StringBuffer --- 亲测OK


    参考:https://blog.csdn.net/aiwangtingyun/article/details/79705042

     参考:https://baike.baidu.com/item/realloc/659993?fr=aladdin

    C语言,基于单向链表实现,变长动态数据缓冲区(线程安全) ---- 类似java的StringBuffer

    0、我的实现与realloc、环形数组的区别

    0.1   realloc原理 

    • 如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address。
      这里说的是“扩大”,我们知道,realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小= newsize。那么就ok。得到的是一块连续的内存。
    • 如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。
      并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针。(数据被移动了)。
      老块被放回堆上。

    0.2  我的原理

    我的是一个buffer,不能像realloc那样可扩大、可缩小,我的只能扩大,类似于流的缓存

    原理是:

    数据追加——不再顾虑原来分配的空间,而是重新分配出要扩大的空间,将指令以节点方式挂在单向表的尾部

    数据读取——从头节点中读出数据,如果头节点被读完,则删除并释放头节点对应的内存

    0.3 要不要使用我的方式

    如果你的内存够用,大可不必像我这样实现,直接调用realloc即可

    有的同学说使用环形数组,环形数组是定长的缓冲区,并且数据接收是可控的范围时使用。

    如果你的内存很紧缺,或者接收的数据不可控——另一个模块在不断的塞给你数据,你又不得不接,环形数组分配太大浪费内存,分配太小不够用,给的数据不存就丢了,这种场景可以以用我的方式,内存用多少开多少,随时用随时申请,请往下看。

    1、就4个接口:

    1 void *BytesBuffer_create(void);   // 缓冲区句柄创建
    2 void BytesBuffer_delete(IN void *p_head); // 缓冲区销毁
    3 int BytesBuffer_read(IN void *p_head, OUT unsigned char *out_buf, IN unsigned int expected_read_len); // 缓冲区读
    4 int BytesBuffer_append(IN void *p_head, IN unsigned char *in_buf, IN unsigned int expected_append_len); // 缓冲区写

     2、直接上代码,两个缓冲区文件,一个测试文件

      1 /**
      2  * Copyright © XXXX Technologies Co., Ltd. All Rights Reserved.
      3  * @Date 2020年08月01日,下午16:00:00
      4  */
      5 
      6 /**********************************************************************************************
      7  * 文件描述: 该文件为带头结点的单链表库函数中,部分功能函数的实现
      8  * 日期: 2020.08.01 16:00:00
      9  * 作者: LiuYan
     10 **********************************************************************************************/
     11 
     12 #include <stdlib.h>
     13 #include <stdio.h>
     14 #include <string.h>
     15 
     16 #include "single_linked_list.h"
     17 
     18 /**********************************************************************************************
     19     单向链表 : 遍历打印链表所有数据
     20 
     21 
     22     日期: 2020.08.01 16:00:00
     23     作者: LiuYan
     24 
     25     参数: h:链表头结点的地址
     26 
     27     返回值: 无
     28 
     29 **********************************************************************************************/
     30 void XXXX_SINGLELIST_print(IN const Node_head_t *h)
     31 {
     32     Node_t *i = NULL;
     33     int index = 0;
     34 
     35     /* 检查是否为空链表 */
     36     if (NULL == h || NULL == h->next)
     37     {
     38         XXXX_print_ln_E("Empty list.");
     39         return;
     40     }
     41     
     42     /* 遍历链表 */
     43     for (index = 0, i = h->next; i != NULL; index++, i = i->next)
     44     {
     45         XXXX_print_ln_I("[%d]= %d/%d:%p", index, i->data.len_used, i->data.len_data, i->data.data);
     46     }
     47 }
     48 
     49 /**********************************************************************************************
     50     单向链表 : 删除链表头节点后的第一个节点
     51 
     52 
     53     日期: 2020.08.01 16:00:00
     54     作者: LiuYan
     55 
     56     参数: h:链表头结点的地址
     57 
     58     返回值: 成功返回0, 空链表返回-1
     59 
     60 **********************************************************************************************/
     61 int XXXX_SINGLELIST_delete_first(IN Node_head_t *h)
     62 {
     63     Node_t *fast = NULL;    // 快指针
     64 
     65     // 获取锁
     66     if (NULL != h->lock) {
     67         XXXX_mutex_take(h->lock);
     68     }
     69 
     70     /* 检查是否为空链表 */
     71     if (NULL == h || NULL == h->next)
     72     {
     73         XXXX_print_ln_E("Empty list.");
     74         // 释放锁
     75         if (NULL != h->lock) {
     76             XXXX_mutex_give(h->lock);
     77         }
     78         
     79         return -1;
     80     }    
     81 
     82     fast = h->next;    // 快指针
     83 
     84     h->next = fast->next;    // 通过摘掉节点
    
    

    fast->next = NULL;
    fast->data.len_used = 0;
    fast->data.len_data = 0;
    if (NULL != fast->data.data) {
        FEITIAN_00_free(fast->data.data);
        fast->data.data = NULL;
    }


    85 XXXX_free(fast); // 和释放节点空间达到删除目的 86 fast = NULL; 87 88 // 释放锁 89 if (NULL != h->lock) { 90 XXXX_mutex_give(h->lock); 91 } 92 93 return 0; 94 } 95 96 /********************************************************************************************** 97 单向链表 : 删除链表的所有节点(除了头节点) 98 99 100 日期: 2020.08.01 16:00:00 101 作者: LiuYan 102 103 参数: h:链表头结点的地址 104 105 返回值: 无 106 107 **********************************************************************************************/ 108 void XXXX_SINGLELIST_delete(IN Node_head_t *h) 109 { 110 int ret = 0; 111 112 /* 检查是否为空链表 */ 113 if (NULL == h || NULL == h->next) 114 { 115 XXXX_print_ln_E("Empty list."); 116 return; 117 } 118 119 /* 遍历链表 */ 120 for (; 0 == ret; ) 121 { 122 ret = XXXX_SINGLELIST_delete_first(h); 123 } 124 } 125 126 /********************************************************************************************** 127 单向链表 : 创建一个节点 128 129 130 日期: 2020.08.01 16:00:00 131 作者: LiuYan 132 133 参数: 无 134 135 返回值: 成功返回新节点指针,失败返回NULL 136 137 **********************************************************************************************/ 138 Node_t *XXXX_SINGLELIST_create_node(void) 139 { 140 Node_t *newNode = NULL; 141 142 /* 第一步:分配新节点空间 */ 143 newNode = XXXX_malloc(sizeof(Node_t)); 144 if (NULL == newNode) 145 { 146 XXXX_print_ln_E("malloc Failed(size=%u).", (unsigned int)sizeof(Node_t)); 147 return NULL; 148 } 149 150 memset((void *)newNode, 0, sizeof(Node_t)); 151 152 return newNode; 153 } 154 155 /********************************************************************************************** 156 单向链表 : 插入节点到链表尾 157 158 **** 注意: 调用完毕此函数后, 请自行维护 node_data 对应的实参 *** 159 160 日期: 2020.08.01 16:00:00 161 作者: LiuYan 162 163 参数: h:链表头结点的地址, node_data:待插入的节点 164 165 返回值: 成功返回0,失败返回-1 166 167 **********************************************************************************************/ 168 169 int XXXX_SINGLELIST_append_tail(IN Node_head_t *h, IN Node_data_t *node_data) 170 { 171 Node_t *newNode = NULL; 172 Node_t *i = (Node_t *)h; 173 174 // 获取锁 175 if (NULL != h->lock) { 176 XXXX_mutex_take(h->lock); 177 } 178 179 /* 参数检查 */ 180 if (NULL == h || NULL == node_data) 181 { 182 XXXX_print_ln_E("wrong para1."); 183 184 // 释放锁 185 if (NULL != h->lock) { 186 XXXX_mutex_give(h->lock); 187 } 188 return -1; 189 } 190 191 /* 第一步:分配新节点空间 */ 192 newNode = XXXX_SINGLELIST_create_node(); 193 if (NULL == newNode) 194 { 195 XXXX_print_ln_E("create_node Failed."); 196 197 // 释放锁 198 if (NULL != h->lock) { 199 XXXX_mutex_give(h->lock); 200 } 201 return -1; 202 } 203 204 /* 第二步:赋值给新节点 */ 205 memcpy((void *)&newNode->data, (void *)node_data, sizeof(Node_data_t)); 206 207 /* 第三步:找到链表尾 */ 208 while (i->next != NULL) 209 { 210 i = i->next; 211 } 212 213 /* 第四步:把新节点插入到链表尾 */ 214 i->next = newNode; // 尾节点指向新节点 215 newNode->next = NULL; // 新节点指向NULL,作为链表尾 216 217 // 释放锁 218 if (NULL != h->lock) { 219 XXXX_mutex_give(h->lock); 220 } 221 222 return 0; // 成功返回0 223 } 224 225 /********************************************************************************************** 226 字节缓存区 : 创建对象 227 228 229 日期: 2020.08.01 16:00:00 230 作者: LiuYan 231 232 参数: 无 233 234 返回值: NULL: 失败 235 非NULL: 成功, 返回对象 236 237 **********************************************************************************************/ 238 void *BytesBuffer_create(void) 239 { 240 Node_head_t *headNode = NULL; 241 242 /* 第一步:分配新节点空间 */ 243 headNode = XXXX_malloc(sizeof(Node_head_t)); 244 if (NULL == headNode) 245 { 246 XXXX_print_ln_E("malloc Failed(size=%u).", (unsigned int)sizeof(Node_head_t)); 247 return NULL; 248 } 249 250 memset((void *)headNode, 0, sizeof(Node_head_t)); 251 252 headNode->lock = XXXX_mutex_init(); 253 if ( NULL == headNode->lock ) { 254 XXXX_print_ln_E(" mutex create failed. "); 255 } 256 257 return (void *)headNode; 258 } 259 260 /********************************************************************************************** 261 字节缓存区 : 销毁对象 262 263 264 日期: 2020.08.01 16:00:00 265 作者: LiuYan 266 267 参数: p_head, 待销毁对象 268 269 返回值: 无 270 271 **********************************************************************************************/ 272 void BytesBuffer_delete(IN void *p_head) 273 { 274 Node_head_t *h = (Node_head_t *)p_head; 275 276 /* 检查是否为空链表 */ 277 if (NULL == h || NULL == h->next) 278 { 279 XXXX_print_ln_E("Empty list."); 280 return; 281 } 282 283 XXXX_SINGLELIST_delete((Node_head_t *)p_head); 284 285 XXXX_free(p_head); 286 p_head = NULL; 287 } 288 289 /********************************************************************************************** 290 字节缓存区 : 读取 291 292 293 日期: 2020.08.01 16:00:00 294 作者: LiuYan 295 296 参数: p_head, 字节缓存区对象 297 out_buf, 字节输出 298 expected_read_len, 期望读取字节个数 299 300 返回值: >=0: 成功读取字节个数 301 302 **********************************************************************************************/ 303 int BytesBuffer_read(IN void *p_head, OUT unsigned char *out_buf, IN unsigned int expected_read_len) 304 { 305 Node_t *i = NULL; 306 unsigned int curr_copy_len = 0; 307 unsigned int curr_node_bytes = 0; 308 unsigned int already_copy_len = 0; 309 unsigned int left_copy_len = expected_read_len; 310 Node_head_t *h = (Node_head_t *)p_head; 311 312 /* 检查是否为空链表 */ 313 if (NULL == h || NULL == h->next || NULL == out_buf || 0 == expected_read_len) 314 { 315 XXXX_print_ln_E("Empty list."); 316 return 0; 317 } 318 319 /* 遍历链表 */ 320 for (i = h->next; i != NULL; ) 321 { 322 if (i->data.len_data <= i->data.len_used) 323 { 324 i = i->next; // 即将删除i节点, 准备好下一点节指令 325 XXXX_SINGLELIST_delete_first(h); 326 continue; 327 } 328 329 left_copy_len = expected_read_len - already_copy_len; 330 331 curr_node_bytes = (i->data.len_data - i->data.len_used); 332 curr_copy_len = (curr_node_bytes > left_copy_len) ? left_copy_len : curr_node_bytes; 333 334 memcpy((void *)(out_buf + already_copy_len), i->data.data + i->data.len_used, curr_copy_len); 335 336 i->data.len_used += curr_copy_len; 337 if (i->data.len_data <= i->data.len_used) 338 { 339 i = i->next; // 即将删除i节点, 准备好下一点节指令 340 XXXX_SINGLELIST_delete_first(h); 341 } 342 343 already_copy_len += curr_copy_len; 344 345 if (already_copy_len >= expected_read_len) { 346 break; 347 } 348 349 } 350 351 return already_copy_len; 352 } 353 354 /********************************************************************************************** 355 字节缓存区 : 追加 356 357 358 **** 注意: 调用完毕此函数后, 调用者请不要释放 in_buf 对应的缓冲区 *** 359 360 日期: 2020.08.01 16:00:00 361 作者: LiuYan 362 363 参数: p_head, 字节缓存区对象 364 in_buf, 待追加的字节流, 调用者请在堆中分配 365 expected_append_len, 待追加的字节流字节个数 366 367 返回值: 成功返回0,失败返回-1 368 369 **********************************************************************************************/ 370 int BytesBuffer_append(IN void *p_head, IN unsigned char *in_buf, IN unsigned int expected_append_len) 371 { 372 Node_data_t node_data; 373 Node_head_t *h = (Node_head_t *)p_head; 374 int ret = -1; 375 376 /* 参数检查 */ 377 if (NULL == h || NULL == in_buf || 0 == expected_append_len) 378 { 379 XXXX_print_ln_E("wrong para."); 380 return -1; 381 } 382 383 memset((void *)&node_data, 0, sizeof(Node_data_t)); 384 385 node_data.len_used = 0; 386 node_data.len_data = expected_append_len; 387 node_data.data = in_buf; 388 389 390 ret = XXXX_SINGLELIST_append_tail(h, &node_data); 391 392 memset((void *)&node_data, 0, sizeof(Node_data_t)); 393 394 return ret; 395 }
     1 /**
     2  * Copyright © XXXX Technologies Co., Ltd. All Rights Reserved.
     3  * @Date 2020年08月01日,下午16:00:00
     4  */
     5 
     6 /**********************************************************************************************
     7  * 文件描述: 该文件为带头结点的单链表库函数中,部分功能函数的实现
     8  * 日期: 2020.08.01 16:00:00
     9  * 作者: LiuYan
    10 **********************************************************************************************/
    11  
    12 #ifndef _NODELIST_H_
    13 #define _NODELIST_H_
    14 
    15 typedef struct node_data
    16 {
    17     unsigned int len_used;     // 节点数据已使用长度         
    18     unsigned int len_data;     // 节点数据长度         
    19     unsigned char *data; // 节点数据指定为整型
    20 }Node_data_t;
    21 
    22 /* 封装链表节点 */
    23 typedef struct node
    24 {
    25     Node_data_t data;  // 节点数据
    26     struct node *next; // 下一个节点
    27 } Node_t;
    28 
    29 /* 封装链表头节点 */
    30 typedef struct _node_head
    31 {
    32     Node_data_t data;  // 节点数据
    33     struct node *next; // 下一个节点
    34     void *lock; //
    35 } Node_head_t;
    36 
    37 /**
    38  * @函数名: XXXX_SINGLELIST_append_tail
    39  * @函数功能: 插入节点到链表尾
    40  * @参数: h:链表头结点的地址 data:待插入的数据
    41  * @返回值:插入成功返回0,失败返回非零
    42  */
    43 int XXXX_SINGLELIST_append_tail(IN Node_head_t *h, IN Node_data_t *node_data);
    44 
    45 /**
    46  * @函数名: XXXX_SINGLELIST_print
    47  * @函数功能: 遍历链表所有数据
    48  * @参数: h:链表头结点的地址
    49  * @返回值: void
    50  */
    51 void XXXX_SINGLELIST_print(IN const Node_head_t *h);
    52 
    53 /**
    54  * @函数名: XXXX_SINGLELIST_delete
    55  * @函数功能: 在链表中删除指定值的节点,如果有多个匹配则删除全部
    56  * @参数: h:链表头结点的地址 data:待删除的数据
    57  * @返回值: void
    58  */
    59 void XXXX_SINGLELIST_delete(IN Node_head_t *h);
    60 
    61 void *BytesBuffer_create(void);
    62 void BytesBuffer_delete(IN void *p_head);
    63 int BytesBuffer_read(IN void *p_head, OUT unsigned char *out_buf, IN unsigned int expected_read_len);
    64 int BytesBuffer_append(IN void *p_head, IN unsigned char *in_buf, IN unsigned int expected_append_len);
    65     
    66 #endif //_NODELIST_H
      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <pthread.h>
      4 #include <string.h>
      5 #include <sys/types.h>
      6 #include <unistd.h>
      7 #include "single_linked_list.h"
      8 
      9 #include <fcntl.h>
     10 
     11 
     12 /**********************************************************************************************
     13     锁初始化
     14 
     15 
     16     日期: 2019.09.20 10:36:00
     17     作者: LiuYan
     18 
     19     参数:    无
     20 
     21     返回值:     锁指针
     22 
     23 **********************************************************************************************/
     24 void *XXXX_mutex_init( void )
     25 {
     26     static pthread_mutex_t g_mutex_lock;
     27     int ret = 0;
     28     
     29     ret = pthread_mutex_init(&g_mutex_lock, NULL);
     30      if (ret != 0) {
     31          XXXX_print_ln_E("mutex init failed
    ");
     32          return NULL;
     33      }
     34 
     35     return (void *)&g_mutex_lock;
     36 }
     37 
     38 /**********************************************************************************************
     39     锁获取
     40 
     41 
     42     日期: 2019.09.20 10:36:00
     43     作者: LiuYan
     44 
     45     参数:    mutex, 锁指针
     46 
     47     返回值:     无
     48 
     49 **********************************************************************************************/
     50 void XXXX_mutex_take( void *mutex )
     51 {
     52     pthread_mutex_lock(mutex);
     53 
     54     return;
     55 }
     56 
     57 /**********************************************************************************************
     58     锁释放
     59 
     60 
     61     日期: 2019.09.20 10:36:00
     62     作者: LiuYan
     63 
     64     参数:    mutex, 锁指针
     65 
     66     返回值:     无
     67 
     68 **********************************************************************************************/
     69 void XXXX_mutex_give( void *mutex )
     70 {
     71     pthread_mutex_unlock(mutex);
     72     return;
     73 }
     74 
     75 unsigned char rand_byte()
     76 {
     77 //    return rand() % 26 + 0x41;
     78     return rand() % 0xff;
     79 }
     80 
     81 int rand_bytes(unsigned char **p_in_buf, int *p_rand_len)
     82 {
     83     int index = 0;
     84     int rand_len = 0; 
     85     unsigned char *p = NULL;
     86 
     87     rand_len = rand() % 1024 + 1;
     88     p = XXXX_malloc(rand_len + 1);
     89     if (NULL == p)
     90     {
     91         return -1;
     92     }
     93 
     94     memset((void *)p, 0, rand_len + 1);
     95     
     96     for (index = 0; index < rand_len; index++) {
     97         p[index] = rand_byte();
     98     }
     99 
    100     *p_in_buf = p;
    101     *p_rand_len = rand_len;
    102     return 0;
    103 }
    104 
    105 void *bb = NULL;
    106 
    107 #define char_file (0)
    108 
    109 void *thr_fn(void *arg)
    110 {
    111     int ret = 0;
    112     int rand_len = 0;
    113     unsigned char *out_buf = NULL;
    114 
    115 #if char_file    
    116     FILE * fp_r;
    117     fp_r = fopen ("file_r.txt", "w+");
    118 #else
    119 
    120     int fp_r = open("./file_r.txt", O_RDWR|O_CREAT);
    121     XXXX_print_ln_I("fp_r=%d%s", fp_r, "./file_r.txt");
    122 
    123 #endif
    124     
    125     for (; ;)
    126     {
    127         if (NULL == bb) {
    128             usleep(1000 * 200);    
    129             continue;
    130         }
    131 
    132         rand_len = rand() % 1024 + 1;
    133         out_buf = XXXX_malloc(rand_len + 1);
    134         if (NULL == out_buf)
    135         {
    136             return ((void *)0);
    137         }
    138 
    139         memset((void *)out_buf, 0, rand_len + 1);
    140         
    141         ret = BytesBuffer_read(bb, out_buf, rand_len);
    142 //        XXXX_print_ln_I("		r_ret=%d/%d:%s", ret, rand_len, out_buf);
    143 
    144 #if char_file
    145         fprintf(fp_r, "%s", out_buf);
    146 #else
    147         if (0 < ret) {
    148             write(fp_r, out_buf, ret);
    149         }
    150 #endif
    151         XXXX_free(out_buf);
    152 
    153 //        usleep(1000 * 50);
    154 
    155     }
    156 
    157 #if char_file
    158     fclose(fp_r);
    159 #else
    160     close(fp_r);
    161 #endif
    162 
    163     return ((void *)0);
    164 }
    165 
    166 
    167 int main(int argc, char *argv[])
    168 {
    169     int ret = 0;
    170     unsigned char *in_buf = NULL;
    171     int rand_len = 0;
    172     pthread_t ntid;
    173         int err = 0;
    174 
    175     bb = BytesBuffer_create();
    176     if (NULL == bb) {
    177         XXXX_print_ln_I("BytesBuffer_create Failed.");
    178         return -1;
    179     }
    180     
    181     err = pthread_create(&ntid, NULL, thr_fn, NULL);
    182     if(err != 0){
    183         XXXX_print_ln_I("can't create thread: %s",strerror(err));
    184     }
    185 #if char_file    
    186     FILE * fp_w;
    187     fp_w = fopen ("file_w.txt", "w+");
    188 #else
    189     int fp_w = open("./file_w.txt", O_RDWR|O_CREAT);
    190     XXXX_print_ln_I("fp_w=%d%s", fp_w, "./file_w.txt");
    191 #endif
    192         
    193 
    194     for (; ;)
    195     {
    196         ret = rand_bytes(&in_buf, &rand_len);
    197         if (0 != ret) {
    198             return -1;
    199         }
    200         
    201         ret = BytesBuffer_append(bb, in_buf, rand_len);
    202 
    203         //XXXX_print_ln_I("w_ret=%d/%d:%s", ret, rand_len, in_buf);
    204 #if char_file
    205         fprintf(fp_w, "%s", in_buf);
    206 #else    
    207         write(fp_w, in_buf, rand_len);        
    208 #endif
    209 
    210         // XXXX_free(in_buf); 不要释放
    211 
    212 //        XXXX_SINGLELIST_print((const Node_head_t *)bb);
    213 
    214 //        usleep(1000 * 50);
    215             
    216     }
    217 
    218 #if char_file
    219     fclose(fp_w);
    220 #else
    221     close(fp_w);
    222 #endif
    223 
    224     return 0;
    225 }
    // 移植请实现以下接口 或 宏
    #if 1
    void *XXXX_mutex_init( void );
    void XXXX_mutex_take( void *mutex );
    void XXXX_mutex_give( void *mutex );
    
    #define XXXX_malloc malloc
    #define XXXX_free free
    #define XXXX_print_ln_E(fmt, arg...)      { printf(fmt"
    ", ##arg); }
    #define XXXX_print_ln_I(fmt, arg...)      { printf(fmt"
    ", ##arg); }
    
    // 输入参数标识
    #define IN
    
    // 输出参数标识
    #define OUT
    
    // 输入输出参数标识
    #define INOUT
    
    #endif
    1 gcc *.c -o a.out -Wall -lpthread -g ; ./a.out
  • 相关阅读:
    ubuntu分屏终端
    Xcode-5.1.1更改文件盯作者
    Swift——(两)Swift访问元组
    Android Loader使用,屏幕解锁,重复荷载
    医疗信息季节:第二十三届中国国际医疗仪器设备展览会暨研讨会 思考
    UIBarButtonItem 小记边
    L轻松学习inux教程5 知识与学习bash
    Android 教你打造炫酷的ViewPagerIndicator 不仅仅是高仿MIUI
    Android Context 上下文 你必须知道的一切
    Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/13416693.html
Copyright © 2020-2023  润新知