Linux信號

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/10/06/5935200.html
-Advertisement-
Play Games

信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 , 信號是非同步的, 也就是進程並不知道信號何時會到達 $kill 9 3390 向PID為3390的進程發送編號為9的信號= 一個兩個進程間通信的方式之一 一共6 ...


  • 信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 ,* 信號是非同步的, 也就是進程並不知道信號何時會到達
  • $kill -9 3390 #向PID為3390的進程發送編號為9的信號=>一個兩個進程間通信的方式之一
 1) SIGHUP      2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT     7) SIGBUS       8) SIGFPE       9) SIGKILL      10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX    
  • 一共62個,不是64個, 歷史原因, 沒有32,33
  • 1~31之間的信號叫做不可靠信號, 不支持排隊, 信號可能會丟失, 也叫做非實時信號
  • 34~64之間的信號叫做可靠信號, 支持排隊, 信號不會丟失, 也叫做實時信號

絕大多數信號的預設處理方式都是終止進程, 另外兩種預設處理方式: 忽略處理, 自定義處理

1) SIGHUP 連接掛斷 終止(預設處理)
2) SIGINT 終端中斷,Ctrl+c產生該信號 終止(terminate)
3) SIGQUIT 終端退出,Ctrl+\ 終止+轉儲
4) SIGILL *進程試圖執行非法指令 終止+轉儲
5) SIGTRAP 進入斷點 終止+轉儲
6) SIGABRT *進程異常終止,abort()產生 終止+轉儲
7) SIGBUS 硬體或對齊錯誤 終止+轉儲
8) SIGFPE *浮點運算異常 終止+轉儲
9) SIGKILL 不可以被捕獲或忽略的終止信號 終止
10) SIGUSR1 用戶定義信號1 終止
11) SIGSEGV *無效的記憶體段訪問=>Segmentation error 終止+轉儲
12) SIGUSR2 用戶定義信號2 終止
13) SIGPIPE 向讀端已關閉的管道寫入 終止
14) SIGALRM 真實定時器到期,alarm()產生 終止
15) SIGTERM 可以被捕獲或忽略的終止信號 終止
16) SIGSTKFLT 協處理器棧錯誤 終止
17) SIGCHLD 子進程已經停止, 對於管理子進程很有用 忽略
18) SIGCONT 繼續執行暫停進程(用戶一般不用) 忽略
19) SIGSTOP 不能被捕獲或忽略的停止信號 停止(stop)
20) SIGTSTP 終端掛起,用戶產生停止符(Ctrl+Z) 停止
21) SIGTTIN 後臺進程讀控制終端 停止
22) SIGTTOU 後臺進程寫控制終端 停止
23) SIGURG 緊急I/O未處理 忽略
24) SIGXCPU 進程資源超限 終止+轉儲
25) SIGXFSZ 文件資源超限 終止+轉儲
26) SIGVTALRM 虛擬定時器到期 終止
27) SIGPROF 實用定時器到期 終止
28) SIGWINCH 控制終端視窗大小改變 忽略
29) SIGIO 非同步I/O事件 終止
30) SIGPWR 斷電 終止
31) SIGSYS 進程試圖執行無效系統調用 終止+轉儲

*系統對信號響應應視具體情況而定

發送信號的主要方式:

  1. 鍵盤 //只能發送部分特殊的信號 eg:Ctrl+C可以發送SIGINT
  2. 程式出錯 //只能發送部分特殊的信號 eg: 出現段錯誤, 可以發送SIGSEGV
  3. $kill -Signal PID #能發所有信號
  4. 系統函數kill()/raise()/alarm()/sigqueue()

kill()

//給任何進程或進程組發任何信號,成功返回0,失敗返回-1設errno

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

pid

  • pid>0 //指定pid
  • pid=0 //GID是調用進程PID的子進程
  • pid=-1 //任何子進程
  • pid<-1 //GID是PID的子進程

sig

  • sig == 0 //不發任何信號,但錯誤檢查還是會做,可以檢查PID是否存在
#include<sys/types.h>
#include<signal.h>
void fa(int signo){
    printf("catch signal number:%d\n",signo);
}
int main(){
    pid_t pid=fork();
    if(0==pid){
        …
        while(1);
    }
    if(0==kill(pid,0)){                         //if child exists
        printf("parent starts to raise signal\n");
        if(-1==kill(pid,40))                    //if fail to kill child
            perror("kill"),exit(-1);
    }
    return 0;
}.

raise()

//給調用的線程或進程發信號,如果發送的信號被handler處理了,會在handler返回後再返回,成功返回0,失敗返回非0
#include<signal.h>
int raise(int sig);
  • 在單線程程式中等價於 kill(getpid,sig)
  • 在多線程程式中等價於pthread_kill(pthread_self(), sig);
if(0!=raise(SIGINT))
    perror("raise success"),exit(-1);
//raise.c
#include<unistd.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
    printf("catch %d success\n",signo);
}
int main(){
    //設置對SIGINT自定義處理
    if(SIG_ERR==signal(SIGINT,fa))
        perror("signal"),exit(-1);

    //5s後使用raise發送SIGINT
    unsigned int second=sleep(5);
    if(0==second)
        printf("sleep well");
    else
        printf("sleep is interrupted, there are %ds left\n",second);
    if(0!=raise(SIGINT))
        perror("raise success"),exit(-1);
    return 0;
}

alarm()

//seconds秒之後給調用進程發送SOGALRM信號,返回距離下次鬧鐘響起剩餘的描述,沒有鬧鐘返回0

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds=0表示不設置新的SIGALRM的同時取消之前的鬧鐘==>專門用來取消鬧鐘

//alarm.c
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void fa(int signo){
    printf("catch %d\n",signo);
    alarm(1);
}
int main(){
//  設置對信號SIGALRM進行自定義處理;
    if(SIG_ERR==signal(SIGALRM,fa))
        perror("signal"),exit(-1);
//  設置5s發送SIGALRM;
    unsigned int second=alarm(5);
    printf("second=%u\n",second);   //0
    sleep(2);
    //調整鬧鐘10s後響(發SIGALRM),second是3
    second=alarm(10);
    printf("second=%u\n",second);   //3
    //取消鬧鐘(不會發SIGALRM),second是3
//  second=alarm(0);
//  printf("second=%d\n",second);   //10
    
    while(1);
    return 0;
}

sigqueue()

//將信號和數據排好發給指定進程,成功返回0,失敗返回-1設errno

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);

pid //進程的編號(給誰發信號)
sig //信號值/信號的名稱(發送什麼樣的信號)
value //聯合類型的附加數據(伴隨什麼樣的數據)

union sigval {
    int   sival_int;
    void *sival_ptr;
};
void fa(int signo,siginfo_t* info,void* pv){
    printf("進程%d發來的信號是:%d,附加數據是%d\n",info->si_pid,signo,info->si_value);
}
int main(){
    //設置對信號40的自定義處理
    struct sigaction action={};
    action.sa_sigaction=fa;
    action.sa_flags=SA_SIGINFO;
    int res=sigaction(40,&action,NULL);
    if(-1==res)
        perror("sigaction"),exit(-1);
    pid_t pid=fork();
    if(-1==pid)
        perror("fork"),exit(-1);
    if(0==pid){ //child開始, 發送1~100之間的數據發給parent
        printf("child%dstarts\n",getpid());
        int i=1;
        for(i=1;i<=100;i++){
            union sigval value;
            value.sival_int=i;
            sigqueue(getppid(),40,value);//這裡沒有錯誤處理
        }
        exit(0);
    }
    while(1);
    return 0;
}

sleep()

//讓調用的線程睡seconds秒,除非這期間收到了不能忽略的信號,成功返回0,失敗返回剩餘沒睡的時間

#include <unistd.h>
unsigned int sleep(unsigned int seconds);
unsigned int second=sleep(5);
if(0==second)
    printf("sleep well");
else
    printf("sleep is interrupted, there are %ds left\n",second);

pause()

//讓調用的線程一直睡直到接收到了一個終止進程或者引發捕獲信號函數的信號,當接受到這樣的信號時,返回-1設errno

int pause(void);
void ding(int signo){
}
int main(){
    (void) signal(SIGALRM, ding);   //ding是一個函數
    pause();    
}

Q:pause 放在signal-catching很遠的地方可以嗎

A:pause就相當於sleep(∞), 跟捕捉語句沒什麼關係,誰說一定要組合用的

Q: 寫了捕獲語句, 是不是就使整個進程有了捕獲某個信號的能力還是只是這一句???

A:當然不是, signal就像是全局變數, 使用sigaction()對某個信號進行重定向後, 此後的程式遇到這個信號就使用新的處理方式

signal()

//重新設置對信號的處理方式,將信號signum交給handler處理,成功返回之前的signal handler,失敗返回-1設errno

#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);

handler

  • SIG_IGN(Signal ignore)
  • SIG_DFL(Signal default)
  • User-defined fhandler with argument signum
#include<signal.h>
void fa(int signo){
    printf("catch signal:%d\n",signo);
    if(SIG_ERR==signal(signo,SIG_DFL))
        perror("signal"),exit(-1);
}
int main(){
    if(SIG_ERR==signal(3,fa))
        perror("signal"),exit(-1);
    return 0;
}

信號集

信號的集合,當前系統所支持的信號範圍:1~64(共62個),就是說每個進程可以使用信號1~64,為了對信號操作方便, 我們把一些信號放在一個集合中一起處理, 很多函數(包括sigpromask,sigpending)都是對信號集操作而不是對單個信號

sigset_t

Linux中表示信號集的數據類型, 就是一個超級大的整數(128byte, 32個unsigned long int的數組), 底層採用一個二進位位來代表一個信號, 根據該二進位位為0 or 1表示該信號是否存在

typedef struct{
        unsigned long int __val[(1024 / (8 * sizeof (unsigned long int)))];
  }__sigset_t;
typedef __sigset_t sigset_t;

在當前系統中, 按照%d列印的話就是列印信號集類型中的低4個位元組的數據其實當前只需要8byte, 設計這麼大是為了未來擴展

sigemptyset() /sigfillset()/sigaddset()/sigdelset()/sigismember()

//這些都是信號集操作函數
//sigemptyset()/sigfillset()/ sigaddset()/ sigdelset() 成功返回0,失敗返回-1設errno
//如果signum是指定信號集的一員,sigismember()返回1,不是返回0,失敗返回-1設errno。

#include <signal.h>
int sigemptyset(sigset_t *set);                     //清空信號集
int sigfillset(sigset_t *set);                      //填滿信號集
int sigaddset(sigset_t *set, int signum);           //添加信號到信號集
int sigdelset(sigset_t *set, int signum);           //刪除信號集的信號
int sigismember(const sigset_t *set, int signum);   //判斷信號是否屬於信號集

sigprocmask()

//sigprocmask()只適用與單線程進程,多線程的參考pthread_sigmask()
//在某些特殊程式的執行過程中, 是不能被信號打斷的, 此時需要信號的屏蔽技術來解決該問題
//獲取並修改調用線程的屏蔽信號集
//成功返回0,失敗返回-1設errno

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

how

  • SIG_BLOCK //新的屏蔽信號集是原有集合(不是oldset,oldset是用來存儲的)和set的合集
  • SIG_UNBLOCK //set內的信號被從屏蔽集合oldset中移除
  • SIG_SETMASK //屏蔽信號集就是setThe set of blocked signals is set to the argument set.

oldset

  • 如果oldset 不是NULL,則之前的信號集被存儲在其中
  • 如果set是NULL,屏蔽信號集不會變數,但現存的屏蔽信號集還是會被存儲在oldset中
//準備信號集中有:2,3
sigset_t set,oldset;
if(-1==sigemptyset(&set))
    perror("sigemptyset"),exit(-1);
if(-1==sigemptyset(&oldset))
    perror("sigemptyset"),exit(-1);
if(-1==sigaddset(&set,2))
    perror("sigaddset"),exit(-1);

//設置屏蔽的信號集
if(-1== sigprocmask(SIG_SETMASK,&set,&oldset))
    perror("sigprocmask"),exit(-1);
  • 信號屏蔽並不是刪除信號, 而是將信號單獨保存起來, 等信號的屏蔽解除之後, 信號還是會被處理的
  • 可靠信號: 支持排隊, mask期間有多少信號, mask解除之後就會處理多少
  • 不可靠信號:不支持排隊, mask期間有多個信號時, mask解除之後只處理第一個

sigpending()

//檢查mask期間捕捉到但是沒有處理的信號, 通過形參帶出結果,成功返回0,失敗返回-1設errno

#include <signal.h>
int sigpending(sigset_t *set);

sigaction()

//是前面的信號處理的集大成者且有很多擴展,是一個非常健壯的signal處理介面,建議使用,成功返回0,失敗返回-1設errno

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

signum可以代表除了SIGSTOP和SIGKILL之外的任何一個信號
如果NULL!=act,act作為針對signal的新的action。
如果NULL!=oldact, 之前的action被存儲在oldact。
act/oldact:

struct sigaction {
    void        (*sa_handler)(int);
    void        (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t    sa_mask;
    int         sa_flags;
    void        (*sa_restorer)(void);       //obsolete, should not be used.
};

如果 sa_flags 被設為SA_SIGINFO,則sa_sigaction用來作signum的handler, 否則,用sa_handler
sa_handler表明針對signum的handler,和signal()第二個參數類型一致, 都是用於設置signal信號的處理方式SIG_DFL,SIG_IGN,user-defined handler

//sa_sigaction
struct  siginfo_t {
    int         si_signo;       /* Signal number */
    int         si_errno;       /* An errno value */
    int         si_code;        /* Signal code */
    int         si_trapno;      /*Trap number that caused hardware-generated signal
    pid_t       si_pid;         /* Sending process ID */    //發送信號的進程號
    uid_t       si_uid;         /* Real user ID of sending process */
    int         si_status;      /* Exit value or signal */
    clock_t     si_utime;       /* User time consumed */
    clock_t     si_stime;       /* System time consumed */
    sigval_t    si_value;       /* Signal value */          //伴隨信號到來的附加數據
    int         si_int;         /* POSIX.1b signal */
    void*       si_ptr;         /* POSIX.1b signal */
    int         si_overrun;     /* Timer overrun count; POSIX.1b timers */
    int         si_timerid;     /* Timer ID; POSIX.1b timers */
    void*       si_addr;        /* Memory location which caused fault */
    long        si_band;        /* Band event (was int in glibc 2.3.2 and earlier) */
    int         si_fd;          /* File descriptor */
    short       si_addr_lsb;    /* Least significant bit of address(since kernel 2.6.32) */
} 

sa_mask 表明在信號handler執行期間需要屏蔽的信號,此外,出發handler的信號預設是被屏蔽的,除非flag里指定的SA_NODEFE

sa_flags(Bitwise Or)

  • SA_NOCLDSTOP //If signum is SIGCHLD, do not receive notification when child processes stop (i.e., when they receive one of SIGSTOP, SIGTSTP, SIGTTIN or SIGTTOU) or resume (i.e., they receive SIGCONT) (see wait(2)). This flag is only meaningful when establishing a handler for SIGCHLD.
  • SA_NOCLDWAIT //If signum is SIGCHLD, do not transform children into zombies when they terminate. See also waitpid(2). This flag is only meaningful when establishing a handler for SIGCHLD, or when setting that signal's disposition to SIG_DFL.If the SA_NOCLDWAIT flag is set when establishing a handler for SIGCHLD, POSIX.1 leaves it unspecified whether a SIGCHLD signal is generated when a child process terminates. On Linux, a SIGCHLD signal is generated in this case; on some other implementations, it is not.
  • SA_NODEFER //解除對觸發信號處理函數相同信號的屏蔽
  • SA_ONSTACK //Call the signal handler on an alternate signal stack provided by sigaltstack(2). If an alternate stack is not available, the default stack will be used. This flag is only meaningful when establishing a signal handler.
  • SA_RESETHAND //一旦信號處理函數被調用之後, 信號的處理方式就會恢復為預設處理方式
  • SA_RESTART //Provide behavior compatible with BSD signal semantics by making certain system calls restartable across signals.This flag is only meaningful when establishing a signal handler. See signal(7) for a discussion of system call restarting.
  • SA_SIGINFO //使用第二個函數指針設置信號的處理方式
//sigaction.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
void fa(int signo){
    printf("fa1...\n");
    sleep(3);
    printf("fa2...\n");
}
int main(){
    printf("current pid:%d\n",getpid());
    //prepare struc var
    struct sigaction action={};
    action.sa_handler=fa;
    sigemptyset(&action.sa_mask);
    sigaddset(&action.sa_mask,3);
    action.sa_flags=SA_NODEFER; //解除對信號2的屏蔽,sigaction(2....)
    int res=sigaction(2,&action,NULL);
    if(-1==res)
        perror("sigaction"),exit(-1);
    printf("set signal successfully\n");
    while(1);
    return 0;
}

sigaction()那點事:

  1. 使用sigaction(),需要明確兩個act,一個用來存儲老act的oldact,一個用來安裝新act的act,
  2. 這兩個act都是struct sigaction
  3. 結構體有兩個函數分別指明對sig的處理方式,具體使用哪個需要看flag是否被set了SA_SIGINFO,set了,就是sa_sigaction()處理信號,沒set,就是sa_handler,前者比後者的功能更多,後者其實就是signal()
  4. 指明瞭用哪個函數處理了信號,我們還可以指定在signal handler執行的過程中哪些SIG被mask,註意這個是sigset_t類型,把要mask的信號放在一個信號集里,預設條件下the signal which triggered the handler是被mask的,除非在sa_flags中set了SA_NODEFER;
  5. 可以看到,整個act中flags主要起到了配置的作用,它決定使用哪個handler(SA_SIGINFO),也決定要不要屏蔽觸發信號(SA_NODEFER);
  6. 除了這兩個,flags還有其他選項
void fa(int signo,siginfo_t* info,void* pv){
    printf("進程%d發來了信號%d\n",info->si_pid,signo);
}
int main(){
    struct sigaction action={};
    action.sa_sigaction=fa;
    action.sa_flags=SA_SIGINFO;
    if(-1== sigaction(2,&action,NULL))
        perror("sigaction"),exit(-1);
    return 0;
}

父子進程信號

  1. 對於fork()創建的child, child完全照搬parent對信號的處理方式:
    • parent忽略=>child忽略;
    • parent預設=>child預設;
    • arent自定義=>child自定義;
  2. 對於vfork(),execl()啟動的child, 部分照搬parent的信號處理方式:
    • parent忽略=>child忽略;
    • parent預設=>child預設;
    • parent自定義=>child預設=>因為execl()已經跳出的子進程, 而process.out裡面沒有fa,所以會按預設方式處理
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
void fa(int signo){
    printf("%d\n",signo);
}
int main(){
    if(SIG_ERR==signal(2,fa))
        perror("signal"),exit(-1);
    if(SIG_ERR==signal(3,SIG_IGN))
        perror("signal"),exit(-1);
    pid_t pid=vfork();
    if(-1==pid)
        perror("vfork"),exit(-1);
    if(0==pid){
        printf("child's pid=%d\n",getpid());
        int res=execl("process.out","process.out",NULL);
        if(-1==res)
            perror("execl"),exit(-1);
        while(1);
    }
    return 0;
}

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

-Advertisement-
Play Games
更多相關文章
  • 模型 1. 獲取key ftok() 2. 創建/獲取信號量集 semget() 3. 初始化信號量集 semctl() 4. 操作信號量集 semop() 3. 刪除信號量集 semctl() 使用的頭文件: ftok() pathname :文件名 proj_id : 1~255的一個數,表示p ...
  • 其實樹莓派自帶realvnc server 不需要額外tightvnc server 1.點擊左上角樹莓派圖標-->Preferences-->Raspberry Pi Configuration 2.Enable VNC 3.點擊"OK"後,其實VNCserver已經打開了: 4.點擊"More" ...
  • 模型: 1. 獲取key值 :ftok() 2. 創建/獲取消息隊列 :msgget() 3. 發消息到消息隊列/從消息隊列收信息 :msgsnd()/msgrcv() 3. 刪除消息隊列 :msgctl() 使用的頭文件 ftok() pathname :文件名 proj_id : 1~255的一 ...
  • 1.代碼 input_subsys.drv.c 在linux輸入子系統(input subsystem)之按鍵輸入和LED控制的基礎上有小改動,input_subsys_test.c不變。 input_subsys.drv.c 2. input_subsys_drv.c, input.c, evde ...
  • 通信模型: 1. 獲取key值 :ftok() 2. 創建/獲取共用記憶體 :shmget() 3. 掛接共用記憶體 :shmat() 4. 脫接共用記憶體 :shmdt() 5. 刪除共用記憶體 :shmctl() 使用的頭文件 ftok() pathname :文件名 proj_id : 1~255的一 ...
  • ipcs ipcs m 查看系統中已經存在的共用記憶體 shmid :共用記憶體的id perms :permission nattch :number attatch ipcs q 查看系統中現有的消息隊列 used byte: 隊列的大小 message : 隊列中消息的條數 ipcs s 查看系統 ...
  • 管道是Linux的十種文件類型之一,使用管道通信本質上還是以文件作為通信的媒介 有名管道+無名管道=管道 有名管道(FIFO文件):就是 有文件名的管道, 可以用於任意兩個進程間的通信 無名管道(pipe文件):就是沒有文件名的管道, 只能用於父子進程之間的通信 mkfifo 創建有名管道,管道不能 ...
  • Linux中, 系統為每個系統都維護了三種計時器,分別為: 真實計數器, 虛擬計時器以及實用計時器, 一般情況下都使用真實計時器 getitimer()/setitimer() which //具體的計時器類型 1. ITIMER_REAL :真實計時器 統計進程消耗的真實時間 通過定時產生SIGA ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...