国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux多路IO復(fù)用:epoll

這篇具有很好參考價(jià)值的文章主要介紹了Linux多路IO復(fù)用:epoll。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1. epoll

? ? ? ? epoll是為克服select、poll每次監(jiān)聽都需要在用戶、內(nèi)核空間反復(fù)拷貝,以及需要用戶程序自己遍歷發(fā)現(xiàn)有變化的文件描述符的缺點(diǎn)的多路IO復(fù)用技術(shù)。

epoll原理

創(chuàng)建內(nèi)核空間的紅黑樹;

將需要監(jiān)聽的文件描述符上樹;

內(nèi)核監(jiān)聽紅黑樹上文件描述符的變化;

返回有變化的文件描述符。

epoll優(yōu)點(diǎn)

? ? ? ? ① 無需在用戶、內(nèi)核空間反復(fù)拷貝數(shù)據(jù);

? ? ? ? ② 內(nèi)核返回發(fā)生變化的文件描述符,無需用戶遍歷所有文件描述符。


2. epoll API

(1)epoll_create 創(chuàng)建紅黑樹

#include<sys/epoll.h>

int epoll_create(int size);
/*
功能:
    創(chuàng)建內(nèi)核中的epoll紅黑樹;
參數(shù):
    size:監(jiān)聽的文件描述符上限,kernel 2.6版本后寫1即可,會(huì)自動(dòng)擴(kuò)展。
返回值:
    成功:返回紅黑樹的句柄(相當(dāng)于操作樹的入口)。
    失?。?1,會(huì)設(shè)置errno。
*/

(2)epoll_ctl 上樹、下樹、修改節(jié)點(diǎn)的監(jiān)聽事件

#include<sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
/*
功能:
    上樹、下樹、修改節(jié)點(diǎn)。
參數(shù):
    epfd:紅黑樹的句柄,epoll_create的返回值。
    op:對(duì)文件描述符fd的操作
        EPOLL_CTL_ADD:將文件描述符fd上樹
        EPOLL_CTL_MOD:修改文件描述符fd的事件
        EPOLL_CTL_DEL:將文件描述符fd下樹
    fd:op要操作的文件描述符
    event:用于對(duì)特定的文件描述符事件進(jìn)行設(shè)置。
返回值:
    成功:
    失敗:
*/

struct epoll_event {
    uint32_t events;    // 監(jiān)聽的事件
    epoll_data_t data;  // 需要監(jiān)聽的文件描述符(共用體中的fd)
}
/*
參數(shù) events:
    EPOLLIN:讀事件
    EPOLLOUT:寫事件
*/

typedef union epoll_data {
    void* ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

(2)epoll_wait 監(jiān)聽

#include<sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
/*
功能:
    監(jiān)聽紅黑樹上文件描述符的變化;
參數(shù):
    epfd:紅黑樹的句柄(epoll_create的返回值);
    events:接收發(fā)送變化的文件描述符的數(shù)組地址
    maxevents:數(shù)組元素的個(gè)數(shù)
    timeout:
        > 0:監(jiān)聽超時(shí)時(shí)間(多久監(jiān)聽一次);
        0:無文件描述符變化則立即返回;
        -1:阻塞監(jiān)聽到有文件描述符變化才返回
返回值:
    成功:0表示沒有文件描述符發(fā)生變化;否則返回發(fā)生變化的文件描述符的個(gè)數(shù)
    失?。?1,調(diào)用錯(cuò)誤,會(huì)設(shè)置errno
*/

3. epoll使用示例

(1)監(jiān)聽管道

? ? ? ? 子進(jìn)程每3s向管道寫數(shù)據(jù),父進(jìn)程使用epoll監(jiān)聽管道,有數(shù)據(jù)可讀則讀出管道中的數(shù)據(jù)。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/epoll.h>


int main(int argc, const char* argv[]) {

    int fd[2];

    pipe(fd);

    pid_t pid;
    pid = fork();

    if (0 == pid) { // 子進(jìn)程
        close(fd[0]);
        char buf[5];
        char ch = 'a';
        while (1) {
            memset(buf, ch++, 5);
            write(fd[1], buf, 5);
            sleep(3);
        }
    } else {    // 父進(jìn)程
        close(fd[1]);

        // 創(chuàng)建紅黑樹
        int epfd = epoll_create(1);

        // 上樹
        struct epoll_event ev;
        ev.data.fd = fd[0];
        ev.events = EPOLLIN;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &ev); // 將fd[0]上樹,同時(shí)使用ev結(jié)構(gòu)體來設(shè)置監(jiān)聽fd[0]的讀事件。

        // 監(jiān)聽
        struct epoll_event evs[1]; // 接收從內(nèi)核返回的有變化的文件描述符的數(shù)組。
        while (1) {
            int n = epoll_wait(epfd, evs, 1, -1);
            if (1 == n) {
                char buf[64] = "";
                n = read(fd[0], buf, 64);
                if (n <= 0) {
                    printf("子進(jìn)程關(guān)閉了寫端");
                    close(fd[0]);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd[0], &ev); // 下樹
                    break;
                } else {
                    printf("讀到子進(jìn)程寫的內(nèi)容:%s\n", buf);
                }
            }
        }
    }
    return 0;
}

運(yùn)行結(jié)果:

Linux多路IO復(fù)用:epoll

(2)epoll實(shí)現(xiàn)簡(jiǎn)單并發(fā)服務(wù)器示例:

#include<stdio.h>
#include<sys/epoll.h>
#include"wrap.h"

int main(int argc, const char* argv[]) {

    // 1.創(chuàng)建socket,綁定
    int lfd = tcp4bind(8888, NULL);

    // 2.監(jiān)聽
    Listen(lfd, 128);

    // 3.創(chuàng)建樹
    int epfd = epoll_create(1);

    // 5.將lfd上樹
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    // 4.while epoll_wait監(jiān)聽
    struct epoll_event evs[1024];
    while (1) {
        int n = epoll_wait(epfd, evs, 1024, -1); // 阻塞監(jiān)聽到有文件描述符變化才返回
        if (n < 0) {  // 調(diào)用出錯(cuò)
            perror("epoll_wait");
            break;
        } else if (0 == n) {
            continue;
        } else {  // 有文件描述符變化
            for (int i = 0;i < n;i++) {
                // 若lfd有讀事件
                if (evs[i].data.fd == lfd && evs[i].events & EPOLLIN) {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len); // 提取
                    printf("新連接到來:IP = %s, port = %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
                        ntohs(cliaddr.sin_port));

                    ev.data.fd = cfd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // cfd上樹;監(jiān)聽cfd的讀事件

                } else if (evs[i].events & EPOLLIN) { // 若cfd有讀事件
                    char buf[1024] = "";
                    int n = read(evs[i].data.fd, buf, 1024);
                    if (n < 0) { // 出錯(cuò)
                        perror("read");
                        close(evs[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下樹
                    } else if (0 == n) {  // 客戶端關(guān)閉
                        printf("客戶端關(guān)閉.\n");
                        close(evs[i].data.fd);
                        epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下樹
                    } else {
                        write(STDOUT_FILENO, buf, 1024); / printf依賴于字符串終止符'\0',而write輸出指定長(zhǎng)度的字符串。
                    }
                }
            }
        }
    }
    return 0;
}

運(yùn)行結(jié)果:

Linux多路IO復(fù)用:epoll


4.? epoll的兩種工作方式

epoll有兩種工作方式:水平觸發(fā)(LT)、邊緣觸發(fā)(ET)。

(1)水平觸發(fā)

? ? ? ? 如監(jiān)聽文件描述符的讀緩沖區(qū)時(shí),只要讀緩沖區(qū)有數(shù)據(jù)就會(huì)觸發(fā)epoll_wait。例如讀緩沖區(qū)有數(shù)據(jù),只要沒讀干凈,就會(huì)觸發(fā)epoll_wait。

? ? ? ? 如監(jiān)聽文件描述符的寫緩沖區(qū)時(shí),只要可寫就會(huì)觸發(fā)epoll_wait,因此監(jiān)聽寫緩沖區(qū)時(shí)推薦使用邊緣觸發(fā)。

(2)邊緣觸發(fā)

????????如監(jiān)聽文件描述符的讀緩沖區(qū)時(shí),讀緩沖區(qū)有數(shù)據(jù)到來才會(huì)觸發(fā)epoll_wait;與水平觸發(fā)不一樣,緩沖區(qū)數(shù)據(jù)沒讀干凈且無數(shù)據(jù)到來,則下次不會(huì)再觸發(fā)epoll_wait,因此要求一次性將讀緩沖區(qū)數(shù)據(jù)讀干凈。

????????如監(jiān)聽文件描述符的寫緩沖區(qū)時(shí),寫緩沖區(qū)數(shù)據(jù)從有到無才會(huì)觸發(fā)epoll_wait。

epoll默認(rèn)工作方式為水平觸發(fā),但推薦使用邊緣觸發(fā),以減少epoll_wait系統(tǒng)調(diào)用次數(shù)。


5. epoll的邊緣觸發(fā)使用示例

使用邊緣觸發(fā),主要兩點(diǎn):1. 監(jiān)聽的事件加上邊緣觸發(fā)的屬性;2. 只要觸發(fā)就一次性將事情處理完。

1. 監(jiān)聽的事件加上邊緣觸發(fā)的屬性

無需將監(jiān)聽的文件描述符設(shè)置為邊緣觸發(fā),而是將與客戶端通信的文件描述符設(shè)置為邊緣觸發(fā),需將上面的 “epoll實(shí)現(xiàn)簡(jiǎn)單并發(fā)服務(wù)器示例” 代碼第44行:

ev.events = EPOLLIN;

?改為如下,即加上邊緣觸發(fā)的屬性。

ev.events = EPOLLIN | EPOLLET;

2. 只要觸發(fā)就一次性將事情處理完

? ? ? ? 以讀事件為例,將上面的 “epoll實(shí)現(xiàn)簡(jiǎn)單并發(fā)服務(wù)器示例” 一次性讀取字節(jié)數(shù)由1024B變?yōu)?B,則大多數(shù)情況下無法一次read調(diào)用就讀完緩沖區(qū)中所有數(shù)據(jù),因此循環(huán)讀取緩沖區(qū),直至讀完。

? ? ? ? 由于是循環(huán)讀取,直至讀完,因此文件描述符cfd需要設(shè)置為非阻塞,否則循環(huán)到最后一次無數(shù)據(jù)可讀時(shí),read函數(shù)將阻塞,無法返回繼續(xù)監(jiān)聽。

????????而水平觸發(fā)時(shí),結(jié)合上面代碼,read是阻塞的,但通常不會(huì)阻塞住。因?yàn)槭侵灰袛?shù)據(jù)可讀就會(huì)觸發(fā)epoll_wait,無數(shù)據(jù)就不會(huì)觸發(fā),因此不會(huì)阻塞住。

上述 "epoll實(shí)現(xiàn)簡(jiǎn)單并發(fā)服務(wù)器示例"?改為邊緣觸發(fā):

#include<stdio.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include"wrap.h"

int main(int argc, const char* argv[]) {

    // 1.創(chuàng)建socket,綁定
    int lfd = tcp4bind(8888, NULL);

    // 2.監(jiān)聽
    Listen(lfd, 128);

    // 3.創(chuàng)建樹
    int epfd = epoll_create(1);

    // 5.將lfd上樹
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    // 4.while epoll_wait監(jiān)聽
    struct epoll_event evs[1024];
    while (1) {
        int n = epoll_wait(epfd, evs, 1024, -1); // 阻塞監(jiān)聽到有文件描述符變化才返回
        if (n < 0) {  // 調(diào)用出錯(cuò)
            perror("epoll_wait");
            break;
        } else if (0 == n) {
            continue;
        } else {  // 有文件描述符變化
            for (int i = 0;i < n;i++) {
                // 若lfd有讀事件
                if (evs[i].data.fd == lfd && evs[i].events & EPOLLIN) {
                    struct sockaddr_in cliaddr;
                    char ip[16] = "";
                    socklen_t len = sizeof(cliaddr);
                    int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len); // 提取

                    /* 設(shè)置cfd非阻塞 */
                    int flag = fcntl(cfd, F_GETFL);
                    flag |= O_NONBLOCK;
                    fcntl(cfd, F_SETFL, flag);

                    printf("新連接到來:IP = %s, port = %d\n",
                        inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
                        ntohs(cliaddr.sin_port));

                    ev.data.fd = cfd;
                    ev.events = EPOLLIN | EPOLLET;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // cfd上樹;監(jiān)聽cfd的讀事件

                } else if (evs[i].events & EPOLLIN) { // 若cfd有讀事件
                    while (1) { // 循環(huán)讀取
                        char buf[4] = "";
                        /*緩沖區(qū)無數(shù)據(jù)時(shí),以阻塞的方式讀取,則會(huì)阻塞等待;若以非阻塞的方式讀取,則返回-1,并且設(shè)置errno為EAGAIN*/
                        int n = read(evs[i].data.fd, buf, 4);
                        if (n < 0) { // 出錯(cuò)
                            if (EAGAIN == errno) { // 緩沖區(qū)被讀干凈,則繼續(xù)下一次監(jiān)聽
                                break;
                            }
                            perror("read");
                            close(evs[i].data.fd);
                            epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下樹
                            break;
                        } else if (0 == n) {  // 客戶端關(guān)閉
                            printf("客戶端關(guān)閉.\n");
                            close(evs[i].data.fd);
                            epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下樹
                            break;
                        } else {
                            write(STDOUT_FILENO, buf, 4); // printf依賴于字符串終止符'\0',而write輸出指定長(zhǎng)度的字符串。
                        }
                    }
                }
            }
        }
    }
    return 0;
}

運(yùn)行結(jié)果:

Linux多路IO復(fù)用:epoll文章來源地址http://www.zghlxwxcb.cn/news/detail-440083.html

到了這里,關(guān)于Linux多路IO復(fù)用:epoll的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll

    【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll

    所有通過捷徑所獲取的快樂,無論是金錢、性還是名望,最終都會(huì)給自己帶來痛苦 1. 后端服務(wù)器最常用的網(wǎng)絡(luò)IO設(shè)計(jì)模式其實(shí)就是Reactor,也稱為反應(yīng)堆模式,Reactor是單進(jìn)程,單線程的,但他能夠處理多客戶端向服務(wù)器發(fā)起的網(wǎng)絡(luò)IO請(qǐng)求,正因?yàn)樗菃螆?zhí)行流,所以他的成本就

    2024年02月09日
    瀏覽(25)
  • Linux多路IO復(fù)用技術(shù)——epoll詳解與一對(duì)多服務(wù)器實(shí)現(xiàn)

    Linux多路IO復(fù)用技術(shù)——epoll詳解與一對(duì)多服務(wù)器實(shí)現(xiàn)

    本文詳細(xì)介紹了Linux中epoll模型的優(yōu)化原理和使用方法,以及如何利用epoll模型實(shí)現(xiàn)簡(jiǎn)易的一對(duì)多服務(wù)器。通過對(duì)epoll模型的優(yōu)化和相關(guān)接口的解釋,幫助讀者理解epoll模型的工作原理和優(yōu)缺點(diǎn),同時(shí)附帶代碼實(shí)現(xiàn)和圖解說明。

    2024年02月05日
    瀏覽(27)
  • 驅(qū)動(dòng)開發(fā),IO多路復(fù)用實(shí)現(xiàn)過程,epoll方式

    驅(qū)動(dòng)開發(fā),IO多路復(fù)用實(shí)現(xiàn)過程,epoll方式

    被稱為當(dāng)前時(shí)代最好用的io多路復(fù)用方式; 核心操作:一棵樹(紅黑樹)、一張表(內(nèi)核鏈表)以及三個(gè)接口; ?思想:(fd代表文件描述符) ????????epoll要把檢測(cè)的事件fd掛載到內(nèi)核空間紅黑樹上,遍歷紅黑樹,調(diào)用每個(gè)fd對(duì)應(yīng)的操作方法,找到發(fā)生事件的fd,如果沒有發(fā)

    2024年02月07日
    瀏覽(30)
  • 【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)

    服務(wù)器模型主要分為兩種, 循環(huán)服務(wù)器 和 并發(fā)服務(wù)器 。 循環(huán)服務(wù)器 : 在同一時(shí)間只能處理一個(gè)客戶端的請(qǐng)求。 并發(fā)服務(wù)器 : 在同一時(shí)間內(nèi)能同時(shí)處理多個(gè)客戶端的請(qǐng)求。 TCP的服務(wù)器默認(rèn)的就是一個(gè)循環(huán)服務(wù)器,原因是有兩個(gè)阻塞 accept函數(shù) 和recv函數(shù) 之間會(huì)相互影響。

    2024年02月03日
    瀏覽(100)
  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? 頭文件 //TcpGrpSer.c? ? ?服務(wù)器端 //TcpGrpUsr.c? ? ?客戶端 通過IO多路復(fù)用實(shí)現(xiàn)服務(wù)器在單進(jìn)程單線程下可以與多個(gè)客戶端交互 ?API epoll函數(shù) ?head.h TcpGrpSer.c TcpGrpUsr.c ?

    2024年02月11日
    瀏覽(24)
  • 【TCP服務(wù)器的演變過程】使用IO多路復(fù)用器epoll實(shí)現(xiàn)TCP服務(wù)器

    【TCP服務(wù)器的演變過程】使用IO多路復(fù)用器epoll實(shí)現(xiàn)TCP服務(wù)器

    手把手教你從0開始編寫TCP服務(wù)器程序,體驗(yàn)開局一塊磚,大廈全靠壘。 為了避免篇幅過長(zhǎng)使讀者感到乏味,對(duì)【TCP服務(wù)器的開發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級(jí)。 本節(jié),在上一章節(jié)的基礎(chǔ)上,將IO多路復(fù)用機(jī)制select改為更高效的IO多路復(fù)用機(jī)制epoll,使用epoll管理每

    2024年01月17日
    瀏覽(16)
  • 多路轉(zhuǎn)接高性能IO服務(wù)器|select|poll|epoll|模型詳細(xì)實(shí)現(xiàn)

    多路轉(zhuǎn)接高性能IO服務(wù)器|select|poll|epoll|模型詳細(xì)實(shí)現(xiàn)

    那么這里博主先安利一下一些干貨滿滿的專欄啦! Linux專欄 https://blog.csdn.net/yu_cblog/category_11786077.html?spm=1001.2014.3001.5482 操作系統(tǒng)專欄 https://blog.csdn.net/yu_cblog/category_12165502.html?spm=1001.2014.3001.5482 手撕數(shù)據(jù)結(jié)構(gòu) https://blog.csdn.net/yu_cblog/category_11490888.html?spm=1001.2014.3001.5482 去倉(cāng)庫(kù)獲

    2024年02月15日
    瀏覽(25)
  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? ? ? ?頭文件 //TcpGrpSer.c? ? ? ? 服務(wù)器端 //TcpGrpUsr.c? ? ? ? 客戶端 select函數(shù)? 功能:阻塞函數(shù),讓內(nèi)核去監(jiān)測(cè)集合中的文件描述符是否準(zhǔn)備就緒,若準(zhǔn)備就緒則解除阻塞。 原型: head.h TcpGrpSer.c TcpGrpUsr.c ? ?

    2024年02月14日
    瀏覽(24)
  • IO多路復(fù)用中select的TCP服務(wù)器模型和poll服務(wù)模型

    服務(wù)器端 客戶端 poll客戶端

    2024年02月12日
    瀏覽(31)
  • TCP服務(wù)器的演變過程:IO多路復(fù)用機(jī)制select實(shí)現(xiàn)TCP服務(wù)器

    TCP服務(wù)器的演變過程:IO多路復(fù)用機(jī)制select實(shí)現(xiàn)TCP服務(wù)器

    手把手教你從0開始編寫TCP服務(wù)器程序,體驗(yàn)開局一塊磚,大廈全靠壘。 為了避免篇幅過長(zhǎng)使讀者感到乏味,對(duì)【TCP服務(wù)器的開發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級(jí)。 本節(jié),在上一章節(jié)的基礎(chǔ)上,將并發(fā)的實(shí)現(xiàn)改為IO多路復(fù)用機(jī)制,使用select管理每個(gè)新接入的客戶端連

    2024年02月03日
    瀏覽(15)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包