SOCKET與NIO

1. 套接字(SOCKET)相關(guān)概念

網(wǎng)絡(luò)套接字的基本操作:創(chuàng)建(socket)淘太、命名(bind)姻僧、偵聽(listen)、連接(accept)蒲牧、關(guān)閉(shutdown)撇贺、發(fā)送(send)、接受(recv)冰抢,以上這幾種操作均是系統(tǒng)調(diào)用松嘶。

socket讀文件過程 (1).png

服務(wù)端

服務(wù)端通過socket()函數(shù)定義一個(gè)socket文件描述符,并使用bind()&listen()函數(shù)監(jiān)聽指定端口挎扰,此時(shí)服務(wù)端狀態(tài)CLOSED->LISTEN翠订。接下來調(diào)用accept()函數(shù)監(jiān)聽已經(jīng)完成TCP三次握手客戶端隊(duì)列巢音,這里服務(wù)端的socket如果設(shè)置成阻塞(默認(rèn)阻塞),當(dāng)調(diào)用accept函數(shù)時(shí)尽超,會阻塞當(dāng)前進(jìn)程官撼,直到客戶端隊(duì)列中出現(xiàn)已經(jīng)完成三次握手的客戶端連接;同理如果服務(wù)端socket設(shè)置非阻塞似谁,不管有沒有準(zhǔn)備好傲绣,將會立馬返回結(jié)果。

調(diào)用完accept()函數(shù)將會生成一個(gè)新socket會客戶端通信(new-socket)巩踏,這個(gè)new-socket經(jīng)歷了三次握手秃诵,狀態(tài):CLOSED->SYNC_RCVD->ESTABLISHED
當(dāng)new-socket調(diào)用read()函數(shù)時(shí)塞琼,同樣有阻塞和非阻塞兩種模式菠净,阻塞時(shí)當(dāng)前進(jìn)程(線程)一直等待直到網(wǎng)卡返回?cái)?shù)據(jù),非阻塞時(shí)立馬返回結(jié)果彪杉。讀取完數(shù)據(jù)且進(jìn)行完邏輯處理后調(diào)用write()函數(shù)嗤练,將響應(yīng)返回給客戶端,雖然write也有NIO的模式在讶,我們通常認(rèn)為write時(shí)網(wǎng)卡不會阻塞,會立馬返回霜大。

通信完雙方通過四次揮手构哺,結(jié)束通信,服務(wù)端new-socket狀態(tài):ESTABLISHED-> CLOSEWAIT -> LAST ACK->CLOSED战坤。

注意:當(dāng)服務(wù)端的socket在參與三次握手后曙强,它會創(chuàng)建一個(gè)新socket參與通信,當(dāng)然雙方結(jié)束通信后只有新創(chuàng)建的socket會關(guān)閉途茫,負(fù)責(zé)監(jiān)聽的socket還是listen狀態(tài)碟嘴。

客戶端

客戶端創(chuàng)建完socket后,通過調(diào)用connect()與服務(wù)端進(jìn)行三次握手囊卜,握手完畢娜扇,客戶端的狀態(tài)CLOSED-> SYN_SEND -> ESTABLISHED
調(diào)用write()方法發(fā)送請求栅组,同樣write()幾乎不會阻塞雀瓢;然后再次調(diào)用read()方法阻塞(非阻塞)讀取服務(wù)端響應(yīng)。
最后通信完畢玉掸,客戶端主動發(fā)起結(jié)束通信刃麸,狀態(tài):ESTABLISHED -> FIN WAIT1 -> FIN WAIT2 -> TIME WAIT

相關(guān)流程

三次握手與四次揮手.jpg

2. C語言中的SOCKET與NIO

c語言最純粹,最接近底層的系統(tǒng)調(diào)用司浪,可以不留余地的欣賞完真實(shí)socket的每個(gè)細(xì)節(jié)泊业。

socket編程的函數(shù)

  1. 創(chuàng)建socket
/**
 * domain  指定發(fā)送通信的域(網(wǎng)絡(luò)層)
    AF_UNIX:本地主機(jī)通信把沼,與IPC類似; 
    AF_INET:Internet地址IPV4協(xié)議簇
    AF_IPX:  IPX/SPX 協(xié)議簇
    AF_APPLETALK: Apple Talk協(xié)議簇
    AF_NETBIOS NetBIOS 協(xié)議簇
    AF_INET6 Internet地址IPV6協(xié)議簇
    AF_IRDA Irda協(xié)議簇
    AF_BTH  藍(lán)牙協(xié)議簇
 * type 指定通信類型(傳輸層)
    SOCK_STREAM:流套接字(eg: TCP)
    SOCK_DGRAM:數(shù)據(jù)報(bào)套接字 (eg: UDP)
    SOCK_RAW:原始套接字,可以處理ICMP、IGMP等上一層(網(wǎng)絡(luò)層)報(bào)文
    SOCK_SEQPACKET:可提供基于數(shù)據(jù)報(bào)的偽流
 * protocol 協(xié)議
    IPPROTO_ICMP:ICMP協(xié)議吁伺,僅當(dāng) domain為AF_INET或AF_INET6饮睬,且type為SOCK_RAW時(shí)可選。
    IPPROTO_IGMP:IGMP協(xié)議箱蝠,僅當(dāng) domain為AF_INET或AF_INET6续捂,且type為SOCK_RAW時(shí)可選。
    BTHPROTO_RFCOMM:藍(lán)牙協(xié)議宦搬,僅當(dāng) domain為AF_BTH牙瓢,且type為SOCK_STREAM時(shí)可選。
    IPPROTO_TCP:TCP協(xié)議间校,僅當(dāng) domain為AF_INET或AF_INET6矾克,且type為SOCK_STREAM時(shí)可選。
    IPPROTO_UDP:UDP協(xié)議憔足,僅當(dāng) domain為AF_INET或AF_INET6胁附,且type為SOCK_STREAM時(shí)可選。
    IPPROTO_ICMPv6:ICMPv6協(xié)議滓彰,僅當(dāng) domain為AF_INET或AF_INET6控妻,且type為SOCK_RAW時(shí)可選。
 * return: socketfd(正常) / -1 (失斀野蟆)
 */    
int socket(int domain, int type, int protocol)
    
  1. 命名bind
/**
 * sockfd:套接字描述符(socket句柄)
 * addr: 指向通用套接字的協(xié)議地址結(jié)構(gòu)弓候,包括協(xié)議、地址和端口等信息
 * addrlen: 協(xié)議地址結(jié)構(gòu)的長度
 * return: 0 成功; -1 失敗
 */
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  1. 監(jiān)聽listen
/**
 * sockfd: socket句柄
 * backlog:sockfd接收連接的最大數(shù)目
 * return: 0 成功; -1 失敗
 */
int listen(int sockfd, int backlog);
  1. 連接accept
/**
 * sockfd: socket句柄
 * addr: addr指向通用套接字的協(xié)議地址結(jié)構(gòu)他匪,包括協(xié)議菇存、地址和端口等信息
 * addrlen: 協(xié)議地址結(jié)構(gòu)的長度,一般為sizeof(sockaddr_in)
 * return: 創(chuàng)造返回一個(gè)新的socket與客戶進(jìn)程通信邦蜜,原sockfd仍用于套接字偵聽
 */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  1. 接收recv
/**
 * sockfd:與遠(yuǎn)程通信連接的套接字描述符
 * buf:接收數(shù)據(jù)的緩沖區(qū)地址
 * len:緩沖區(qū)長度
 * flags:接收標(biāo)志
 */
int recv(int sockfd, void *buf, size_t len, int flags);
  1. 讀取read
/**
 * fd:套接字文件描述符
 * buf:要接收的字符數(shù)組
 * nbyte:最大讀取的字節(jié)
 */
int read (int __fd, void *__buf, size_t __nbyte);
  1. 寫入write
/**
 * fildes:套接字文件描述符
 * buf:要接收的字符數(shù)組
 * nbyte:寫入字節(jié)
 */
int write(int fildes, const void *buf, int nbyte);

BIO模型與例子

我們接下來寫一個(gè)簡單的服務(wù)端接收客戶端請求并相應(yīng)的程序依鸥,包含了server socket整個(gè)的生命周期:

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

void bioServer();

int main() {
    bioServer();
    return 0;
}


void bioServer() {
    int serverFd, newClientFd;
    //創(chuàng)建一個(gè)internet ipv4協(xié)議簇的TCP流協(xié)議的文件描述符serverFd
    if ((serverFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == 0) {
        printf("create socket fail");
        return;
    }

    struct sockaddr_in serverAddress;
    memset(&serverAddress, 0, sizeof(serverAddress));
    int serverAddressLen = sizeof(serverAddress);
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8088);
    serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
    //將serverFd綁定到本地8088端口上
    if (bind(serverFd, (struct sockaddr *) &serverAddress, serverAddressLen) < 0) {
        printf("bind port fail");
        return;
    }
    //開始監(jiān)聽serverFd
    if (listen(serverFd, 3) < 0) {
        printf("listen fail");
        return;
    }
    //阻塞等待, 接收一個(gè)client請求,并生成新的文件描述符clientFd
    if ((newClientFd = accept(serverFd, (struct sockaddr *) &serverAddress, (socklen_t *) &serverAddressLen)) < 0) {
        printf("accept fail");
        return;
    }

    char buffer[1024] = {0};
    //read數(shù)據(jù)(阻塞讀)
    read(newClientFd, buffer, 1024);
    printf("%s\n", buffer);
    char resp[] = "HTTP/1.1 200\nContent-Type: text/plain\n\nOK";
    //send數(shù)據(jù)
    write(newClientFd, resp, strlen(resp));
    close(newClientFd);
    close(serverFd);
    return;
}

我們可以通過curl或者telnet來測試以上程序是運(yùn)行正常的,如果有多個(gè)客戶端悼沈,那么我們不能僅僅用以上代碼來處理一個(gè)客戶端請求后就cloise贱迟,所以每當(dāng)收到accept請求后,新建一個(gè)線程/進(jìn)程去處理這個(gè)socket井辆。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <unistd.h>

void bioServerLoop();

int main() {
    bioServerLoop();
    return 0;
}

void bioServerLoop() {
    //new socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        exit(-1);
    }

    struct sockaddr_in servaddrddd, childAddr;
    int len, cfd;
    char buff[1024];
    memset(&servaddrddd, 0, sizeof(servaddrddd));
    servaddrddd.sin_family = AF_INET;
    servaddrddd.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddrddd.sin_port = htons(6666);

    //bind操作
    if (bind(fd, (const struct sockaddr *) &servaddrddd, sizeof(servaddrddd)) == -1) {
        exit(-1);
    }
    //listen操作
    if (listen(fd, 10) == -1) {
        exit(-1);
    }

    //這里循環(huán)去accept
    for (;;) {
        printf("wait for connect\n");
        len = sizeof(childAddr);
        //使用accept(阻塞)去獲取客戶端的新連接
        cfd = accept(fd, (struct sockaddr *) &childAddr, &len);
        if (cfd == -1) {
            break;
        }
        //fork函數(shù), 復(fù)制本進(jìn)程生成新的子進(jìn)程
        if (fork() == 0) {
            //close(fd), 子進(jìn)程不需要再有父進(jìn)程的fd引用
            close(fd);
            //read數(shù)據(jù)(阻塞)
            int i = recv(cfd, buff, 1024, MSG_WAITALL);
            printf("recv msg from client: %s\n", buff);
            write(cfd, buff, i);
            //
            close(cfd);
        }
        //新客戶端鏈接已經(jīng)在子進(jìn)程中處理关筒,父進(jìn)程不需要持有子進(jìn)程的cfd引用
        close(cfd);
    }
}

BIO模型:

image.png

BIO程序到此結(jié)束,BIO的阻塞進(jìn)程/線程的特點(diǎn)已經(jīng)在程序標(biāo)出:在acceptread的時(shí)候會阻塞杯缺。每當(dāng)有一個(gè)客戶端來連接蒸播,就要有一個(gè)線程/進(jìn)程去阻塞,線程/進(jìn)程對于操作系統(tǒng)來說是十分有限的,所以當(dāng)客戶端并發(fā)上到十萬袍榆、百萬級別的時(shí)候會迅速消耗完系統(tǒng)的資源胀屿。

NIO模型與例子

接下來我們討論NIO,也就是非阻塞包雀,引入非阻塞的目的就是解決阻塞操作過程中宿崭,避免創(chuàng)建大量線程去等待各自的IO操作;因?yàn)橐肓朔亲枞判矗耆梢允褂靡粋€(gè)線程去處理多個(gè)阻塞IO葡兑,這樣線程的利用率就大大提升。

對于client socketconnect赞草、read讹堤、write)和server socketaccept、read厨疙、write)來說洲守,只要將其文件描述符設(shè)置成no_blocked,那么它的IO操作函數(shù)就可以不必等待沾凄,直接返回結(jié)果(可能有數(shù)據(jù)梗醇,也可能沒有數(shù)據(jù))。

客戶端一般不涉及到大并發(fā)操作(其實(shí)是和其他io函數(shù)一樣的)撒蟀,所以我們只討論server socketNIO操作:accept叙谨、readwrite保屯,常用場景中write操作和網(wǎng)卡相關(guān)唉俗,一般不會阻塞,為了簡化邏輯配椭,我們先拿acceptread兩個(gè)函數(shù)舉例雹姊。

對于read操作來說股缸,其實(shí)把網(wǎng)卡的數(shù)據(jù)拷貝到進(jìn)程內(nèi)存上速度是非常之快的,真正時(shí)間瓶頸是花在等待網(wǎng)卡把數(shù)據(jù)準(zhǔn)備好的過程上吱雏,也就是上述說的IO等待的過程(accept操作是等待TCP連接建立敦姻,也是等待IO的過程)。

Read阻塞讀
Accept阻塞讀

c語言socket編程中歧杏,我們可以使用fcntl函數(shù)將某個(gè)文件描述符設(shè)置為非阻塞:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<unistd.h>
#include<fcntl.h>
#include <errno.h>


void nioServer();

void nioServer() {
    //new socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd == -1) {
        exit(-1);
    }

    struct sockaddr_in servaddrddd, childAddr;
    int len, cfd;
    char buff[1024];
    memset(&servaddrddd, 0, sizeof(servaddrddd));
    servaddrddd.sin_family = AF_INET;
    servaddrddd.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddrddd.sin_port = htons(6666);

    //bind操作
    if (bind(fd, (const struct sockaddr *) &servaddrddd, sizeof(servaddrddd)) == -1) {
        exit(-1);
    }
    //listen操作
    if (listen(fd, 10) == -1) {
        exit(-1);
    }
    //設(shè)置nio
    if(fcntl(fd,F_SETFL,fcntl(fd, F_GETFL,0) | O_NONBLOCK) == -1) {
        exit(-1);
    }

    //這里循環(huán)去accept
    for (;;) {
        printf("wait for connect\n");
        len = sizeof(childAddr);
        //使用accept(非阻塞)去獲取客戶端的新連接, 生成的新cfd也是非阻塞
        cfd = accept(fd, (struct sockaddr *) &childAddr, &len);
        if (cfd == -1) {
            //-1錯(cuò)誤可能是accept函數(shù)本身出錯(cuò),也可能是nio沒有獲取到客戶端連接
            //errno是一個(gè)包含在<errno.h>中預(yù)定義的變量镰惦,可以判斷最近一個(gè)函數(shù)調(diào)用是否產(chǎn)生了錯(cuò)誤,所以這里用errno判斷是否正常
            if (errno == EWOULDBLOCK) {
                printf("accept no connect, wait for 2s\n");
                sleep(2);
                continue;
            }
            break;
        }
        //fork函數(shù), 復(fù)制本進(jìn)程生成新的子進(jìn)程
        if (fork() == 0) {
            printf("inter fork\n");
            //子進(jìn)程不需要再有父進(jìn)程的fd引用
            close(fd);
            while(1) {
                //read數(shù)據(jù)(非阻塞)
                int i = recv(cfd, buff, 1024, MSG_WAITALL);
                if (i == -1 && errno == EWOULDBLOCK) {
                    printf("read cfd:%d no data, wait for 2s\n", i);
                    sleep(2);
                    continue;
                }
                else if (i == -1) {
                    close(cfd);
                    return;
                }
                printf("recv msg from client: %s\n", buff);
                //write數(shù)據(jù)(非阻塞)
                write(cfd, buff, i);
                close(cfd);
                break;
            }
        }
        //新客戶端鏈接已經(jīng)在子進(jìn)程中處理,父進(jìn)程不需要持有子進(jìn)程的cfd引用
        close(cfd);
    }
}

int main() {
    nioServer();
    return 0;
}

NIO模型:

image.png

通過以上代碼犬绒,我們知道了如果設(shè)置了一個(gè)文件描述符為非阻塞旺入,那么需要手動while()循環(huán)(輪詢)去判斷各個(gè)IO操作是否準(zhǔn)備好,相比NIO來說,這么寫增加了代碼的復(fù)雜度茵瘾、空跑了很多CPU礼华、而且有線程的sleep()操作,還影響實(shí)時(shí)效率拗秘,但是不要忘記我們的初衷圣絮,我們想要一個(gè)線程去處理多個(gè)IO事件,只有設(shè)置了非阻塞雕旨,才會有可能實(shí)現(xiàn)線程復(fù)用的優(yōu)勢扮匠。

單線程處理輪詢多個(gè)非阻塞IO的代碼就不再寫了,有了非阻塞凡涩,相信大家都能寫出來棒搜。

實(shí)際過程中,NIO線程輪詢的模型幾乎很少用到突照,因?yàn)闉榱四茏尵€程復(fù)用帮非,它犧牲的太多了,更不能忍受的就是每個(gè)應(yīng)用程序都要去設(shè)計(jì)這一套忙輪詢機(jī)制讹蘑,更多細(xì)節(jié)繁瑣難以處理末盔,比如:輪詢多久合適?IO多了選擇什么數(shù)據(jù)類型去存儲座慰?陨舱。相比之下,身為程序員的我們還是希望能像BIO那樣簡單阻塞處理版仔,如果有事件來了直接跳過阻塞繼續(xù)執(zhí)行就好游盲。這么一勞永逸的事情,操作系統(tǒng)還是幫我們實(shí)現(xiàn)了蛮粮,那就是多路復(fù)用益缎。

多路復(fù)用IO模型與例子

接下來我們講select函數(shù)的多路復(fù)用,它的內(nèi)部實(shí)現(xiàn)不僅僅是忙輪詢設(shè)計(jì)這么簡單然想,如果僅僅是忙輪詢莺奔,那么還是會空跑CPU,它還包括wait()等待和notify()通知機(jī)制变泄,可以有效的讓其他程序利用select()等待的這段時(shí)間令哟。但是對于使用方來看,我們的線程只要等待就可以妨蛹,首先我們先看下位于unistd.h下的select函數(shù):

/**
 * nfds:sets的文件描述符最大值
 * readfds:fd_set類型屏富,包含了需要檢查是否可讀的描述符,輸出時(shí)表示哪些描述符可讀蛙卤。
 * writefds:fd_set類型狠半,包含了需要檢查是否可寫的描述符,輸出時(shí)表示哪些描述符可寫。
 * errorfds:fd_set類型典予,包含了需要檢查是否出錯(cuò)的描述符甜滨,輸出時(shí)標(biāo)識哪些描述符錯(cuò)誤。
 * timeout:最大等待時(shí)間
 * return int:返回可以操作的描述符個(gè)數(shù)瘤袖,超時(shí)返回0衣摩,出錯(cuò)返回-1
 */
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);

我們可以發(fā)現(xiàn),select函數(shù)中最為關(guān)鍵的就是它的文件描述符捂敌,它是set集合艾扮,存放哪些需要檢查的文件描述符,為了維護(hù)fd_set占婉,也有四個(gè)宏來操作它:

  • FD_SET():將指定的文件描述符存放到set中泡嘴。
  • FD_CLR():將指定的文件描述符從set中移除。
  • FD_ZERO():初始化set為空逆济。
  • FD_ISSET():判斷指定文件描述符是否存在set中酌予。

以下是用select來實(shí)現(xiàn)線程多路復(fù)用的邏輯:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>

void selectIO();

int main() {
    selectIO();
    return 0;
}

void selectIO() {
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    int result;
    fd_set readfds, testfds;
    //new socket
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(8707);
    server_len = sizeof(server_address);
    bind(server_sockfd, (struct sockaddr *) &server_address, server_len);
    listen(server_sockfd, 5);
    //初始化fds, 它是多個(gè)文件描述符列表, 也是維持select可以同時(shí)處理多個(gè)IO的基礎(chǔ)
    FD_ZERO(&readfds);
    //將服務(wù)端文件描述符加入到set中
    FD_SET(server_sockfd, &readfds);

    while (1) {
        char message[1024];
        char respMessage[] = "HTTP/1.1 200\nContent-Type: text/plain\n\nOK";
        int fd;
        int nread;
        testfds = readfds;
        printf("server waiting\n");

        //select阻塞直到文件描述符set中至少有一個(gè)IO可用的為止, 最后一個(gè)參數(shù)timeout可以設(shè)置阻塞時(shí)長
        //我們只關(guān)心可讀set,可寫set一般都不用關(guān)心
        result = select(FD_SETSIZE, &testfds, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 0);
        if (result < 1) {
            perror("server happen error\n");
            exit(1);
        }

        //掃描所有的文件描述符,找到可用的文件描述符
        for (fd = 0; fd < FD_SETSIZE; fd++) {
            //找到相關(guān)文件描述符
            if (FD_ISSET(fd, &testfds)) {
                //如果是serverFd那么肯定只有一個(gè)accept()操作
                if (fd == server_sockfd) {
                    client_len = sizeof(client_address);
                    //accept()獲取一個(gè)可用的連接(一定是已經(jīng)準(zhǔn)備好的, 不會阻塞),生成一個(gè)新的客戶端文件描述符放到set中
                    client_sockfd = accept(server_sockfd, (struct sockaddr *) &client_address, &client_len);
                    FD_SET(client_sockfd, &readfds);
                    printf("adding client on fd %d\n", client_sockfd);
                }
                    //客戶端連接,fork出子進(jìn)程來處理業(yè)務(wù)讀寫
                else {
                    if (fork() == 0) {
                        //取得數(shù)據(jù)量交給nread
                        ioctl(fd, FIONREAD, &nread);
                        if (nread == 0) {
                            //客戶數(shù)據(jù)請求完畢,關(guān)閉套接字,從集合中清除相應(yīng)描述符
                            close(fd);
                            printf("removing by client on fd %d/n", fd);
                        } else {
                            //一定可讀,不會阻塞
                            read(fd, &message, 1024);
                            printf("recv client on fd %d, message:%s\n", fd, message);
                            write(fd, respMessage, strlen(respMessage));
                            close(fd);
                        }
                    }
                    //直接clean, 如果業(yè)務(wù)沒有處理完畢奖慌,可以在子進(jìn)程中重新添加該文件描述符到set中
                    FD_CLR(fd, &readfds);
                    close(fd);
                }
            }
        }
    }
}

IO復(fù)用模型:


image.png

上述程序是一個(gè)簡單的單線程IO多路復(fù)用 + 多線程業(yè)務(wù)處理的IO模型抛虫。由main線程去執(zhí)行select()函數(shù)并阻塞,建立連接后的子文件描述符的讀寫事件也仍注冊到main線程的select()中简僧。當(dāng)有讀寫事件發(fā)生時(shí)建椰,由線程池負(fù)責(zé)去處理。

流程圖如下:

image.png

另外還有一種比較常見的IO復(fù)用模型是:多線程IO多路復(fù)用和業(yè)務(wù)處理岛马。有效的避免了單個(gè)select()最大文件描述符限制不足的場景:

image.png

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棉姐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子啦逆,更是在濱河造成了極大的恐慌伞矩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夏志,死亡現(xiàn)場離奇詭異扭吁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盲镶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝌诡,“玉大人溉贿,你說我怎么就攤上這事∑趾担” “怎么了宇色?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我宣蠕,道長例隆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任抢蚀,我火速辦了婚禮镀层,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘皿曲。我一直安慰自己唱逢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布屋休。 她就那樣靜靜地躺著坞古,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劫樟。 梳的紋絲不亂的頭發(fā)上痪枫,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音叠艳,去河邊找鬼奶陈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛虑绵,可吹牛的內(nèi)容都是我干的尿瞭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼翅睛,長吁一口氣:“原來是場噩夢啊……” “哼声搁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捕发,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤疏旨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后扎酷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檐涝,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年法挨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谁榜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凡纳,死狀恐怖窃植,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情荐糜,我是刑警寧澤巷怜,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布葛超,位于F島的核電站,受9級特大地震影響延塑,放射性物質(zhì)發(fā)生泄漏绣张。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一关带、第九天 我趴在偏房一處隱蔽的房頂上張望侥涵。 院中可真熱鬧,春花似錦豫缨、人聲如沸独令。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燃箭。三九已至,卻和暖如春舍败,著一層夾襖步出監(jiān)牢的瞬間招狸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工邻薯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留裙戏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓厕诡,卻偏偏與公主長得像累榜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子灵嫌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353