網(wǎng)絡(luò)編程IO多路復(fù)用

場(chǎng)景:設(shè)計(jì)一個(gè)高性能的網(wǎng)絡(luò)服務(wù)器,能夠供多個(gè)客戶端同時(shí)進(jìn)行連接,并且能夠處理這些客戶端傳上來(lái)的請(qǐng)求

應(yīng)對(duì)并發(fā),可以設(shè)計(jì)一個(gè)多線程的程序,每個(gè)傳上來(lái)的請(qǐng)求都開一個(gè)線程。存在一個(gè)弊端,需要CPU上下文的切換,代價(jià)高

如何使用單線程解決問(wèn)題?
每一個(gè)網(wǎng)絡(luò)連接在內(nèi)核中以文件描述符的形式存在
如果服務(wù)器正在處理A的請(qǐng)求,此時(shí)B發(fā)送一個(gè)請(qǐng)求,B的請(qǐng)求會(huì)被丟棄嗎?不會(huì),因?yàn)樘幚鞩O的設(shè)備不是CPU,而是專門的DMA
最簡(jiǎn)單粗暴的方法,缺點(diǎn)是仍然由CPU判斷是否有數(shù)據(jù)

while(1){
    for(fdx in (fda ~ fde)){
        if(fdx有數(shù)據(jù)){
            讀fdx并處理;
        }
    }
}

select

sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(2000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(socket, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
for(i = 0; i < 5; i++){
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(client);
    fds[i] = accept(socket, (struct sockaddr*)&client, &addrlen);
    if(fds[i] > max)
        max = fds[i];
}
/*----------------------*/
while(1){
    FD_ZERO(&rset);
    for(i = 0; i < 5; i++)
        FD_SET(fds[i], &rset);

    puts("round again");
    select(max + 1, &rset, NULL, NULL, NULL);
 
    for(i = 0; i < 5; i++){
        if(FD_ISSET(fds[i], &rset)){
            memset(buffer, 0, MAXBUF);
            read(fds[i], buffer, MAXBUF);
            puts(buffer);
        }
    }
}

上一個(gè)部分主要是為了準(zhǔn)備文件描述符的數(shù)組fds。首先創(chuàng)建了一個(gè)socket的服務(wù)端,然后創(chuàng)建了五個(gè)文件描述符
文件描述符是一些隨機(jī)的不重復(fù)的數(shù),將其中的最大值存到max中

select方法的參數(shù):讀文件描述符集合、rset、寫文件描述符集合、異常描述符集合、超時(shí)時(shí)間
這里關(guān)心的是讀文件描述符,因?yàn)橐x取網(wǎng)絡(luò)連接中的數(shù)據(jù),將寫文件描述符集合、異常描述符集合設(shè)置為NULL,超時(shí)時(shí)間NULL表示使用默認(rèn)時(shí)間
rset是一個(gè)bitmap,用來(lái)表示哪一個(gè)文件描述符是被啟用/監(jiān)聽的,bitmap有1024位,哪一位為1表示哪一個(gè)文件描述符被監(jiān)聽

select將rset從用戶態(tài)拷貝到內(nèi)核態(tài),由內(nèi)核態(tài)直接判斷文件描述符是否有數(shù)據(jù)的操作;暴力方法判斷時(shí)需要反復(fù)從用戶態(tài)切換到內(nèi)核態(tài),效率更低

select函數(shù)的執(zhí)行流程

  1. select是一個(gè)阻塞函數(shù),當(dāng)沒(méi)有數(shù)據(jù)時(shí),會(huì)一直阻塞在select函數(shù)那一行
  2. 當(dāng)有數(shù)據(jù)時(shí)會(huì)將rset中對(duì)應(yīng)的那一位置位
  3. select函數(shù)返回,不再阻塞
  4. 遍歷文件描述符數(shù)組,判斷哪個(gè)fd被置位了
  5. 讀取數(shù)據(jù),處理

select函數(shù)的缺點(diǎn)

  1. bitmap默認(rèn)大小為1024,雖然可以調(diào)整但是有限度
  2. rset每次循環(huán)都需要重置,不可重復(fù)使用
  3. 盡管將rset從用戶態(tài)拷貝到內(nèi)核態(tài)由內(nèi)核判斷是否有數(shù)據(jù),但還是有拷貝的開銷
  4. 當(dāng)有數(shù)據(jù)時(shí)select就會(huì)返回,但是select函數(shù)并不知道哪個(gè)文件描述符有數(shù)據(jù)了,后面還需要再次對(duì)文件描述符遍歷

poll

struct pollfd{
    int fd;
    short events;
    short revents;
}
/*----------*/
for(i = 0; i < 5; i++){
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(client);
    pollfds[i].fd = accept(socket, (struct sockaddr*)&client, &addrlen);
    pollfds[I].events = POLLIN;
}
sleep(1);
/*----------*/
while(1){
    puts("round again");
    poll(pollfds, 5, 50000);
    
    for(i = 0; i < 5; i++){
        if(pollfds[i].revents & POLLIN){
            pollfds[i].revents = 0;
            memset(buffer, 0, MAXBUF);
            read(pollfds[i].fd, buffer, MAXBUF);
            puts(buffer);
        }
    }
}

poll函數(shù)的參數(shù)

  1. 自定義的結(jié)構(gòu)體數(shù)組
  2. 數(shù)組的長(zhǎng)度
  3. 超時(shí)時(shí)間

自定義結(jié)構(gòu)體

  1. fd,文件描述符
  2. events,在意的事件是什么,讀是POLLIN,寫是POLLOUT,讀和寫都在意用或
  3. revents,對(duì)events的回饋,開始時(shí)為0,當(dāng)有數(shù)據(jù)可讀時(shí)就置為POLLIN,類似于上面的rset

poll函數(shù)的執(zhí)行流程

  1. 將五個(gè)fd從用戶態(tài)拷貝到內(nèi)核態(tài)
  2. poll為阻塞方法,執(zhí)行poll方法,如果有數(shù)據(jù)會(huì)將fd對(duì)應(yīng)的revents置為POLLIN
  3. poll方法返回
  4. 循環(huán)遍歷,查找哪個(gè)fd被置位為POLLIN了
  5. 將revents重置為0,便于復(fù)用
  6. 對(duì)置位的fd進(jìn)行讀取和處理

解決了select的哪些缺陷

  1. 使用數(shù)組,大小不止1024,解決了bitmap的大小限制
  2. 每次置位revents字段,revents可以恢復(fù),解決了rset不可重用的情況
  3. 3和4的缺陷未被解決,因?yàn)閮烧咴硐嗤?/li>

epoll

struct epoll_events[5];
int epfd = epoll_create(10);
...
...
for(i = 0; i < 5; i++){
    static struct epoll_event ev;
    memset(&client, 0, sizeof(client));
    addrlen = sizeof(client);
    ev.data.fd = accept(socket, (struct sockaddr*)&client, &addrlen);
    ev.events = POLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}
/*---------*/
while(1){
    puts("round again");
    nfds = epoll_wait(epfd, events, 5, 10000);

    for(i = 0; i < nfds; i++){
        memset(buffer, 0, MAXBUF);
        read(events[I].data.fd, buffer, MAXBUF);
        puts(buffer);
    }
}

epoll準(zhǔn)備過(guò)程

  1. 利用epoll_create創(chuàng)建epfd,epfd相當(dāng)于一個(gè)白板,用來(lái)作為epoll_wait的第一個(gè)參數(shù),epoll_create的參數(shù)沒(méi)有多大的實(shí)際意義,可以隨意取值,用戶態(tài)和內(nèi)核態(tài)共享epfd
  2. 利用epoll_ctl對(duì)epfd進(jìn)行配置,添加了五個(gè)fd-events的數(shù)據(jù),沒(méi)有revents

epoll的執(zhí)行流程

  1. 當(dāng)有數(shù)據(jù)的時(shí)候,會(huì)把相應(yīng)的文件描述符置位,但是epoll沒(méi)有revent標(biāo)志位,并非真正的置位,而是將有數(shù)據(jù)的文件描述符放到隊(duì)首
  2. epoll返回有數(shù)據(jù)的文件描述符的個(gè)數(shù)
  3. 根據(jù)返回的個(gè)數(shù),讀取前n個(gè)文件描述符即可
  4. 讀取并處理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容