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

【Linux】C++項目實戰(zhàn)-高并發(fā)服務(wù)器詳析

這篇具有很好參考價值的文章主要介紹了【Linux】C++項目實戰(zhàn)-高并發(fā)服務(wù)器詳析。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

橙色

多進(jìn)程實現(xiàn)并發(fā)服務(wù)器

server_process.c文件內(nèi)容如下:

注意第70行的if(errno == EINTR),如果沒有這個if判斷的話,當(dāng)同時多個客戶端鏈接進(jìn)來,停掉一個客戶端,然后再啟動一個客戶端,就會發(fā)現(xiàn)沒法連接了,accept會報一個錯誤。因為一個客戶端停掉,在服務(wù)器端就相當(dāng)于一個子進(jìn)程終止執(zhí)行,會發(fā)出SIGCHLD信號,被信號捕捉函數(shù)所捕捉,而此時程序正停在accept處阻塞,等待下一個客戶端的連接。當(dāng)信號捕捉函數(shù)處理完再返回accpet時,就會報一個錯誤,該錯誤為EINTR。這個也可以去看accept函數(shù)的介紹,有說明(如下圖)。所以這里要做一個處理,如果errno是EINTR的話,則略過該報錯。
高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++


第101行strlen(recvBuf) + 1是很有必要的,strlen在計數(shù)的時候是結(jié)束符’\0’為止,但不包含結(jié)束符。+1之后寫入文件描述符的字符串就會帶上結(jié)束符。如果不帶上結(jié)束符的話,在另一端通過文件描述符讀出的時候,數(shù)據(jù)的最末尾很容易出現(xiàn)一個奇怪的符號。

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>

void recyleChild(int arg) {
    while(1) {
        int ret = waitpid(-1, NULL, WNOHANG);
        if(ret == -1) {
            // 所有的子進(jìn)程都回收了
            break;
        }else if(ret == 0) {
            // 還有子進(jìn)程活著
            break;
        } else if(ret > 0){
            // 被回收了
            printf("子進(jìn)程 %d 被回收了\n", ret);
        }
    }
}

int main() {

    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = recyleChild;
    // 注冊信號捕捉
    sigaction(SIGCHLD, &act, NULL);
    

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 監(jiān)聽
    ret = listen(lfd, 128);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 不斷循環(huán)等待客戶端連接
    while(1) {

        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        // 接受連接
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
        if(cfd == -1) {
            if(errno == EINTR) {
                continue;
            }
            perror("accept");
            exit(-1);
        }

        // 每一個連接進(jìn)來,創(chuàng)建一個子進(jìn)程跟客戶端通信
        pid_t pid = fork();
        if(pid == 0) {
            // 子進(jìn)程
            // 獲取客戶端的信息
            char cliIp[16];
            inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
            unsigned short cliPort = ntohs(cliaddr.sin_port);
            printf("client ip is : %s, prot is %d\n", cliIp, cliPort);

            // 接收客戶端發(fā)來的數(shù)據(jù)
            char recvBuf[1024];
            while(1) {
                int len = read(cfd, &recvBuf, sizeof(recvBuf));

                if(len == -1) {
                    perror("read");
                    exit(-1);
                }else if(len > 0) {
                    printf("recv client : %s\n", recvBuf);
                } else if(len == 0) {
                    printf("client closed....\n");
                    break;
                }
                write(cfd, recvBuf, strlen(recvBuf) + 1);
            }
            close(cfd);
            exit(0);    // 退出當(dāng)前子進(jìn)程
        }

    }
    close(lfd);
    return 0;
}

client.c文件內(nèi)容如下:

// TCP通信的客戶端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

    // 1.創(chuàng)建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.連接服務(wù)器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.193.128", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }
    
    // 3. 通信
    char recvBuf[1024];
    int i = 0;
    while(1) {
        
        sprintf(recvBuf, "data : %d\n", i++);
        
        // 給服務(wù)器端發(fā)送數(shù)據(jù)
        write(fd, recvBuf, strlen(recvBuf)+1);

        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server : %s\n", recvBuf);
        } else if(len == 0) {
            // 表示服務(wù)器端斷開連接
            printf("server closed...");
            break;
        }

        sleep(1);
    }

    // 關(guān)閉連接
    close(fd);

    return 0;
}

多線程實現(xiàn)并發(fā)服務(wù)器

客戶端文件內(nèi)容同上

服務(wù)器端文件內(nèi)容如下:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

struct sockInfo {
    int fd; // 通信的文件描述符
    struct sockaddr_in addr;
    pthread_t tid;  // 線程號
};

struct sockInfo sockinfos[128];

void * working(void * arg) {
    // 子線程和客戶端通信   需要cfd 客戶端的信息 線程號
    // 獲取客戶端的信息
    struct sockInfo * pinfo = (struct sockInfo *)arg;

    char cliIp[16];
    inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp));
    unsigned short cliPort = ntohs(pinfo->addr.sin_port);
    printf("client ip is : %s, prot is %d\n", cliIp, cliPort);

    // 接收客戶端發(fā)來的數(shù)據(jù)
    char recvBuf[1024];
    while(1) {
        int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf));

        if(len == -1) {
            perror("read");
            exit(-1);
        }else if(len > 0) {
            printf("recv client : %s\n", recvBuf);
        } else if(len == 0) {
            printf("client closed....\n");
            break;
        }
        write(pinfo->fd, recvBuf, strlen(recvBuf) + 1);
    }
    close(pinfo->fd);
    return NULL;
}

int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    // 監(jiān)聽
    ret = listen(lfd, 128);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    // 初始化數(shù)據(jù)
    int max = sizeof(sockinfos) / sizeof(sockinfos[0]);
    for(int i = 0; i < max; i++) {
        bzero(&sockinfos[i], sizeof(sockinfos[i]));//將結(jié)構(gòu)體里面所有的成員都初始化為0
        sockinfos[i].fd = -1;
        sockinfos[i].tid = -1;
    }

    // 循環(huán)等待客戶端連接,一旦一個客戶端連接進(jìn)來,就創(chuàng)建一個子線程進(jìn)行通信
    while(1) {

        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        // 接受連接
        int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);

        struct sockInfo * pinfo;
        for(int i = 0; i < max; i++) {
            // 從這個數(shù)組中找到一個可以用的sockInfo元素
            if(sockinfos[i].fd == -1) {
                pinfo = &sockinfos[i];
                break;
            }
            if(i == max - 1) {
                sleep(1);
                i=-1;
            }
        }

        pinfo->fd = cfd;
        memcpy(&pinfo->addr, &cliaddr, len);//拷貝數(shù)據(jù)

        // 創(chuàng)建子線程,因為線程號僅僅在線程創(chuàng)建后才有,所以直接在這里傳入pinfo->tid,就很方便
        pthread_create(&pinfo->tid, NULL, working, pinfo);

        //這里不能使用pthread_join,因為它是阻塞函數(shù),那么一個子線程沒結(jié)束主線程就只能阻塞在這里,沒辦法創(chuàng)建新的線程
        pthread_detach(pinfo->tid);
    }

    close(lfd);
    return 0;
}

BIO模型

阻塞等待:不占用CPU寶貴的時間片,但是每次只能處理一個操作。
高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++

當(dāng)對方暫時沒有發(fā)送數(shù)據(jù)時,程序就會阻塞在read處


BIO模型:通過多線程/多進(jìn)程解決每次只能處理一個操作的缺陷。但是線程/進(jìn)程本身需要消耗系統(tǒng)資源,并且線程和進(jìn)程的調(diào)度占用CPU.
高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++

NIO模型

非阻塞、忙輪詢:不斷的去催,或者說每隔一端時間就去查看有沒有操作

提高了程序的運行效率、但占用大量CPU資源和系統(tǒng)資源(假設(shè)有1w個客戶端鏈接進(jìn)來,那么服務(wù)器端讀取某一個客戶端的內(nèi)容最慢可能達(dá)到第1w次才能讀到,因為它要依次對這1w個客戶端進(jìn)行輪詢。但可能這1w次輪詢中,僅有一個客戶端的數(shù)據(jù)到達(dá)了,那么其余的9999次遍歷就都浪費了)

高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++

I/O多路復(fù)用(I/O多路轉(zhuǎn)接)

把文件中的數(shù)據(jù)寫入到內(nèi)存中就是輸入,把內(nèi)存中的數(shù)據(jù)寫入到文件中就是輸出

? ? ? ?I/O多路復(fù)用使得程序能夠同時監(jiān)聽多個文件描述符,能夠提高程序的性能,Linux下實現(xiàn)I/O多路復(fù)用的系統(tǒng)調(diào)用有:select、pool和epoll

select

主旨思想
  1. 首先要構(gòu)造一個關(guān)于文件描述符的列表,將要監(jiān)聽的文件描述符添加到該列表中
  2. 調(diào)用一個系統(tǒng)函數(shù),監(jiān)聽該列表中的文件描述符,直到這些描述符中的一個或多個進(jìn)行I/O操作時,該函數(shù)才返回。
    ? ? ? ?a. 這個函數(shù)是阻塞的
    ? ? ? ?b. 函數(shù)對文件描述符的檢測的操作是由內(nèi)核完成的
  3. 在返回時,它會告訴進(jìn)程有多少描述符要進(jìn)行I/O操作
圖解原理

高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++
前三個是系統(tǒng)固定已經(jīng)占用的

函數(shù)解析
//sizeof(fd_set)=128字節(jié)   也就是1024位
#include<sys/time.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timerval *timeval);
	- 參數(shù):
		- nfds:委托內(nèi)核檢測的最大的文件描述符的值+1
        - readfds:要檢測的文件描述符的讀的集合,委托內(nèi)核檢測哪些文件描述符的讀的屬性
        	- 對應(yīng)的是對方發(fā)送過來的數(shù)據(jù),因為讀是被動的接收數(shù)據(jù),檢測的就是讀緩沖區(qū)
        	- 是一個傳入傳出參數(shù)(比如我想看第5個文件描述符是否可以讀,那我把它置為1,傳入函數(shù),函數(shù)會把這個列表指針交給內(nèi)核,內(nèi)核來檢查,如果該文件描述符確實可以讀,那么內(nèi)核會把它置為1,不可讀,內(nèi)核就會把它置為0- writefds:要檢測的文件描述符的寫的集合,委托內(nèi)核檢測哪些文件描述符的寫的屬性
        	- 委托內(nèi)核檢測寫緩沖區(qū)是不是還可以寫數(shù)據(jù)〈不滿的就可以寫,也就是置為1)
        - exceptfds:檢測發(fā)生異常的文件描述符的集合(一般不用)
        - timeout:設(shè)置的超時時間
        	struct timeval {
               time_t      tv_sec;         /* seconds */
               suseconds_t tv_usec;        /* microseconds */
           };

			- NULL:永遠(yuǎn)等待,直到檢測到了文件描述符有變化
			- tv_sec=0 tv_usec=0, 不阻塞
			- tv_sec>0 tv_usec>0,阻塞對應(yīng)的時間

		- 返回值:
			- -1:失敗
			- >0(n):檢測的集合中有n個文件描述符發(fā)生了變化		

//將參數(shù)文件描述符fd對應(yīng)的標(biāo)志位設(shè)為0
void FD_CLR(int fd, fd_set *set);
//判斷fd對應(yīng)的標(biāo)志位是0還是1,返回值:fa對應(yīng)的標(biāo)志位的值是0,返回0,是1,返回1
int  FD_ISSET(int fd, fd_set *set);
//將參數(shù)文件描述符fd對應(yīng)的標(biāo)志位設(shè)為1
void FD_SET(int fd, fd_set *set);
//fd_set一共有1024位,全部初始化為0
void FD_ZERO(fd_set *set);   
代碼舉例

客戶端程序:

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 創(chuàng)建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 連接服務(wù)器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};
        sprintf(sendBuf, "send data %d", num++);
        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服務(wù)器已經(jīng)斷開連接...\n");
            break;
        }
        sleep(1);
        
    }

    close(fd);

    return 0;
}

服務(wù)器端程序:

這個服務(wù)器端的程序里還是蘊含了很多細(xì)節(jié)需要注意的。

我對第一次循環(huán)進(jìn)行一個分析,先是在rdset中把監(jiān)聽描述符lfd置為了1。接著進(jìn)入了while(1)死循環(huán)。

為了避免循環(huán)中select函數(shù)在傳入rdset時改變了rdset(因為rdset中記錄的是我所需要檢測的文件描述符,應(yīng)該一直是1,但如果將rdset傳入select,在該次循環(huán)中需要被檢測的文件描述符并沒有數(shù)據(jù)傳入,那么就會被內(nèi)核置為0,所以需要tmp),所以在循環(huán)開始將rdset拷貝給tmp。

接著,當(dāng)ret>0時,說明肯定有文件描述符變了,那就先看lfd,看是否是有新的客戶端連接進(jìn)來,如果有的話,則加入到集合rdset中(這里可能會有疑惑,為什么不在FD_SET(cfd, &rdset);后加一行FD_SET(cfd, &tmp);呢?這樣這個新端口傳入的數(shù)據(jù)也就能在該次死循環(huán)中讀取出來了。但考慮到可能這個新端口僅僅只是連接,并沒有傳入數(shù)據(jù),那read讀不到數(shù)據(jù)就會阻塞在這里,因此沒有加,讓它在下一次循環(huán)中再讀是比較保險的)

將這兩個程序運行起來,客戶端無論幾個,服務(wù)器都是能運行的,既沒有借助多線程也沒有借助多進(jìn)程,而是依靠了select函數(shù)。

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>

int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 監(jiān)聽
    listen(lfd, 8);

    // 創(chuàng)建一個fd_set的集合,存放的是需要檢測的文件描述符
    fd_set rdset, tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd, &rdset);
    int maxfd = lfd;

    while(1) {

        tmp = rdset;

        // 調(diào)用select系統(tǒng)函數(shù),讓內(nèi)核幫檢測哪些文件描述符有數(shù)據(jù)
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        if(ret == -1) {
            perror("select");
            exit(-1);
        } else if(ret == 0) {  //不可能為0,因為上面select設(shè)置的是阻塞,只有當(dāng)文件描述符有變化時才會到這里
            continue;
        } else if(ret > 0) {
            // 說明檢測到了有文件描述符的對應(yīng)的緩沖區(qū)的數(shù)據(jù)發(fā)生了改變
            //為什么要檢測lfd是否為1呢?因為第一次發(fā)生了改變肯定是lfd,但后面發(fā)生改變就可能是其他的文件描述符,而不是lfd(也就是說不是有新的文件描述符加進(jìn)來)
            if(FD_ISSET(lfd, &tmp)) {
                // 表示有新的客戶端連接進(jìn)來了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                // 將新的文件描述符加入到集合中
                FD_SET(cfd, &rdset);

                // 更新最大的文件描述符
                maxfd = maxfd > cfd ? maxfd : cfd;
            }
            //要檢測的是連接描述符的數(shù)據(jù)有沒有變化,所以不需要檢測監(jiān)聽文件描述符,循環(huán)從lfd+1開始
            for(int i = lfd + 1; i <= maxfd; i++) {
                if(FD_ISSET(i, &tmp)) {
                    // 說明這個文件描述符對應(yīng)的客戶端發(fā)來了數(shù)據(jù)
                    char buf[1024] = {0};
                    int len = read(i, buf, sizeof(buf));
                    if(len == -1) {
                        perror("read");
                        exit(-1);
                    } else if(len == 0) {
                        printf("client closed...\n");
                        close(i);
                        FD_CLR(i, &rdset);
                    } else if(len > 0) {
                        printf("read buf = %s\n", buf);
                        write(i, buf, strlen(buf) + 1);
                    }
                }
            }

        }

    }
    close(lfd);
    return 0;
}
select的缺點
  1. 每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在fd很多時會很大

  2. 同時每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個開銷在fd很多時也很大

  3. select支持的文件描述符數(shù)量太小了,默認(rèn)是1024

  4. fds集合不能重用,每次都需要重置(其實說的就是上面服務(wù)器端程序定義了兩個fd_set,如果只用一個傳入內(nèi)核,該要檢測的端口這時并沒有數(shù)據(jù)到達(dá),那么就會被內(nèi)核置為0再傳遞出來。那么下次再傳入就不會檢測該端口了,而這顯然是不行的)

poll

poll只針對Linux有效,poll模型是基于select最大文件描述符限制提出的,跟select一樣,只是將select使用的三個基于位的文件描述符(readfds/writefds/exceptfds)封裝成了一個結(jié)構(gòu)體,然后通過數(shù)組的是形式來突破最大文件描述符的限制。

函數(shù)解析
#include <poll.h>
struct pollfd{
	int fd;                  //委托內(nèi)核檢測的文件描述符
	short  events;           //委托內(nèi)核檢測文件描述符的什么事件
	short  revents;          //文件描述符實際發(fā)生的事件
}; 

//既要檢測讀也要檢測寫該怎么寫?
struct po11fd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;

int poll(struct pollfd *fds,nfds_t nfds,int timeout);
	- 參數(shù):
		- fds:數(shù)組的首地址
		- nfds:這個是第一個參數(shù)數(shù)組中最后一個有效元素的下標(biāo)+1
		- timeout:阻塞時長
			0:不阻塞
			-1:阻塞,當(dāng)檢測到需要檢測的文件描述符有變化,解除阻塞
			>0:阻塞時長(單位是毫秒)
	- 返回值:
		-1:失敗
		>0(n):成功, n表示檢測到集合中有n個文件描述符發(fā)生變化
		

高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++

代碼示例

客戶端程序和select中的一樣
服務(wù)器端程序如下:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>


int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 監(jiān)聽
    listen(lfd, 8);

    // 初始化檢測的文件描述符數(shù)組
    struct pollfd fds[1024];
    for(int i = 0; i < 1024; i++) {
        fds[i].fd = -1;
        fds[i].events = POLLIN;
    }
    fds[0].fd = lfd;
    int nfds = 0;
    int i;
    while(1) {

        // 調(diào)用poll系統(tǒng)函數(shù),讓內(nèi)核幫檢測哪些文件描述符有數(shù)據(jù)
        int ret = poll(fds, nfds + 1, -1);
        if(ret == -1) {
            perror("poll");
            exit(-1);
        } else if(ret == 0) {
            continue;
        } else if(ret > 0) {
            // 說明檢測到了有文件描述符的對應(yīng)的緩沖區(qū)的數(shù)據(jù)發(fā)生了改變
            if(fds[0].revents & POLLIN) {
                // 表示有新的客戶端連接進(jìn)來了
                //先看結(jié)構(gòu)體數(shù)組中是否有空位,沒空位的話就等下次再accept新的客戶端,有的話就直接accept
                for(i = 1; i < 1024; i++) {
                    if(fds[i].fd == -1) {                      
                        struct sockaddr_in cliaddr;
                        int len = sizeof(cliaddr);
                        int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                        // 將新的文件描述符加入到集合中
                        fds[i].fd = cfd;
                        fds[i].events = POLLIN;
                    
                        // 更新最大的文件描述符的索引
                        nfds = nfds > i ? nfds : i;
                        break;
                    }
                }   
            }

            for(int i = 1; i <= nfds; i++) {
                if(fds[i].revents & POLLIN) {
                    // 說明這個文件描述符對應(yīng)的客戶端發(fā)來了數(shù)據(jù)
                    char buf[1024] = {0};
                    int len = read(fds[i].fd, buf, sizeof(buf));
                    if(len == -1) {
                        perror("read");
                        exit(-1);
                    } else if(len == 0) {
                        printf("client closed...\n");
                        close(fds[i].fd);
                        fds[i].fd = -1;
                    } else if(len > 0) {
                        printf("read buf = %s\n", buf);
                        write(fds[i].fd, buf, strlen(buf) + 1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

epoll(最重要,請重點掌握)

高并發(fā)服務(wù)器開發(fā),# Linux網(wǎng)絡(luò)編程,服務(wù)器,linux,c++

函數(shù)解析

#include <sys/epoll.h>
//創(chuàng)建一個新的epoll示例。在內(nèi)核中創(chuàng)建了一個數(shù)據(jù)。這個數(shù)據(jù)中有兩個比較重要的數(shù)據(jù),一個是需要檢測的文件描述符的信息(紅黑樹〉,還有一個是就緒列表,存放檢測到數(shù)據(jù)發(fā)送改變的文件描述符信息(雙向鏈表〉。
int epoll_create(int size); 
 	 - 參數(shù): size : 目前沒有意義了。隨便寫一個數(shù),必須大于0
 	 - 返回值: -1 : 失敗, > 0 : 文件描述符,操作epoll實例的




//對epo11實例進(jìn)行管理:添加文件描述符信息,刪除信息,修改信息
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
	- 參數(shù):
		- epfd:epoll實例對應(yīng)的文件描述符
		- op:要進(jìn)行什么操作
			EPOLL_CTL_ADD:添加
			EPOLL_CTL_MOD:修改
			EPOLL_CTL_DEL:刪除
		- fd:要檢測的文件描述符
		- event:檢測文件描述符什么事情(如果是刪除操作的話直接NULL就行)
		  struct epoll_event{
		  	  _uint32_t         events;                // Epoll events
	 		  epoll_data       data;                    //user data variable
		  };
		  typedef union epoll_data {
			  void *ptr;                                        //回調(diào)函數(shù)
			  int fd;
			  uint32_t u32;
			  uint64_t u64;
		  } epoll_data_t;	

常見的Epoll檢測事件(events):
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET(邊沿模式)如果想要使用邊沿模式并檢測是否可以讀,events可以這么寫:EPOLLIN | EPOLLET

//檢測函數(shù),檢測內(nèi)核中的eventpoll是否有文件描述符改變了,注意events是一個struct epoll_event數(shù)組的指針
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);  
	- 參數(shù):
		- epfd:epo11實例對應(yīng)的文件描述符
		- events:傳出參數(shù),是一個struct epoll_event數(shù)組的指針,保存了發(fā)送了變化的文件描述符的信息,
		- maxevent:第二個參數(shù)結(jié)構(gòu)體數(shù)組的大小
		- timeout:阻塞時間
			- 0:不阻塞
			- -1:阻塞,直到檢測到fd數(shù)據(jù)發(fā)生變化,解除阻塞
			- >0:阻塞的時長(毫秒)
	- 返回值:
		- 成功,返回發(fā)送變化的文件描述符的個數(shù)>0
		- 失敗 -1 

代碼舉例

服務(wù)器端:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 監(jiān)聽
    listen(lfd, 8);

    // 調(diào)用epoll_create()創(chuàng)建一個epoll實例
    int epfd = epoll_create(100);

    // 將監(jiān)聽的文件描述符相關(guān)的檢測信息添加到epoll實例中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

    struct epoll_event epevs[1024];

    while(1) {

        int ret = epoll_wait(epfd, epevs, 1024, -1);
        if(ret == -1) {
            perror("epoll_wait");
            exit(-1);
        }

        printf("ret = %d\n", ret);

        //ret代表的是發(fā)生改變的文件描述符的數(shù)量
        for(int i = 0; i < ret; i++) {

            int curfd = epevs[i].data.fd;

            if(curfd == lfd) {
                // 監(jiān)聽的文件描述符有數(shù)據(jù)達(dá)到,有客戶端連接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
            } else {
                if(epevs[i].events & EPOLLOUT) {
                    continue;
                }   
                // 有數(shù)據(jù)到達(dá),需要通信
                char buf[1024] = {0};
                int len = read(curfd, buf, sizeof(buf));
                if(len == -1) {
                    perror("read");
                    exit(-1);
                } else if(len == 0) {
                    printf("client closed...\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                } else if(len > 0) {
                    printf("read buf = %s\n", buf);
                    write(curfd, buf, strlen(buf) + 1);
                }

            }

        }
    }
    close(lfd);
    close(epfd);
    return 0;
}

第59行的if(epevs[i].events & EPOLLOUT)則是為了避免一種情況:當(dāng)我同時檢測文件描述符的讀和寫時,因為下面的代碼都是處理讀這種情況的,所以如果該文件描述符的epevs[i].events是寫的話,則continue,略過這個文件描述符的變動


問題:在最開始調(diào)用epoll_ctl把監(jiān)聽的文件描述符放進(jìn)紅黑樹的時候傳入了&epev,也就是epev的指針,為什么后面?zhèn)魅胄碌奈募枋龇臅r候可以重用這個epev呢,這樣重用epev的話前面?zhèn)魅氲谋O(jiān)聽描述符不就被改動了嘛?還是說其實調(diào)用這個函數(shù)傳入紅黑樹之后,epev里面的數(shù)據(jù)已經(jīng)被拷貝了?
答:當(dāng)調(diào)用epoll_ctl函數(shù)將文件描述符添加到epoll對象中時,epoll會將epoll_event結(jié)構(gòu)體中的數(shù)據(jù)拷貝一份,存儲在自己的內(nèi)存空間中,并將這個拷貝的結(jié)構(gòu)體作為一個節(jié)點插入到紅黑樹中。
這樣做的好處是,當(dāng)文件描述符上的事件發(fā)生時,epoll可以直接從自己的內(nèi)存空間中獲取相應(yīng)的事件信息,而不需要每次都去訪問用戶空間中的epoll_event結(jié)構(gòu)體。這樣可以提高效率,減少系統(tǒng)調(diào)用的次數(shù)。

epoll的兩種工作模式

  • Level Triggered(LT)水平觸發(fā):LT (level - triggered)是缺?。ㄈ笔∫簿褪悄J(rèn)的意思)的工作方式,并且同時支持 block和no-block socket。在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的。
假設(shè)委托內(nèi)核檢測讀事件>檢測fd的讀緩沖區(qū)
    讀緩沖區(qū)有數(shù)據(jù)- > epoll檢測到了會給用戶通知
        a.用戶不讀數(shù)據(jù),數(shù)據(jù)一直在緩沖區(qū),epoll會一直通知
        b.用戶只讀了一部分?jǐn)?shù)據(jù),epoll會通知
        c.緩沖區(qū)的數(shù)據(jù)讀完了
  • Edge Triggred(ET) 邊緣觸發(fā):ET (edge - triggered)是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epoll告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了。但是請注意,如果一直不對這個fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會發(fā)送更多的通知(onlyonce)。

ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務(wù)餓死。

假設(shè)委托內(nèi)核檢測讀事件->檢測fd的讀緩沖區(qū)
    讀緩沖區(qū)有數(shù)據(jù)- > epoll檢測到了會給用戶通知
        a.用戶不讀數(shù)據(jù),數(shù)據(jù)一致在緩沖區(qū)中,epoll下次檢測的時候就不通知了
		b.用戶只讀了一部分?jǐn)?shù)據(jù),epoll不通知
		c.緩沖區(qū)的數(shù)據(jù)讀完了,不通知

代碼舉例:

客戶端程序如下:

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main() {

    // 創(chuàng)建socket
    int fd = socket(PF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(9999);

    // 連接服務(wù)器
    int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num = 0;
    while(1) {
        char sendBuf[1024] = {0};
        // sprintf(sendBuf, "send data %d", num++);
        fgets(sendBuf, sizeof(sendBuf), stdin);

        write(fd, sendBuf, strlen(sendBuf) + 1);

        // 接收
        int len = read(fd, sendBuf, sizeof(sendBuf));
        if(len == -1) {
            perror("read");
            return -1;
        }else if(len > 0) {
            printf("read buf = %s\n", sendBuf);
        } else {
            printf("服務(wù)器已經(jīng)斷開連接...\n");
            break;
        }
    }

    close(fd);

    return 0;
}

epoll水平觸發(fā)模式代碼如下:

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 監(jiān)聽
    listen(lfd, 8);

    // 調(diào)用epoll_create()創(chuàng)建一個epoll實例
    int epfd = epoll_create(100);

    // 將監(jiān)聽的文件描述符相關(guān)的檢測信息添加到epoll實例中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

    struct epoll_event epevs[1024];

    while(1) {

        int ret = epoll_wait(epfd, epevs, 1024, -1);
        if(ret == -1) {
            perror("epoll_wait");
            exit(-1);
        }

        printf("ret = %d\n", ret);

        for(int i = 0; i < ret; i++) {

            int curfd = epevs[i].data.fd;

            if(curfd == lfd) {
                // 監(jiān)聽的文件描述符有數(shù)據(jù)達(dá)到,有客戶端連接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                epev.events = EPOLLIN;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
            } else {
                if(epevs[i].events & EPOLLOUT) {
                    continue;
                }   
                // 有數(shù)據(jù)到達(dá),需要通信
                char buf[5] = {0};
                int len = read(curfd, buf, sizeof(buf));
                if(len == -1) {
                    perror("read");
                    exit(-1);
                } else if(len == 0) {
                    printf("client closed...\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                } else if(len > 0) {
                    printf("read buf = %s\n", buf);
                    write(curfd, buf, strlen(buf) + 1);
                }

            }

        }
    }

    close(lfd);
    close(epfd);
    return 0;
}

epoll邊沿觸發(fā)模式代碼如下:

邊沿觸發(fā)模式的代碼與水平觸發(fā)模式的代碼是有所不同的,在前面的概念中,已經(jīng)了解到了邊沿觸發(fā)模式僅會通知一次文件描述符從未就緒變?yōu)榫途w(也就是有數(shù)據(jù)到了)。所以在邊沿觸發(fā)模式下,我們需要一次就讀完發(fā)送方所發(fā)送的所有內(nèi)容。如果沒有讀完的話,文件描述符的狀態(tài)仍會是就緒,而在下次循環(huán)中epoll不會再通知我們,那發(fā)送方所發(fā)送的剩余的我們未讀完的數(shù)據(jù)就丟失了。


(假設(shè)char buf[2],然后發(fā)送方向文件描述符fd中輸入lllhh,那么在LT模式下,首先epoll_wait會檢測到fd中的讀事件就緒。那么開始讀取,因為buf的容量為2,所以先讀取了ll,接著返回到了循環(huán)開頭的epoll_wait,因為沒讀完,該fd中的讀事件仍就處于就緒狀態(tài),再讀,直到讀完為止。而在ET模式下,先讀取了ll,接著返回到了循環(huán)開頭的epoll_wait,即使因為沒讀完該fd中的讀事件仍就處于就緒狀態(tài),但不予理睬。后面的數(shù)據(jù)也就相當(dāng)于丟失了。值得注意的是會丟失的前提是該發(fā)送方此后沒有再發(fā)送數(shù)據(jù),則剩余未讀的在緩沖區(qū)中的數(shù)據(jù)就會丟失。如果說發(fā)送方又一次數(shù)據(jù),比如發(fā)送了rr,那就會讀取出lh,rr仍舊在讀緩沖區(qū)不會被讀出)


所以說ET模式下要保證一次讀完。那么如何一次讀完呢?自然是需要while循環(huán),但又有個問題,當(dāng)讀完數(shù)據(jù)后,read讀不到數(shù)據(jù)了,但發(fā)送方又沒有斷開連接,這是read就會阻塞在這里,從而導(dǎo)致程序無法再繼續(xù)往下運行。所以我們要設(shè)置read函數(shù)不阻塞,其實也就是設(shè)置套接字非阻塞,用到了fcntl函數(shù)。
而通過把套接字設(shè)置為非阻塞從而使read非阻塞,這就又會導(dǎo)致一個問題,當(dāng)某次遍歷已經(jīng)把文件描述符緩沖區(qū)中的數(shù)據(jù)全部讀完之后,下次來讀,read不阻塞,但文件描述符中又沒有數(shù)據(jù),發(fā)送端連接未關(guān)閉,就會報一個EAGAIN的錯誤。也就是程序中第81行。這種情況下不應(yīng)該退出while循環(huán),所以這里用了一個if來判別。
在第74行printf沒辦法數(shù)據(jù)全部讀完后打印出over,所以將74行的printf改為75行的write,第75行是直接將buf的內(nèi)容寫入到標(biāo)準(zhǔn)輸出,將數(shù)據(jù)寫入到終端或命令行界面進(jìn)行顯示。第76行是將buf的內(nèi)容寫入到curfd套接字中,目的是為了完成回射,使客戶端發(fā)送過來的內(nèi)容再被客戶端讀取出來文章來源地址http://www.zghlxwxcb.cn/news/detail-702332.html

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

int main() {

    // 創(chuàng)建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    // 綁定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    // 監(jiān)聽
    listen(lfd, 8);

    // 調(diào)用epoll_create()創(chuàng)建一個epoll實例
    int epfd = epoll_create(100);

    // 將監(jiān)聽的文件描述符相關(guān)的檢測信息添加到epoll實例中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);

    struct epoll_event epevs[1024];

    while(1) {

        int ret = epoll_wait(epfd, epevs, 1024, -1);
        if(ret == -1) {
            perror("epoll_wait");
            exit(-1);
        }

        printf("ret = %d\n", ret);

        for(int i = 0; i < ret; i++) {

            int curfd = epevs[i].data.fd;

            if(curfd == lfd) {
                // 監(jiān)聽的文件描述符有數(shù)據(jù)達(dá)到,有客戶端連接
                struct sockaddr_in cliaddr;
                int 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);

                epev.events = EPOLLIN | EPOLLET;    // 設(shè)置邊沿觸發(fā)
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
            } else {
                if(epevs[i].events & EPOLLOUT) {
                    continue;
                }  

                // 循環(huán)讀取出所有數(shù)據(jù)
                char buf[5];
                int len = 0;
                while( (len = read(curfd, buf, sizeof(buf))) > 0) {
                    // 打印數(shù)據(jù)
                    // printf("recv data : %s\n", buf);
                    write(STDOUT_FILENO, buf, len);
                    write(curfd, buf, len);
                }
                if(len == 0) {
                    printf("client closed....");
                }else if(len == -1) {
                    if(errno == EAGAIN) {
                        write(STDOUT_FILENO, "over.\n", strlen("over.\n") + 1);
                    }else {
                        perror("read");
                        exit(-1);
                    }
                    
                }

            }

        }
    }

    close(lfd);
    close(epfd);
    return 0;
}

到了這里,關(guān)于【Linux】C++項目實戰(zhàn)-高并發(fā)服務(wù)器詳析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

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

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

相關(guān)文章

  • linux并發(fā)服務(wù)器 —— 動態(tài)庫和靜態(tài)庫實戰(zhàn)(一)

    linux并發(fā)服務(wù)器 —— 動態(tài)庫和靜態(tài)庫實戰(zhàn)(一)

    -E 預(yù)處理指定源文件 -S 編譯指定源文件 -c 匯編指定源文件 -o 生成可執(zhí)行文件 -I directory 指定Include包含文件的搜索目錄 -g 編譯的時候生成調(diào)試信息 -D 在程序編譯時指定一個宏 -w 不生成任何的警告信息 -Wall 生成所有警告 -On n:0~3;表示編譯器的優(yōu)化選項級別 O0 - 不優(yōu)化;O1 -

    2024年02月11日
    瀏覽(20)
  • 一、C++項目:仿muduo庫實現(xiàn)高性能高并發(fā)服務(wù)器

    一、C++項目:仿muduo庫實現(xiàn)高性能高并發(fā)服務(wù)器

    仿mudou庫one thread oneloop式并發(fā)服務(wù)器實現(xiàn) 仿muduo庫One Thread One Loop式主從Reactor模型實現(xiàn)高并發(fā)服務(wù)器: 通過實現(xiàn)的高并發(fā)服務(wù)器組件,可以簡潔快速的完成一個高性能的服務(wù)器搭建。并且,通過組件內(nèi)提供的不同應(yīng)用層協(xié)議支持,也可以快速完成一個高性能應(yīng)用服務(wù)器的搭建

    2024年02月07日
    瀏覽(34)
  • C++linux高并發(fā)服務(wù)器項目實踐 day4

    C++linux高并發(fā)服務(wù)器項目實踐 day4

    int access(const char * pathname ,int mode); int chmod(const char * filename,int mode); int chown(const char* path,uid_t owner,gid_t group); int truncate(const char* path,off_t length); #include unistd.h int access(const char *pathname, int mode); 作用:判斷某個文件是否有某個權(quán)限,或者判斷文件是否存在 參數(shù): pathname:判斷文件路

    2023年04月16日
    瀏覽(33)
  • C++linux高并發(fā)服務(wù)器項目實踐 day2

    C++linux高并發(fā)服務(wù)器項目實踐 day2

    庫的定義和特點詳情請看隔壁c++階段學(xué)習(xí)的day10查看 Linux: libxxx.a lib:固定前綴 xxx:庫的名字,自定義 .a:固定后綴 windows:libxxx.lib gcc獲得.o文件 將.o文件打包,使用ar工具(archive) ar rcs libxxx.a xxx.o xxx.o r- 將文件插入備存文件中 c-建立備存文件 s-索引 sudo apt install tree 安裝tree插件,用

    2023年04月20日
    瀏覽(23)
  • C++linux高并發(fā)服務(wù)器項目實踐 day5

    C++linux高并發(fā)服務(wù)器項目實踐 day5

    程序 是包含一系列信息的文件,這些信息描述了如何在運行時創(chuàng)建一個進(jìn)程: 程序是文件,只占用硬盤的大??;進(jìn)程會占用cpu和內(nèi)存資源 進(jìn)程 是正在運行的程序的實例。是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。他是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,

    2023年04月17日
    瀏覽(23)
  • C++項目:仿mudou庫one thread one loop式并發(fā)服務(wù)器實現(xiàn)

    C++項目:仿mudou庫one thread one loop式并發(fā)服務(wù)器實現(xiàn)

    目錄 1.實現(xiàn)目標(biāo) 2.HTTP服務(wù)器 3.Reactor模型 3.1分類 4.功能模塊劃分: 4.1SERVER模塊: 4.2HTTP協(xié)議模塊: 5.簡單的秒級定時任務(wù)實現(xiàn) 5.1Linux提供給我們的定時器 5.2時間輪思想: 6.正則庫的簡單使用 7.通用類型any類型的實現(xiàn) 8.日志宏的實現(xiàn) 9.緩沖區(qū)buffer類的實現(xiàn) 10.套接字Socket類實現(xiàn) 11.

    2024年02月08日
    瀏覽(22)
  • (一)專題介紹:移動端安卓手機(jī)改造成linux服務(wù)器&linux服務(wù)器中安裝軟件、部署前后端分離項目實戰(zhàn)

    總體概述: 本篇文章隸屬于“手機(jī)改造服務(wù)器 部署前后端分離項目”系列專欄,該專欄將分多個板塊,每個板塊獨立成篇 來詳細(xì)記錄:手機(jī)(安卓)改造成個人服務(wù)器(Linux)、Linux中安裝軟件、配置開發(fā)環(huán)境、部署JAVA+VUE+MySQL5.7前后端分離項目,以及內(nèi)網(wǎng)穿透實現(xiàn)外網(wǎng)訪問等全過

    2024年02月04日
    瀏覽(21)
  • 實戰(zhàn):Prometheus+Grafana監(jiān)控Linux服務(wù)器及Springboot項目

    實戰(zhàn):Prometheus+Grafana監(jiān)控Linux服務(wù)器及Springboot項目

    相信大家都知道一個項目交付生產(chǎn)并不意味著結(jié)束,更多的是對線上服務(wù)的運維監(jiān)控。運維監(jiān)控主要涉及到部署服務(wù)器的資源情況,各個子服務(wù)的資源情況以及垃圾收集和吞吐量等等,還有故障告警等等功能。當(dāng)然,作為一個搬磚人也是需要了解全鏈路的運維監(jiān)控組件Promet

    2024年02月14日
    瀏覽(21)
  • 【linux高性能服務(wù)器編程】項目實戰(zhàn)——仿QQ聊天程序源碼剖析

    【linux高性能服務(wù)器編程】項目實戰(zhàn)——仿QQ聊天程序源碼剖析

    hello !大家好呀! 歡迎大家來到我的Linux高性能服務(wù)器編程系列之項目實戰(zhàn)——仿QQ聊天程序源碼剖析,在這篇文章中, 你將會學(xué)習(xí)到如何利用Linux網(wǎng)絡(luò)編程技術(shù)來實現(xiàn)一個簡單的聊天程序,并且我會給出源碼進(jìn)行剖析,以及手繪UML圖來幫助大家來理解,希望能讓大家更能了

    2024年04月28日
    瀏覽(33)
  • linux并發(fā)服務(wù)器 —— 多進(jìn)程并發(fā)(四)

    linux并發(fā)服務(wù)器 —— 多進(jìn)程并發(fā)(四)

    程序是包含一系列信息的文件,描述了如何在運行時創(chuàng)建一個進(jìn)程; 進(jìn)程是正在運行的程序的實例,可以用一個程序來創(chuàng)建多個進(jìn)程; 用戶內(nèi)存空間包含程序代碼以及代碼所使用的變量,內(nèi)核數(shù)據(jù)結(jié)構(gòu)用于維護(hù)進(jìn)程狀態(tài)信息; 進(jìn)程控制塊(PCB):維護(hù)進(jìn)程相關(guān)的信息,tas

    2024年02月11日
    瀏覽(27)

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

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

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

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包