Linux TCP/UDP socket 通信和IO多路復用

来源:https://www.cnblogs.com/stux/archive/2023/11/13/17816974.html
-Advertisement-
Play Games

包括套接字相關函數socket、bind、listen、accept、recv、send、connect;以及IO多路復用函數select和epoll的簡介 ...


1.socket 通信

1.1 大小端轉換

  • 主機位元組序 16 位值 <==> 網路位元組序 16 位值
  • 主機位元組序 32 位值 <==> 網路位元組序 32 位值
#include <arpa/inet.h>

// 主機位元組序轉換為網路位元組序
uint16_t htons(uint16_t hostshort);    // host to net unsigned short 可用埠轉換
unit32_t htonl(unit32_t hostlong);     // host to net unsigned int 可用ip地址轉換

// 網路位元組序轉換為主機位元組序
uint16_t ntohs(uint16_t netshort);
unit32_t ntohl(unit32_t netlong);

1.2 IP地址轉換

  • 主機位元組序的字元串IP地址  <==> 網路位元組序的整形IP地址
#include <arpa/inet.h>

// 主機位元組序IP to 網路位元組序(大端)IP
int inet_pton(int af, const char* src, void* dst);
/*  參數:
        af: 地址族協議 AF_INET(ipv4), AF_INET6(ipv6)
        src: 主機位元組序的字元串類型的IP地址,被轉換的數據
        dst: 傳出參數, 存儲轉換之後的大端的IP地址
    返回值: 成功0; 失敗-1                */

const char *int_ntop(int af, const void *src, char *dst, socklen_t size);
/*  參數:
        af: 地址族協議 AF_INET; AF_INET6
        src: 傳入參數, 要被轉換的數據指針, 指向記憶體中存儲的大端IP地址(整形數)
        dst: 傳出參數, 指針指向主機位元組序, 字元串類型的IP地址
        size: dst指向的記憶體的大小
    返回值: 
        成功: 返回指向 dst 指針指向的記憶體
        失敗: NULL                          */

1.3 套接字相關函數

1.3.1 socket 創建

#include <arpa/inet.h>  // 該頭文件包括了 <sys/socket.h>

int socket(int domain, int type, int protocol);
/* 參數:
        domain: AF_INET; AF_INET6
        type:
            SOCK_STREAM: 流式傳輸協議 TCP
            SOCK_DGRAM: 報式傳輸協議 UDP
        protocol: 預設寫0
            流式傳輸預設 TCP
            報式傳輸預設 UDP
    返回值:
        成功: 返迴文件描述符
        失敗: 返回-1                      */

1.3.2 bind 綁定套接字

  將監聽的套接字和本地IP和埠進行關聯

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*  參數:
        sockfd: 用於監聽的套接字, 通過socket創建
        addr: 將本地ip和埠初始化給該結構體(需要用大端)
            綁定的時候伺服器一般ip使用巨集 INADDR_ANY (0)
            0 表示綁定該主機的所有ip地址, 多個網卡可能有多個ip
        addrlen: 記錄第二個指針指向記憶體的大小, sizeof(struct sockaddr)
    返回值:
        成功0, 失敗-1                       */

1.3.3 listen 監聽套接字

  給監聽的套接字設置監聽,開始檢測客戶端鏈接

int listen(int sockfd, int backlog);
/*  參數:
        sockfd: 監聽的套接字, 設置監聽前需要先綁定
        backlog: 可以同時檢測的新的連接個數, 最大值128
    返回值:
        成功0, 失敗-1                */

1.3.4 accept 接收客戶端連接

  等待並接受客戶端的連接,阻塞函數,沒有客戶端連接就阻塞,監聽的文件描述符緩衝區沒有數據就阻塞,有數據就解除阻塞建立連接,連接建立成功後,返回一個通信用的文件描述符

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*  參數:
        sockfd: 監聽的文件描述符
        addr: 傳出參數, 保存了建立連接的客戶端的地址信息(ip 埠) -> 大端存儲
            不需要客戶端信息則填NULL
        addrlen: 傳入傳出參數, 傳入addr指針指向的記憶體大小, 傳出存儲了客戶端信息的addr記憶體大小
            addr為NULL,則該參數也填NULL                    
    返回值:
        文件描述符或-1                           */

1.3.5 read、recv 讀數據

  讀取數據,如果數據區空會讀堵塞

ssize_t read(int sockfd, void *buf, size_t size);
ssize_t recv(int sockfd, void *buf, size_t size, int flags);
/*  參數:
        sockfd: 通信文件描述符
            伺服器端: accept 返回值
            客戶端: socket 創建得到, connect 初始化連接
        buf: 存儲接收到的數據, 數據來自文件描述符對應的緩衝區
        size: buf 的記憶體容量
        flag: 預設屬性0即可
    返回值:
        >0: 讀到的位元組數
        =0: 對方斷開連接
        -1: 讀異常, 失敗                      */

1.3.6 write、send 寫數據

  發送數據,如果數據區滿會寫阻塞

ssize_t write(int fd, const void *buf, size_t len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
/*  參數:
        fd: 通信的文件描述符
        buf: 要發送的數據緩衝區
        len: 緩衝區大小
        flags: 使用預設屬性0即可             */

1.3.7 recvfrom / sendto 發送接收

  • 報式傳輸協議發送
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
/*  參數:
        sockfd: 通信文件描述符
        buf: 一塊有效記憶體地址
        len: 參數buf指向的記憶體地址大小
        flags: 預設屬性0即可
        src_addr: 傳出參數, 保存發送端的IP和埠(網路位元組序), 不感興趣可以NULL
        addrlen: 傳入傳出參數, src_addr指針指向記憶體空間的大小, 如果src_addr為NULL, 則填NULL
    返回值:
        >0: 接收到的位元組數;  -1: 失敗                     */
  • 報式傳輸協議接收
ssize_t sendto(int sockfd, void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t *addrlen);
/*  參數:
        sockfd: 通信文件描述符
        buf: 待發送的數據地址
        len: 參數buf指向的記憶體地址大小
        flags: 預設屬性0即可
        dest_addr: 傳入參數, 接收端的IP和埠信息(網路位元組序)
        addrlen: 傳入參數, src_addr指針指向記憶體空間的大小
    返回值:
        >0: 發送的位元組數;  -1: 失敗                     */

1.3.8 connect 客戶端連接

  客戶端連接伺服器

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*  參數:
        sockfd: 通信文件描述符
        addr: 連接伺服器的ip和埠信息(需要使用大端描述)
        addrlen: 參數addr指向的記憶體大小
    返回值:
        成功0; 失敗-1                   */

1.4 套接字選項

  該函數用來設置套接字選項,埠復用、廣播、組播等,下麵是埠復用的參數解釋

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
/*  參數
        sockfd: 監聽的套接字
        level: SOL_SOCKET
        optname: SO_REUSEPORT
        optval: 實際類型int
            0 -> 埠不復用
            1 -> 埠復用
        optlen: optval 指針指向的記憶體大小 sizeof(int)
    返回值
        成功0, 失敗-1                        */

2. IO多路復用

2.1 select

  • 構造一個文件描述符列表,將要監聽的文件描述符添加到該列表中(最大支持1024,線性描述)
  • 調用一個函數,監聽該表中的文件描述符,知道這些描述符中的一個進行IO操作時,函數返回(該函數為阻塞函數,檢測由內核完成)
    • 讀集合:檢測文件描述符列表的讀緩衝區
      • 監聽的文件描述符:新客戶端連接
      • 通信的文件描述符:新數據到達
    • 寫集合:內核檢測集合中文件描述符是否可寫
      • 通信的文件描述符
    • 異常集合:檢測文件描述符是否有異常
  • 返回時,告訴進程有哪些描述符需要進行IO操作
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
/*  參數:
        nfds: 下麵三個集合中, 最大文件描述符值 + 1
        readfds: 傳出傳出參數,讀集合,檢測若幹文件描述符的讀緩衝區(新連接 / 新數據)
        writefds: 傳入傳出參數,寫集合,檢測若幹文件描述符的寫緩衝區(一般都可寫,很少用)
        execptfds: 傳入傳出參數,異常集合
        timeout: 表示時間段,最長檢測多長時間,超過這個時間還在阻塞就解除阻塞
            NULL 一直阻塞等待; 0 函數調用後立刻返回
    返回值:
        >0: 檢測完成後,滿足條件的總個數
        =0: 超時強制返回
        - 1: 失敗                                            */

  timeval 結構體

struct timeval {
    time_t         tv-sec;
    suseconds_t    tv_usec;
};

  fd_set 文件描述符集合(位操作)操作函數

void FD_CLR(int fd, fd_set *set);     // 刪除fd
int FD_ISSET(int fd, fd_set *set);    // 判斷fd是否在集合
void FD_SET(int fd, fd_set *set);     // 添加fd
void FD_ZERO(fd_set *set);            // 清空fd(初始化)

2.2 epoll

  在select/poll時代,伺服器進程每次都把這100萬個連接告訴操作系統(從用戶態複製句柄數據結構到內核態),讓操作系統內核去查詢這些套接字上是否有事件發生,輪詢完後,再將句柄數據複製到用戶態,讓伺服器應用程式輪詢處理已發生的網路事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的併發連接。

epoll的設計和實現與select完全不同。epoll通過在Linux內核中申請一個簡易的文件系統。把原先的select/poll調用分成了3個部分:

1)調用epoll_create()建立一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)

2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字

3)調用epoll_wait收集發生的事件的連接

如此一來,要實現上面說是的場景,只需要在進程啟動時建立一個epoll對象,然後在需要的時候向這個epoll對象中添加或者刪除連接。同時,epoll_wait的效率也非常高,因為調用epoll_wait時,並沒有一股腦的向操作系統複製這100萬個連接的句柄數據,內核也不需要去遍歷全部的連接。

2.2.1 epoll_create 創建 epoll

#include <sys/epoll.h>
int epoll_create(int size);
/*  參數:
        size: 沒有實際意義, 大於0即可
    返回值:
        成功: 返回一個文件描述符
                該文件描述符對應的指針存儲了紅黑樹的根節點
        失敗: -1                             */

2.2.2 epoll_ctl 操作epoll

  實現對 epoll 樹上節點的操作(添加、修改、刪除節點)

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
/* 參數:
        epfd: epoll_create() 函數的返回值,找到對應的epoll實例
        op: 
            EPOLL_CTL_ADD: 添加新節點
            EPOLL_CTL_MOD: 修改已經添加到樹上節點的屬性(讀改寫)
            EPOLL_CTL_DEL: 刪除節點
        fd: 要操作的文件描述符
            添加 / 修改 / 刪除(監聽、通信)
        event: 對應的事件(若刪除填NULL)
            EPOLLIN: 讀事件
            EPOLLOUT: 寫事件                     */
  • epoll_data
typedef union epoll_data{
    void      *ptr;
    int        fd;         // 該聯合體常用這個
    uint32_t   u32;
    uint64_t   u64;
} epoll_data_t;
  • epoll_event
    • event 是位操作,EPOLLIN 檢測寫緩衝區,EPOLLOUT 檢測讀緩衝區
    • data.fd 等於 epoll_ctl 函數調用的第三個參數
struct epoll_event{
    uint32_t    event;    // Epoll events;
    epoll_data_t data;    // User data variable
};

2.2.3 epoll_wait

  阻塞函數,委托內核檢測epoll樹上文件描述符的狀態,如果沒有狀態變化,預設一直阻塞

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
/*  參數:
        epfd: epoll_create() 的返回值, 找到epoll實例
        event: 傳出參數,記錄了這輪檢測到epoll模型中有狀態變化的文件描述符(結構體數組地址)
        maxevent: events數組的容量
        timeout: 超時時長 ms(-1一直阻塞; 0立即返回)
    返回值:
        成功: 有多少文件描述符發生變化                        */

2.2.4 Level triggered 水平模式(預設)

  LT(level triggered)是預設的工作方式,同時支持 block 和 no-block socket。這種模式下,內核會通知文件描述符是否就緒,如果不進行任何操作,內核會一直通知你該文件描述符就緒

2.2.5 Edge triggered 邊沿模式

  ET(edge triggered)是高速工作模式,只支持 no-block socket。這種模式下,如果接到通知,但是沒有把數據從緩衝區讀完,epoll_wait不會再次通知;直到再次接收到新數據也一樣通知一次,但是此時他會接著上次的緩衝區數據讀。

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;  // 設置文件描述符為邊沿模式
    ev.data.fd = lfd;

  使用邊沿模式讀數據需要在收到消息後我們一般需要 while(1) 死迴圈讀取數據直到緩衝區數據讀完,所以需要設置文件描述符為非阻塞狀態,讓read可以非阻塞讀取數據,通過 read 的返回值判斷是否結束該死迴圈

int fcntl(int fd, int cmd, ...);

int flag = fcntl(cfd, F_GETFL);
flag = flag | O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);    //設置文件描述符為非阻塞, read函數再讀取不會阻塞

  最後因為這裡已經設置為非阻塞,可以根據read的返回值判斷是否已經讀完緩衝區了,如果讀完了會有errno EAGAIN的錯誤碼,根據該錯誤碼跳出迴圈即可

while(1)
{
    int len = recv(curfd, buf, sizeof(buf), 0);
    if(len > 0)
        printf("列印接收的數據");
    else if( len == 0)
        printf("斷開連接");
    else
    {
        if(errno==EAGAIN)
        {
            printf("數據讀完了");
            break; // 跳出迴圈
        }
        perror("接收錯誤");
        exit(0);
    }
}

3. 代碼示例

3.1 TCP、epoll伺服器

  1. 創建socket套接字
  2. 綁定ip和埠
  3. 設置監聽
  4. 初始化一個epoll樹
  5. 將文件描述符加入epoll樹
  6. 委托內核檢測文件描述符狀態
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

int main()
{
    // 1. 創建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 2.將 套接字 和 ip埠 綁定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;  // ipv4
    addr.sin_addr.s_addr= INADDR_ANY;   // 0地址(本地任意地址)
    addr.sin_port = htons(8989);    // 埠轉為大端
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(2);
    }

    // 3.設置監聽
    ret = listen(lfd, 128);
    if(ret == -1)
    {
        perror("listen error");
        exit(3);
    }

    // 4.初始化檢測的集合
    int epfd = epoll_create(1);
    if(epfd == -1)
    {
        perror("epoll_create error");
        exit(4);
    }

    // 5.將要檢測的節點添加到epoll樹中
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if(ret == -1)
    {
        perror("epoll_ctl");
        exit(5);
    }

    // 6.委托內核檢測epoll樹中的文件描述符狀態
    struct epoll_event evs[1024];
    int size = sizeof(evs) / sizeof(evs[0]);
    while(1)
    {
        int num = epoll_wait(epfd, evs, size, -1);  // 把文件描述符發生變化的儲存到 evs 數組中
        printf("num = %d\n", num);
        // 遍歷evs數組
        for(int i=0; i<num; i++)
        {
            int curfd = evs[i].data.fd;
            if(curfd == lfd)    // lfd 套接字狀態改變說明有新鏈接請求
            {
                int cfd = accept(lfd, NULL, NULL);
                ev.events = EPOLLIN;
                ev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);   // 把新的鏈接加入到epoll樹中
            }
            else    // 其他套接字狀態改變說明有新數據抵達
            {
                char buf[1024];
                memset(buf, 0, sizeof(buf));
                int len = recv(curfd, buf, sizeof(buf), 0);
                if(len == 0)
                {
                    printf("客戶端斷開了鏈接...\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                }
                else if(len>0)
                {
                    printf("recv data: %s\n");
                    send(curfd, buf, len, 0);
                }
                else
                {
                    perror("recv error");
                    exit(6);
                }
            }
        }
    }
}

3.2 UDP

3.2.1 伺服器

  1. UDP伺服器需要創建套接字
  2. 綁定埠
  3. 接收數據
  4. 根據接收數據的客戶端發送數據
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1.創建通信套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1)
    {
        perror("socket");
        exit(0);
    }
    // 2.接收數據需要綁定固定的埠
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8989);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret==-1)
    {
        perror("bind");
        exit(0);
    }
    // 通信
    char ip[24];
    char buf[1024];
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    while(1)
    {
        // 3.接收數據
        int len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &clilen); // 把發送端數據保存在cliaddr中
        if(len==-1)
        {
            break;
        }
        printf("client ip: %s, port: %d\n",
                inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)),
                ntohs(cliaddr.sin_port));   // 列印發送端ip和port
        printf("client say: %s\n", buf);    // 列印發送端發送的內容

        // 4.回覆數據
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, clilen);
    }
    close(fd);
    return 0;
}

3.2.2 客戶端

  • UDP客戶端相對於伺服器端減少了手動綁定ip埠的步驟
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1.創建通信套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1)
    {
        perror("socket");
        exit(0);
    }
    // 伺服器地址
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8989);
    inet_pton(AF_INET, "10.0.2.15", &serveraddr.sin_addr.s_addr);
    // 通信
    char ip[24];
    char buf[1024];
    int num=0;
    while(1)
    {   
        // 2.發送數據
        sprintf(buf, "Hello World!, %d\n", num++);
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

        // 3.接收數據
        memset(buf, 0, sizeof(buf));
        int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); // 把發送端數據保存在cliaddr中
        if(len==-1)
        {
            break;
        }
        printf("client say: %s\n", buf);    // 列印發送端發送的內容
    }
    close(fd);
    return 0;
}

3.3 UDP廣播

3.3.1 伺服器

  1. 伺服器創建socket
  2. 設置廣播屬性
  3. 向廣播ip端發送數據
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

// 伺服器就是廣播端, 不需要收數據, 自動綁定了以後發數據就行
int main()
{
    // 1.創建socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1)
    {
        perror("socket");
        exit(0);
    }
    
    // 2.設置廣播屬性
    int opt = 1;    // 1表示允許廣播, 0表示不允許廣播
    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

    // 初始化數據接收端地址信息
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(8989);
    inet_pton(AF_INET, "10.0.2.255", &cliaddr.sin_addr.s_addr);

    // 3.廣播發送數據
    char buf[1024];
    int num = 0;
    while(1)
    {
        sprintf(buf, "發送廣播數據: %d\n", num++);
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
        printf("%s\n", buf);
        sleep(1);
    }
    close(fd);
    return 0;
}

3.3.2 客戶端

  1. 客戶端創建socket
  2. 綁定固定的埠用來接收數據
  3. recvfrom接收數據
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    // 1.創建通信套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd==-1)
    {
        perror("socket");
        exit(0);
    }
    // 綁定固定的埠
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8989);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret==-1)
    {
        perror("bind");
        exit(0);
    }
    // 通信
    char ip[24];
    char buf[1024];
    while(1)
    {
        // 接收數據
        memset(buf, 0, sizeof(buf));
        int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); // 把發送端數據保存在cliaddr中
        if(len==-1)
        {
            break;
        }
        printf("boardcast say: %s\n", buf);    // 列印發送端發送的內容
    }
    close(fd);
    return 0;
}

 


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

-Advertisement-
Play Games
更多相關文章
  • 在 Java 中,有四種方法可以獲取當前正在執行方法體的方法名稱,分別是: 使用 Thread.currentThread().getStackTrace() 方法 使用異常對象的 getStackTrace() 方法 使用匿名內部類的 getClass().getEnclosingMethod() ...
  • Guava 是 Google 提供的一套 Java 工具包,而 Guava Cache 是該工具包中提供的一套完善的 JVM 級別高併發緩存框架;本文主要介紹它的相關功能及基本使用,文中所使用到的軟體版本:Java 1.8.0_341、Guava 32.1.3-jre。 1、簡介 緩存在很多情況下非 ...
  • Python安裝之後,其標準庫中有的模塊,不一定要通過代碼來引用,還可以直接在命令行中使用的。 在命令行中直接使用Python標準庫的模塊,最大的好處就是就是不用寫代碼,就能使用其中的功能,當臨時需要一些某些功能的時候,用這種方式會快捷,方便很多。 1. 命令行中使用模塊 命令行中使用python標 ...
  • 四、基本數據類型和計算(三) 1、枚舉變數 1)通過案例體現枚舉類型的作用 ​ 假設要為我們的游戲裝備設置稀有度屬性,應該如何設計 裝備級別 變數名 普通 normal 高級 high 稀有 rare 史詩 epic 傳說 legend 神話 myth 不使用枚舉變數,使用常量方式設置 #inclu ...
  • 目錄 Welcome to YARP - 1.認識YARP並搭建反向代理服務 Welcome to YARP - 2.配置功能 2.1 - 配置文件(Configuration Files) 2.2 - 配置提供者(Configuration Providers) 2.3 - 配置過濾器(Confi ...
  • 思路 我發現 .NET Core WebAPi項目有一個與Springboot的不同之處,就是Springboot項目有自動裝配機制,他可以將在src下麵與啟動類在同一級包目錄下的類進行掃描註冊 而之前我瞭解到Springboot的自動裝配機制本質上也就是通過掃描對應包,然後進行通過它自身進行服務註 ...
  • Sql Server中Cross Apply關鍵字的使用 前言 在寫一個業務的時候,有1列數據如下: 車牌號 湘A00001/湘G00001 湘A00002/湘G00002 湘A00003/湘G00003/湘A8888888 湘A00004/湘G00004/湘A00001 我的查詢條件也是車牌號,我 ...
  • 壓縮和解壓指令 gzip/gunzip 指令 gzip:用於壓縮文件 gunzip:用於解壓的 基本語法: gzip 文件,壓縮文件,只能將文件壓縮為 .gz 文件。 gunzip 文件.gz,解壓縮文件命令。 zip/unzip 指令 zip:用於壓縮文件 unzip:用於解壓文件,這個在項目打包 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...