C基礎 帶你手寫 redis adlist 雙向鏈表

来源:https://www.cnblogs.com/life2refuel/archive/2019/11/22/11911839.html
-Advertisement-
Play Games

引言 - 導航欄目 有些朋友可能對 redis 充滿著數不盡的求知欲, 也許是 redis 屬於工作, 交流(面試)的大頭戲, 不得不 ... 而自己當下對於 redis 只是停留在會用層面, 細節層面幾乎沒有涉獵. 為了更快的融於大 家, 這裡嘗試拋磚引玉. 先帶大家手寫個 redis 中最簡單的 ...


引言 - 導航欄目

  有些朋友可能對 redis 充滿著數不盡的求知欲, 也許是 redis 屬於工作, 交流(面試)的大頭戲,

不得不 ... 而自己當下對於 redis 只是停留在會用層面, 細節層面幾乎沒有涉獵. 為了更快的融於大

家, 這裡嘗試拋磚引玉. 先帶大家手寫個 redis 中最簡單的數據結構, adlist 雙向鏈表. 讓我們一

起對 redis 有個初步的認知. 本文會從下麵幾個標題展開解讀(吐槽), 歡迎交流和指正.

  1. redis adlist 解析
  2. redis config.h 分析
  3. redis setproctitle.c 分析
  4. redis atomicvar.h 分析
  5. redis zmalloc 分析
  6. redis Makefile 解析

redis 大頭是 C 寫的, 而 C 啥也不缺, 就缺手寫, OK 開始廢話手寫之旅吧 :)

前言 - redis adlist      

  全篇示例代碼都有手寫過, 不過為了素材正規, 這裡直接原封不動的引用 

github.com/antirez/redis 中相關代碼.

1. redis adlist 解析

 1 /* adlist.h - A generic doubly linked list implementation
 2  *
 3  * Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 4  * All rights reserved.
 5  *
 6  * Redistribution and use in source and binary forms, with or without
 7  * modification, are permitted provided that the following conditions are met:
 8  *
 9  *   * Redistributions of source code must retain the above copyright notice,
10  *     this list of conditions and the following disclaimer.
11  *   * Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  *   * Neither the name of Redis nor the names of its contributors may be used
15  *     to endorse or promote products derived from this software without
16  *     specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #ifndef __ADLIST_H__
32 #define __ADLIST_H__
33 
34 /* Node, List, and Iterator are the only data structures used currently. */
35 
36 typedef struct listNode {
37     struct listNode *prev;
38     struct listNode *next;
39     void *value;
40 } listNode;
41 
42 typedef struct listIter {
43     listNode *next;
44     int direction;
45 } listIter;
46 
47 typedef struct list {
48     listNode *head;
49     listNode *tail;
50     void *(*dup)(void *ptr);
51     void (*free)(void *ptr);
52     int (*match)(void *ptr, void *key);
53     unsigned long len;
54 } list;
55 
56 /* Functions implemented as macros */
57 #define listLength(l) ((l)->len)
58 #define listFirst(l) ((l)->head)
59 #define listLast(l) ((l)->tail)
60 #define listPrevNode(n) ((n)->prev)
61 #define listNextNode(n) ((n)->next)
62 #define listNodeValue(n) ((n)->value)
63 
64 #define listSetDupMethod(l,m) ((l)->dup = (m))
65 #define listSetFreeMethod(l,m) ((l)->free = (m))
66 #define listSetMatchMethod(l,m) ((l)->match = (m))
67 
68 #define listGetDupMethod(l) ((l)->dup)
69 #define listGetFreeMethod(l) ((l)->free)
70 #define listGetMatchMethod(l) ((l)->match)
71 
72 /* Prototypes */
73 list *listCreate(void);
74 void listRelease(list *list);
75 void listEmpty(list *list);
76 list *listAddNodeHead(list *list, void *value);
77 list *listAddNodeTail(list *list, void *value);
78 list *listInsertNode(list *list, listNode *old_node, void *value, int after);
79 void listDelNode(list *list, listNode *node);
80 listIter *listGetIterator(list *list, int direction);
81 listNode *listNext(listIter *iter);
82 void listReleaseIterator(listIter *iter);
83 list *listDup(list *orig);
84 listNode *listSearchKey(list *list, void *key);
85 listNode *listIndex(list *list, long index);
86 void listRewind(list *list, listIter *li);
87 void listRewindTail(list *list, listIter *li);
88 void listRotate(list *list);
89 void listJoin(list *l, list *o);
90 
91 /* Directions for iterators */
92 #define AL_START_HEAD 0
93 #define AL_START_TAIL 1
94 
95 #endif /* __ADLIST_H__ */

首先手寫的是 adlist.h 雙向鏈表的頭文件, 對於這個頭文件有幾點要聊一聊的. 

1.1' redis 中頭文件格式目前沒有統一

#ifndef __ADLIST_H__    #endif
#ifndef __REDIS_HELP_H  #endif
#ifndef __ZMALLOC_H     #endif

可能也是, redis 這個項目維護和開發都十年多了. 代碼風格在變(千奇百怪)也是正常.

這裡推薦第三種寫法 -> __{不帶尾碼文件名}_H

1.2' adlist.h 中函數命名隨意

void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotate(list *list);
void listJoin(list *l, list *o);

命名隨意不是個好習慣, 推薦參數名強區分. 例如下麵這樣固定格式

extern void listReleaseIterator(listIter * iter);
extern list * listDup(list * l);
extern listNode * listSearchKey(list * l, void * key);
extern listNode * listIndex(list * l, long index);
extern void listRewind(list * l, listIter * iter);
extern void listRewindTail(list * l, listIter * iter);
extern void listRotate(list * l);
extern void listJoin(list * l, list * o);

寫完 adlist.h 介面定義部分, 相信有些人對待實現的 adlist.c 也有了大致輪廓了吧 :)

  1 /* adlist.c - A generic doubly linked list implementation
  2  *
  3  * Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
  4  * All rights reserved.
  5  *
  6  * Redistribution and use in source and binary forms, with or without
  7  * modification, are permitted provided that the following conditions are met:
  8  *
  9  *   * Redistributions of source code must retain the above copyright notice,
 10  *     this list of conditions and the following disclaimer.
 11  *   * Redistributions in binary form must reproduce the above copyright
 12  *     notice, this list of conditions and the following disclaimer in the
 13  *     documentation and/or other materials provided with the distribution.
 14  *   * Neither the name of Redis nor the names of its contributors may be used
 15  *     to endorse or promote products derived from this software without
 16  *     specific prior written permission.
 17  *
 18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 28  * POSSIBILITY OF SUCH DAMAGE.
 29  */
 30 
 31 
 32 #include <stdlib.h>
 33 #include "adlist.h"
 34 #include "zmalloc.h"
 35 
 36 /* Create a new list. The created list can be freed with
 37  * AlFreeList(), but private value of every node need to be freed
 38  * by the user before to call AlFreeList().
 39  *
 40  * On error, NULL is returned. Otherwise the pointer to the new list. */
 41 list *listCreate(void)
 42 {
 43     struct list *list;
 44 
 45     if ((list = zmalloc(sizeof(*list))) == NULL)
 46         return NULL;
 47     list->head = list->tail = NULL;
 48     list->len = 0;
 49     list->dup = NULL;
 50     list->free = NULL;
 51     list->match = NULL;
 52     return list;
 53 }
 54 
 55 /* Remove all the elements from the list without destroying the list itself. */
 56 void listEmpty(list *list)
 57 {
 58     unsigned long len;
 59     listNode *current, *next;
 60 
 61     current = list->head;
 62     len = list->len;
 63     while(len--) {
 64         next = current->next;
 65         if (list->free) list->free(current->value);
 66         zfree(current);
 67         current = next;
 68     }
 69     list->head = list->tail = NULL;
 70     list->len = 0;
 71 }
 72 
 73 /* Free the whole list.
 74  *
 75  * This function can't fail. */
 76 void listRelease(list *list)
 77 {
 78     listEmpty(list);
 79     zfree(list);
 80 }
 81 
 82 /* Add a new node to the list, to head, containing the specified 'value'
 83  * pointer as value.
 84  *
 85  * On error, NULL is returned and no operation is performed (i.e. the
 86  * list remains unaltered).
 87  * On success the 'list' pointer you pass to the function is returned. */
 88 list *listAddNodeHead(list *list, void *value)
 89 {
 90     listNode *node;
 91 
 92     if ((node = zmalloc(sizeof(*node))) == NULL)
 93         return NULL;
 94     node->value = value;
 95     if (list->len == 0) {
 96         list->head = list->tail = node;
 97         node->prev = node->next = NULL;
 98     } else {
 99         node->prev = NULL;
100         node->next = list->head;
101         list->head->prev = node;
102         list->head = node;
103     }
104     list->len++;
105     return list;
106 }
107 
108 /* Add a new node to the list, to tail, containing the specified 'value'
109  * pointer as value.
110  *
111  * On error, NULL is returned and no operation is performed (i.e. the
112  * list remains unaltered).
113  * On success the 'list' pointer you pass to the function is returned. */
114 list *listAddNodeTail(list *list, void *value)
115 {
116     listNode *node;
117 
118     if ((node = zmalloc(sizeof(*node))) == NULL)
119         return NULL;
120     node->value = value;
121     if (list->len == 0) {
122         list->head = list->tail = node;
123         node->prev = node->next = NULL;
124     } else {
125         node->prev = list->tail;
126         node->next = NULL;
127         list->tail->next = node;
128         list->tail = node;
129     }
130     list->len++;
131     return list;
132 }
133 
134 list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
135     listNode *node;
136 
137     if ((node = zmalloc(sizeof(*node))) == NULL)
138         return NULL;
139     node->value = value;
140     if (after) {
141         node->prev = old_node;
142         node->next = old_node->next;
143         if (list->tail == old_node) {
144             list->tail = node;
145         }
146     } else {
147         node->next = old_node;
148         node->prev = old_node->prev;
149         if (list->head == old_node) {
150             list->head = node;
151         }
152     }
153     if (node->prev != NULL) {
154         node->prev->next = node;
155     }
156     if (node->next != NULL) {
157         node->next->prev = node;
158     }
159     list->len++;
160     return list;
161 }
162 
163 /* Remove the specified node from the specified list.
164  * It's up to the caller to free the private value of the node.
165  *
166  * This function can't fail. */
167 void listDelNode(list *list, listNode *node)
168 {
169     if (node->prev)
170         node->prev->next = node->next;
171     else
172         list->head = node->next;
173     if (node->next)
174         node->next->prev = node->prev;
175     else
176         list->tail = node->prev;
177     if (list->free) list->free(node->value);
178     zfree(node);
179     list->len--;
180 }
181 
182 /* Returns a list iterator 'iter'. After the initialization every
183  * call to listNext() will return the next element of the list.
184  *
185  * This function can't fail. */
186 listIter *listGetIterator(list *list, int direction)
187 {
188     listIter *iter;
189 
190     if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
191     if (direction == AL_START_HEAD)
192         iter->next = list->head;
193     else
194         iter->next = list->tail;
195     iter->direction = direction;
196     return iter;
197 }
198 
199 /* Release the iterator memory */
200 void listReleaseIterator(listIter *iter) {
201     zfree(iter);
202 }
203 
204 /* Create an iterator in the list private iterator structure */
205 void listRewind(list *list, listIter *li) {
206     li->next = list->head;
207     li->direction = AL_START_HEAD;
208 }
209 
210 void listRewindTail(list *list, listIter *li) {
211     li->next = list->tail;
212     li->direction = AL_START_TAIL;
213 }
214 
215 /* Return the next element of an iterator.
216  * It's valid to remove the currently returned element using
217  * listDelNode(), but not to remove other elements.
218  *
219  * The function returns a pointer to the next element of the list,
220  * or NULL if there are no more elements, so the classical usage patter
221  * is:
222  *
223  * iter = listGetIterator(list,<direction>);
224  * while ((node = listNext(iter)) != NULL) {
225  *     doSomethingWith(listNodeValue(node));
226  * }
227  *
228  * */
229 listNode *listNext(listIter *iter)
230 {
231     listNode *current = iter->next;
232 
233     if (current != NULL) {
234         if (iter->direction == AL_START_HEAD)
235             iter->next = current->next;
236         else
237             iter->next = current->prev;
238     }
239     return current;
240 }
241 
242 /* Duplicate the whole list. On out of memory NULL is returned.
243  * On success a copy of the original list is returned.
244  *
245  * The 'Dup' method set with listSetDupMethod() function is used
246  * to copy the node value. Otherwise the same pointer value of
247  * the original node is used as value of the copied node.
248  *
249  * The original list both on success or error is never modified. */
250 list *listDup(list *orig)
251 {
252     list *copy;
253     listIter iter;
254     listNode *node;
255 
256     if ((copy = listCreate()) == NULL)
257         return NULL;
258     copy->dup = orig->dup;
259     copy->free = orig->free;
260     copy->match = orig->match;
261     listRewind(orig, &iter);
262     while((node = listNext(&iter)) != NULL) {
263         void *value;
264 
265         if (copy->dup) {
266             value = copy->dup(node->value);
267             if (value == NULL) {
268                 listRelease(copy);
269                 return NULL;
270             }
271         } else
272             value = node->value;
273         if (listAddNodeTail(copy, value) == NULL) {
274             listRelease(copy);
275             return NULL;
276         }
277     }
278     return copy;
279 }
280 
281 /* Search the list for a node matching a given key.
282  * The match is performed using the 'match' method
283  * set with listSetMatchMethod(). If no 'match' method
284  * is set, the 'value' pointer of every node is directly
285  * compared with the 'key' pointer.
286  *
287  * On success the first matching node pointer is returned
288  * (search starts from head). If no matching node exists
289  * NULL is returned. */
290 listNode *listSearchKey(list *list, void *key)
291 {
292     listIter iter;
293     listNode *node;
294 
295     listRewind(list, &iter);
296     while((node = listNext(&iter)) != NULL) {
297         if (list->match) {
298             if (list->match(node->value, key)) {
299                 return node;
300             }
301         } else {
302             if (key == node->value) {
303                 return node;
304             }
305         }
306     }
307     return NULL;
308 }
309 
310 /* Return the element at the specified zero-based index
311  * where 0 is the head, 1 is the element next to head
312  * and so on. Negative integers are used in order to count
313  * from the tail, -1 is the last element, -2 the penultimate
314  * and so on. If the index is out of range NULL is returned. */
315 listNode *listIndex(list *list, long index) {
316     listNode *n;
317 
318     if (index < 0) {
319         index = (-index)-1;
320         n = list->tail;
321         while(index-- && n) n = n->prev;
322     } else {
323         n = list->head;
324         while(index-- && n) n = n->next;
325     }
326     return n;
327 }
328 
329 /* Rotate the list removing the tail node and inserting it to the head. */
330 void listRotate(list *list) {
331     listNode *tail = list->tail;
332 
333     if (listLength(list) <= 1) return;
334 
335     /* Detach current tail */
336     list->tail = tail->prev;
337     list->tail->next = NULL;
338     /* Move it as head */
339     list->head->prev = tail;
340     tail->prev = NULL;
341     tail->next = list->head;
342     list->head = tail;
343 }
344 
345 /* Add all the elements of the list 'o' at the end of the
346  * list 'l'. The list 'other' remains empty but otherwise valid. */
347 void listJoin(list *l, list *o) {
348     if (o->head)
349         o->head->prev = l->tail;
350 
351     if (l->tail)
352         l->tail->next = o->head;
353     else
354         l->head = o->head;
355 
356     if (o->tail) l->tail = o->tail;
357     l->len += o->len;
358 
359     /* Setup other as an empty list. */
360     o->head = o->tail = NULL;
361     o->len = 0;
362 }

是的, 就是這樣, 就是這樣簡單. 

我們稍微多講點, 其實對於 listCreate 可以寫的更加簡約, 不是嗎? 

struct list * listCreate(void) {
    return zcalloc(sizeof(struct list));  
}

好了, 那我們繼續交流(吐槽)吧.

 

1.3' 代碼括弧 { } 位置隨意

這不是個好習慣, 畢竟誰也不喜歡兩面派. 大項目還是得需要在大方向上統一風格和約束.

 

1.4' struct listIter::direction 不一定是個很好的設計

direction 通過與 AL_START_HEAD or AL_START_TAIL 巨集進行運行時比對, 來區分遍歷的方向. 覺得

有點浪費. 內心更傾向於幹掉運行時比對, 從一開始用戶就應該知道該怎麼遍歷更好, 畢竟這是所有數據結構

的標桿. 

 

❤ 恭喜大家, 到這我們關於 redis adlist 最基礎最簡單的數據結構已經手寫分析完畢, 後面可以不用看了.

謝謝大家捧場 ~ 

 

正文 - adlist 周邊

   簡單愉快的背後總會有些更深的不可捉摸. 離開了奶頭樂, 我們將從 adlist.c 中一行代碼, 正式開啟

我們此次探險之旅.

#include "zmalloc.h"

2. redis config.h 分析

  同樣在 zmallo.c 中我們發現瞭如下兩行代碼, 這就是我們要說的一個主體之一 config.h

#include "config.h"
#include "atomicvar.h"

config.h 主要作用是用於確定程式的運行環境, 例如是什麼操作系統, 是什麼位元組序, 要不要啟用某些功能

/*
 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
 *

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.LinkedHashSet 的概述和使用 llinkedHashSet 的特點: 是唯一能保證怎麼存就怎麼輸出的 set 集合,並且去重覆 1 LinkedHashSet<String> linkSet = new LinkedHashSet<>(); 2 /* LinkedHashSet可以保 ...
  • kafka 由於它自身的高性能發送與消費能力,而受到廣大企業的喜歡,所以我們就先看看kafka 一些源碼實現如下: 這段代碼摘抄的是,kafka源碼 生產者發送消息demo(kafka.examples.Producer) 裡面的一個片段,主要是涉及到兩個知識點,一個是非同步發送消息, 回調函數的實現 ...
  • 最近由於一些原因,接觸了一些房產中介,其中發現一些房產中介說起房產相關的東西來,是滔滔不絕,對於所在片區樓盤各個優略,性價比幾許之類的,爛熟於心。 說到樓盤一些動情之處,時而會陷入沉思,時而張牙舞爪。給人的感覺非常用心專業。額,說這些和代碼有什麼關係?其實我從這些中介的身上發現他們對於自己所從事 的 ...
  • 本文源碼: "GitHub·點這裡" || "GitEE·點這裡" 一、生活場景 1、場景描述 變色龍是爬行動物,是非常奇特的動物,它有適於樹棲生活的種種特征和行為,身體也會隨著環境的變化而變化出適應環境的顏色,非常神奇。下麵基於狀態模式對該變化過程進行描述。 2、代碼實現 二、狀態模式 1、基礎概 ...
  • Kafka的結構與RabbitMQ類似,消息生產者向Kafka伺服器發送消息,Kafka接收消息後,再投遞給消費者。 生產者的消費會被髮送到Topic中,Topic中保存著各類數據,每一條數據都使用鍵、值進行保存。 每一個Topic中都包含一個或多個物理分區(Partition),分區維護著消息... ...
  • 1.持久化 1.1 持久化簡介 持久化(Persistence),持久化是將程式數據在持久狀態和瞬時狀態間轉換的機制,即把數據(如記憶體中的對象)保存到可永久保存的存儲設備中(如磁碟)。 1.2 redis持久化 redis為記憶體資料庫,為了防止伺服器宕機以及伺服器進程退出後,伺服器數據丟失,Redi ...
  • 錯誤與異常 錯誤,可以理解程式本身的錯誤,例如語法錯誤。而異常則更偏向於程式運行不符合預期或者不符合正常流程;對於 PHP 語言而言,處理錯誤和處理異常使用的機制完全不同,因此很容易讓人產生困惑。 例如,我們希望通過捕獲異常來處理除數為 0 的情況,但是在捕獲到異常之前,PHP 就觸發了錯誤。 1 ...
  • 在上一篇文章中我們對lock和AbstractQueuedSynchronizer(AQS)有了初步的認識。在同步組件的實現中,AQS是核心部分,同步組件的實現者通過使用AQS提供的模板方法實現同步組件語義,AQS則實現了對同步狀態的管理,以及對阻塞線程進行排隊,等待通知等等一些底層的實現處理。AQ... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...