線程終止

来源:https://www.cnblogs.com/yunfan1024/archive/2019/07/24/11237866.html
-Advertisement-
Play Games

POSIX線程終止相關函數 線程終止方式 單個線程可以通過3種方式退出,可以在不終止整個進程的情況下,停止線程的控制流。 (1)線程可以直接從啟動常式(也就是線程函數)中返回,即執行return語句,返回值是線程的退出碼。 (2)線程可以被同一進程中的其他線程取消。即其他線程調用pthread_ca ...


POSIX線程終止相關函數

//頭文件
#include <pthread.h>
//API函數
int
pthread_join(pthread_t thread, void **value_ptr); void pthread_exit(void *retval); int pthread_cancel(pthread_t thread); void pthread_cleanup_push(void (*routine)(void*), void *arg); void pthread_cleanup_pop(int execute);

線程終止方式

  單個線程可以通過3種方式退出,可以在不終止整個進程的情況下,停止線程的控制流。

(1)線程可以直接從啟動常式(也就是線程函數)中返回,即執行return語句,返回值是線程的退出碼。

(2)線程可以被同一進程中的其他線程取消。即其他線程調用pthread_cancel()函數。

(3)線程函數本身調用pthread_exit()。函數返回線程退出後傳出來的retval指針。

【說明】

1. pthread_exit()函數的參數retval是一個無類型指針,這與pthread_create函數中傳給啟動常式的單個參數類似。進程中的其他線程可以通過調用pthread_join函數來訪問到這個指針。

2. 調用pthread_join()函數將一直阻塞,直到指定的線程(參數thread)終止,終止方式是上面所描述的3種方式。

(1) 如果線程簡單地從啟動常式中返回,即執行return語句,pthread_join函數的參數value_ptr就包含返回碼。

(2) 如果線程被其他線程取消,pthread_join函數的參數value_ptr指向的記憶體單元就被設置為PTHREAD_CANCELED。

(3) 如果線程是調用pthread_exit()函數退出的,pthread_join函數的參數value_ptr將能獲取到pthread_exit()函數返回的retval指針。

3. 如果對線程的返回值不感興趣,可以將pthread_exit函數的retval置為NULL,這種情況下,pthread_join()函數仍可以等待指定的線程終止,但並不會獲取到線程的終止狀態。

實例1:獲取已終止的線程的退出碼。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void* thr_fn1(void *arg)
 6 {
 7     printf("thread1 returning***\n");
 8     return (void*)1;
 9 }
10 
11 void* thr_fn2(void *arg)
12 {
13     printf("thread2 exiting###\n");
14     pthread_exit((void*)2);
15 }
16 
17 int main(int argc, char *argv[])
18 {
19     int err;
20     pthread_t tid1, tid2;
21     void *tret;
22     
23     if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){
24         printf("Error: can`t create thread1!\n");
25     }
26     if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
27         printf("Error: can`t create thread2!\n");
28     }
29     
30     if((err=pthread_join(tid1, &tret)) != 0){
31         printf("Error: can`t join with thread1!\n");
32     }
33     printf("thread1 exit code: %d\n", (int*)tret);
34     if((err=pthread_join(tid2, &tret)) != 0){
35         printf("Error: can`t join with thread2!\n");
36     }
37     printf("thread2 exit code: %d\n", (int*)tret);
38     
39     return 0;
40 }
View Code

## 運行結果:

thread1 returning***
thread2 exiting###
thread1 exit code: 1
thread2 exit code: 2

【分析】從運行結果可以看出,當一個線程通過調用pthread_exit退出或者簡單地從啟動常式中返回(return語句),進程中的其他線程可以通過調用pthread_join函數獲得該進程的退出狀態。

【說明】pthread_create和pthread_exit函數的無類型指針參數可以傳遞的值不止一個,這個指針可以是包含複雜信息的結構的地址,但是註意的是,這個結構指針指向的記憶體空間在調用者(線程函數)完成調用以後仍然是有效的。例如,在調用線程的棧區上分配了該結構,那麼其他線程在使用這個結構時記憶體內容可能已經改變了。又如,線程在自己的棧區上分配了一個結構,然後把指向這個結構的指針傳給pthread_exit,那麼調用pthread_join的線程試圖使用該結構時,這個棧區有可能已經被撤銷,這塊記憶體也已另作他用。

實例2:用局部(自動)變數(在棧區上分配的變數)作為pthread_exit的參數時出現的問題。

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

typedef struct foo{
    int a,b,c,d;
}FOO;

void printfoo(const char *s, const FOO *fp)
{
    printf("%s", s);
    printf("  structure at %p\n", fp);
    printf("  foo.a = %d\n", fp->a);
    printf("  foo.b = %d\n", fp->b);
    printf("  foo.c = %d\n", fp->c);
    printf("  foo.d = %d\n", fp->d);
}

void* thr_fn1(void *arg)
{
    FOO foo={1,2,3,4};
    
    printf("thread 1: thread_id=%lu\n", pthread_self());
    printfoo("thread 1:\n", &foo);
    printf("****** thread 1 exiting\n");
    pthread_exit((void*)&foo);
}

void* thr_fn2(void *arg)
{
    printf("thread 2: thread_id=%lu\n", pthread_self());
    printf("###### thread 2 exiting\n");
    pthread_exit((void*)2);
}

int main(int argc, char *argv[])
{
    int err;
    pthread_t tid1,tid2;
    FOO *fp;
    
    printf("Parent starting the first thread\n");
    if((err=pthread_create(&tid1, NULL, thr_fn1, NULL) != 0)){
        printf("Error: can`t create thread1!\n");
    }
    if((err=pthread_join(tid1, (void*)&fp)) != 0){
        printf("Error: can`t join with thread1!\n");
    }
    
    sleep(1);
    printf("\nParent starting the second thread\n");
    if((err=pthread_create(&tid2, NULL, thr_fn2, NULL) != 0)){
        printf("Error: can`t create thread2!\n");
    }
    sleep(1);
    printfoo("\nParent thread:\n", fp);
    
    return 0;
}
View Code

## 運行結果:

Parent starting the first thread
thread 1: thread_id=140128023041792
thread 1:
structure at 0x7f7219094f00
foo.a = 1
foo.b = 2
foo.c = 3
foo.d = 4
****** thread 1 exiting

Parent starting the second thread
thread 2: thread_id=140128023041792
###### thread 2 exiting

Parent thread:
structure at 0x7f7219094f00
foo.a = 420042496
foo.b = 32626
foo.c = 1
foo.d = 0

【分析】從運行結果可以看出,當主線程訪問局部結構時,結構的內容(線上程tid1的棧上分配的)已經發生改變了。即主線程試圖訪問已退出的tid1線程傳給它的結構時,由於該結構是線上程tid1的棧區上定義的,當線程退出時,棧區的記憶體空間也隨之釋放掉了,所以讀取到的內容是隨機值。為瞭解決這個問題,可以使用動態記憶體分配(malloc)或者使用全局結構。

 線程取消機制

  在預設情況下,pthread_cancel()函數會使得thread標識的線程的行為表現為如同調用了參數為PTHREAD_CANCELED的pthread_exit()函數,即pthread_exit(PTHREAD_CANCELED)。但是,線程可以選擇忽略取消或者控制如何被取消。【註意】pthread_cancel函數並不等待線程終止,它僅僅是提出請求。

實例3:線程取消的使用。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 int done = 0;
 6 int cnt = 0;
 7 
 8 void* thr_fn(void *arg)
 9 {
10     //printf("new thread start\n"); //線程取消點
11     while(!done){
12         cnt++;
13         if(cnt == 10)
14             pthread_testcancel(); //自己設置一個線程取消點
15     }
16     return ((void*)1);
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     int err;
22     pthread_t tid;
23     void *tret;
24     
25     if(0 != (err=pthread_create(&tid, NULL, thr_fn, NULL))){
26         printf("Error: can`t create thread\n");
27         return -1;
28     }
29     pthread_cancel(tid);
30     if(0 != (err=pthread_join(tid, &tret))){
31         printf("Error: can`t join with thread\n");
32         return -2;
33     }
34     printf("thread exit code: %d\n", (int*)tret);
35     printf("cnt = %d\n", cnt);
36     
37     return 0;
38 }
View Code

 ## 運行結果:

thread exit code: -1
cnt = 10

【分析】在主線程中調用了pthread_cancel(tid),線上程的啟動常式中,當cnt==10時,調用了pthread_testcancel()函數,這個函數是表示設置一個函數取消點。當線程運行到取消點的時候,線程就會終止。線程退出時的狀態碼為-1,說明瞭線程的退出是非正常退出的,而正常退出是的狀態碼應該是1。

【說明】線程在收到pthread_cancel的取消請求後,可能會忽略、立即取消線程或者運行至取消點再取消線程。系統預設情況下,收到取消請求後,線程會繼續運行,直到遇到下一個取消點處終止線程。

取消點:取消點是線程檢查它是否被取消的一個點,posix保證在一些函數中會自帶取消點,如sleep,accept,write,read,printf等,當執行上述函數時,自動觸發線程取消點,使線程終止。

【擴展】實際上,線程是否取消除了與取消點有關外,還和線程的取消狀態有關。取消狀態分為:PTHREAD_CANCEL_ENABLE(可取消狀態,這是系統預設的線程取消狀態);PTHREAD_CANCEL_DISABLE(不可取消狀態)。當線程的取消狀態是PTHREAD_CANCEL_DISABLE時,即使線程收到取消請求在取消點也不會取消線程,直到可取消狀態變更為PTHREAD_CANCEL_ENABLE時,線程才會在下一個取消點取消線程。

//設置線程取消點函數
void pthread_testcancel(void);

//
修改線程的取消狀態函數 int pthread_setcancelstate(int state, int *oldstate);

【參數說明】
state:設置新狀態值。
oldstate:存放原先的取消狀態。
【函數說明】該函數會在函數內部設置一個取消點,調用該函數時,如果收到一個取消請求,且取消狀態是可取消的,就會立即將線程取消。如果取消狀態為不可取消,且沒有取消請求,就不會取消,直到兩者條件都滿足時才會取消函數。

  線上程的屬性中還有一個屬性與線程的取消有關,即它的取消類型,之前我們所說的取消屬於推遲取消,即在調用pthread_cancel函數後,需要等到線程運行至一個取消點時,線程才會被取消而終止線程。

但是,還有一種取類型為非同步取消,即當調用pthread_cancel後,線程就會被立即取消,而不用等到線程運行至取消點時再取消線程,取消類型同取消狀態一樣可以修改。

//修改線程的取消類型函數
int pthread_setcanceltype(int type, int *oldtype);
【參數說明】
type:設置新的取消類型。
oldtype:存放原先的取消類型。
【函數說明】取消類型有:PTHREAD_CANCEL_DEFERRED、PTHREAD_CANCEL_ASYNCHRONOUS。
PTHREAD_CANCEL_DEFERRED:線程接收到取消請求後,直到運行至"取消點"後才取消線程。

PTHREAD_CANCEL_ASYNCHRONOUS:線程接收到取消請求後,立即取消線程。

<說明>線程的“取消狀態”和“取消類型”存在於任意一個新建線程中,包括主線程,預設設置是PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DEFERRED。

 線程清理處理程式

  線程可以安排它退出時需要調用的函數,這與進程在退出時可以用atexit函數安排退出是類似的。這樣的函數被稱為線程處理清理程式(thread cleanup handler)。一個線程可以建立多個清理處理程式。處理程式記錄在棧中,也就是說它們的執行順序與它們註冊時相反。

//註冊線程清理處理程式
void pthread_cleanup_push(void (*rtn)(void*), void *arg);
【參數】
rtn:線程退出時被調用的清理函數。
arg:傳入給rtn的參數。
//解除線程清理處理程式 void pthread_cleanup_pop(int execute);

【說明】當線程執行以下動作時,清理函數rtn是由phtread_cleanup_push函數調度的,調用時只傳入一個參數arg。

  • 線程函數調用pthread_exit時;
  • 響應取消線程請求時;
  • 用非零execute參數調用pthread_cleanup_pop時。

<註意> 如果pthread_cleanup_pop的execute參數如果設置為0,清理函數rtn將不被調用,也就是說,線程函數執行pthread_cleanup_pop(0)時,在phtread_cleanup_push中註冊的清理函數rtn將不被執行,但是

pthread_cleanup_pop函數仍將刪除上次在phtread_cleanup_push函數中註冊的清理處理程式(或函數)。

【擴展】這兩個函數有一個限制,因為它們可以實現為巨集,pthread_cleanup_push()與pthread_cleanup_pop()必須成對的出現線上程函數相同的作用域中。

pthread_cleanup_push的巨集定義可以包含字元 { ,這種情況下,在與pthread_cleanup_pop的巨集定義中要有對應的匹配字元 } 。示例如下:

#define pthread_cleanup_push(rtn,arg) { \
struct _pthread_handler_rec __cleanup_handler, **__head; \
__cleanup_handler.rtn = rtn; \
__cleanup_handler.arg = arg; \
(void) pthread_getspecific(_pthread_handler_key, &__head); \
__cleanup_handler.next = *__head; \
*__head = &__cleanup_handler;

#define pthread_cleanup_pop(ex) \
*__head = __cleanup_handler.next; \
if (ex) (*__cleanup_handler.rtn)(__cleanup_handler.arg); \
}

  如果pthread_cleanup_pop函數的參數execute設置為0,清理將不被調用。但不管發生上述哪種情況,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用建立的清理處理程式。示例如下:

pthread_cleanup_push(routine, &arg);
......
pthread_cleanup_pop(0);
pthread_exit((void*)1);

<說明>當線程函數執行到pthread_exit函數時,pthread_cleanup_pop函數將解除pthread_cleanup_push函數註冊的清理處理函數routine,但是不會執行routine中的函數體代碼。

實例4:使用線程清理處理程式。

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <pthread.h>
 4 
 5 void cleanup(void *arg)
 6 {
 7     printf("cleanup: %s\n", (char*)arg);
 8 }
 9 
10 void* thr_fn1(void *arg)
11 {
12     printf("thread 1 start\n");
13     pthread_cleanup_push(cleanup, "thread 1 first handler");
14     pthread_cleanup_push(cleanup, "thread 1 secend handler");
15     printf("thread 1 push complete\n");
16     if(arg)
17         return ((void*)11);
18     pthread_cleanup_pop(0);
19     pthread_cleanup_pop(0);
20     return ((void*)12);
21 }
22 
23 void* thr_fn2(void *arg)
24 {
25     printf("thread 2 start\n");
26     pthread_cleanup_push(cleanup, "thread 2 first handler");
27     pthread_cleanup_push(cleanup, "thread 2 secend handler");
28     printf("thread 2 push complete\n");
29     if(arg)
30         pthread_exit((void*)21);
31     pthread_cleanup_pop(0);
32     pthread_cleanup_pop(0);
33     pthread_exit((void*)22);
34 }
35 
36 void* thr_fn3(void *arg)
37 {
38     printf("thread 3 start\n");
39     pthread_cleanup_push(cleanup, "thread 3 first handler");
40     pthread_cleanup_push(cleanup, "thread 3 secend handler");
41     printf("thread 3 push complete\n");
42     if(arg)
43         pthread_exit((void*)31);
44     pthread_cleanup_pop(0);
45     pthread_cleanup_pop(0);
46     pthread_exit((void*)32);
47 }
48 
49 int main(int argc, char *argv[])
50 {
51     int err;
52     pthread_t tid1, tid2, tid3;
53     void *tret;
54     
55     if(0 != (err = pthread_create(&tid1, NULL, thr_fn1, (void*)1) )){
56         printf("Error: can`t create thread 1\n");
57     }
58     if(0 != (err = pthread_create(&tid2, NULL, thr_fn2, (void*)1) )){
59         printf("Error: can`t create thread 2\n");
60     }
61     if(0 != (err = pthread_create(&tid3, NULL, thr_fn3, NULL) )){
62         printf("Error: can`t create thread 3\n");
63     }
64     
65     if(0 != (err = pthread_join(tid1, &tret))){
66         printf("Error: can`t join with thread 1\n");
67     }
68     printf("thread 1 exit code: %d\n", (int*)tret);
69     if(0 != (err = pthread_join(tid2, &tret))){
70         printf("Error: can`t join with thread 2\n");
71     }
72     printf("thread 2 exit code: %d\n", (int*)tret);
73     if(0 != (err = pthread_join(tid3, &tret))){
74         printf("Error: can`t join with thread 3\n");
75     }
76     printf("thread 3 exit code: %d\n", (int*)tret);
77     
78     return 0;
79 }
View Code

## 運行結果:

thread 1 start
thread 1 push complete
thread 1 exit code: 11
thread 2 start
thread 2 push complete
cleanup: thread 2 secend handler
cleanup: thread 2 first handler
thread 3 start
thread 3 push complete
thread 2 exit code: 21
thread 3 exit code: 32

## 分析:

1、線程1是直接執行return語句終止線程的,即return ((void*)11); 沒有執行到pthread_cleanup_pop(0); 線程就終止了,並沒有執行在pthread_cleanup_push函數中註冊的清理函數cleanup,因為它不滿足註冊的清理函數被調用的那3個條件中的任何一個,所以線程1的退出碼為11,即thread 1 exit code: 11。

2、線程2是執行到pthread_exit((void*)21); 時線程就終止了,滿足已註冊的清理函數被調用的條件。這時將調用在pthread_cleanup_push中註冊的清理函數cleanup。從運行結果中可以看到,調用順序和註冊順序是相反的,這是因為清理函數是記錄在棧中的,而棧是一種先進後出的數據結構。特別值得註意的是,

pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)22);

線上程2的啟動常式函數體中,上面的3條語句是沒有執行到的,從線程2的退出碼結果為:thread 2 exit code: 21 可以證明這一點。

3、線程3是執行到pthread_exit((void*)32); 時線程終止,並且線上程函數體中是執行了兩個pthread_cleanup_pop(0); 語句的,所以pthread_cleanup_pop函數會刪除掉在前面的pthread_cleanup_push中註冊的清理函數cleanup,但是不會執行清理處理函數,線程3的退出碼為:thread 3 exit code: 32。

綜上所述,可以得出以下結論:

1、如果線程是通過從它的啟動常式中調用return語句而終止的話,它的清理處理程式就不會被調用。

2、清理處理程式是按照與它們註冊時相反的順序被調用的。

進程和線程原語的比較

  在預設狀態下,線程的終止狀態會保存直到對該線程調用pthread_join。但是如果線程已經被分離,線程的底層存儲資源可以線上程終止時立即被收回。線上程被分離後,我們不能用pthread_join函數等待它的終止狀態,因為對分離線程調用pthread_join會產生未定義的行為。分離線程可以調用pthread_detach()函數。

//線程分離函數
int pthread_detach(pthread_t thread);
【參數】thread:待分離的線程ID值。
【返回值】成功,返回0;失敗,返回錯誤碼。

 


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

-Advertisement-
Play Games
更多相關文章
  • 本地化(Localization)也就是多語言功能,藉此用戶能夠選擇他的母語或熟悉的語言來使用系統,這顯然非常有利於軟體系統推向國際化。一個應用程式的UI界面至少有一種語言,ABP開發框架提供了一個彈性的多語言框架,可以簡化我們在多語言方面的開發時間。利用ABP實現多語言只需要簡單地完成三個步驟:建... ...
  • 最近因為工作需要,客戶那邊工程師使用的是JAVA語言開發的程式,我們這邊平臺中是用C#語言開發的,因為有些操作必須統一,所以我在網上查找解決方法,自己也實踐過,在這裡做個筆記吧,分享一下。 一、使用C#編寫com組件 開發環境 :VS2017 1、新建工程:CalcTest(類庫項目,根據自己喜好需 ...
  • 一、RabbitMQ介紹1、RabbitMQ簡介RabbitMQ是一個消息代理:它接受和轉發消息。你可以把它想象成一個郵局:當你把你想要發佈的郵件放在郵箱中時,你可以確定郵差先生最終將郵件發送給你的收件人。在這個比喻中,RabbitMQ是郵政信箱,郵局和郵遞員。RabbitMQ和郵局的主要區別在於它 ...
  • C#: 使用緩衝區進行文件下載操作,避免下載超大文件時記憶體占用過大 ...
  • IOCContainer文件: Global.asax: 使用: ...
  • LinuxShell腳本——函數 摘要:本文主要學習了Shell中函數的定義和使用。 函數的定義 Shell函數的本質是一段可以重覆使用的腳本代碼,這段代碼被提前編寫好了,放在了指定的位置,使用時直接調取即可。 函數定義的語法 Shell函數定義的語法格式如下: 對各個部分的說明: 由 {} 包圍的 ...
  • LinuxShell腳本——迴圈結構 摘要:本文主要學習了Shell腳本中的迴圈結構。 while迴圈 基本語法 while迴圈是最簡單的一種迴圈,如果條件滿足則執行迴圈里的語句,如果條件不滿足則退出迴圈: 註意,在迴圈體命令中必須有相應的語句使得條件“不成立”,只有這樣才能最終退出迴圈,否則就成了 ...
  • 由於銀行對網路有控制,連接銀行虛擬桌面,就不能訪問外網,解決如下: route print 能查看目前的活動路由、添加過的永久路由等信息 route add 10.60.4.10 mask 255.255.255.0 -p 10.60.4.1 route add 192.168.16.9 mask2 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...