溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Socket-IO復(fù)用技術(shù)

發(fā)布時(shí)間:2020-07-19 22:08:06 來源:網(wǎng)絡(luò) 閱讀:2801 作者:SherryX 欄目:系統(tǒng)運(yùn)維

(上一篇地址)前面使用socket完成一個(gè)服務(wù)器對(duì)應(yīng)多個(gè)客戶端的小實(shí)驗(yàn)的時(shí)候,針對(duì)TCP連接,我們必須得創(chuàng)建新的進(jìn)程來與新的客戶端通信。那么,就意味著,1000個(gè)客戶端就有有1000個(gè)server進(jìn)程,這顯然是不實(shí)際的。如果,我們可以提前把要監(jiān)聽的文件描述符放到一個(gè)集合里,一旦其中一個(gè)發(fā)生事件(不管是連上,還是通信),就去處理。這樣,會(huì)方便很多。所以,今天學(xué)習(xí)一下IO復(fù)用。

1 五個(gè)I/O模型

  • 阻塞I/O
  • 非阻塞I/O
  • I/O復(fù)用(select和poll)
  • 信號(hào)驅(qū)動(dòng)I/O
  • 異步I/O

    阻塞IO

    最流行的I/O模型是阻塞I/O模型,缺省時(shí),所有的套接口都是阻塞的。
    Socket-IO復(fù)用技術(shù)

    非阻塞IO

    Socket-IO復(fù)用技術(shù)

    IO復(fù)用

    Socket-IO復(fù)用技術(shù)

    信號(hào)驅(qū)動(dòng)IO

    Socket-IO復(fù)用技術(shù)

    異步IO

    Socket-IO復(fù)用技術(shù)

    2 I/O復(fù)用

    如果一個(gè)或多個(gè)I/O條件滿足(例如:輸入已準(zhǔn)備好被讀,或者描述字可以承接更多輸出的時(shí)候)我們就能夠被通知到,這樣的能力被稱為I/O復(fù)用,是由函數(shù)selectpoll支持的。

    I/O復(fù)用網(wǎng)絡(luò)應(yīng)用場(chǎng)合

  • 當(dāng)客戶處理多個(gè)描述字
  • 一個(gè)客戶同時(shí)處理多個(gè)套接口
  • 如果一個(gè)tcp服務(wù)器既要處理監(jiān)聽套接口,又要處理連接套接口
  • 如果一個(gè)服務(wù)器既要處理TCP,又要處理UDP

select

         /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);//從集合中刪除一個(gè)描述字
       int  FD_ISSET(int fd, fd_set *set);//描述字是否在該集合中
       void FD_SET(int fd, fd_set *set);//添加一個(gè)描述字到集合中
       void FD_ZERO(fd_set *set);//清空描述字集合
  • 作用:函數(shù)允許進(jìn)程指示內(nèi)核等待多個(gè)事件中的任一個(gè)發(fā)生,并僅在一個(gè)或多個(gè)事件發(fā)生或經(jīng)過某指定的時(shí)間后才喚醒進(jìn)程
    提供了即時(shí)響應(yīng)多個(gè)套接的讀寫事件
  • 參數(shù):
  • nfds:集合中最大的文件描述符 + 1 (指定被測(cè)試的描述字個(gè)數(shù),它的值是要被測(cè)試的最大描述字加1,描述字0、1、2…….一直到nfds均被測(cè)試)
  • readfds:要檢查讀事件的容器
  • writefds:要檢查寫事件的容器
  • timeout:超時(shí)時(shí)間
  • 返回值:返回觸發(fā)套接字的個(gè)數(shù)
    中間的三個(gè)參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測(cè)試讀、寫和異常條件所需的描述字
    如果我們對(duì)某個(gè)條件不感興趣,這三個(gè)參數(shù)中相應(yīng)的參數(shù)就可以設(shè)為空指針

timeout參數(shù)

時(shí)間的結(jié)構(gòu)體如下:

            struct timeval(
                long tv_sec;  //秒
                long tv_usec;//微秒
            );

timeout參數(shù)有三種可能

  • 永遠(yuǎn)等待下去:僅在有一個(gè)描述字準(zhǔn)備好I/O時(shí)才返回,為此,我們將timeout設(shè)置為空指針
  • 等待固定時(shí)間:在有一個(gè)描述字準(zhǔn)備好I/O是返回,但不超過由timeout參數(shù)所指timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)
  • 根本不等待:檢查描述字后立即返回,這稱為輪詢。定時(shí)器的值必須為0

    fd_set參數(shù)

    select使用描述字集,它一般是一個(gè)整數(shù)數(shù)組,每個(gè)數(shù)中的每一位對(duì)應(yīng)一個(gè)描述字。

    使用流程

    使用select完成之前socket的測(cè)試,流程如下:
    Socket-IO復(fù)用技術(shù)
    客戶端代碼不變。

    #include < sys/types.h>     
    #include < sys/socket.h>
    #include < netinet/in.h>    //sockaddr_in
    #include < stdio.h>
    #include < string.h>
    
    //TCP
    int main()
    {
        int fd;
        int ret;
        int addrLen;
        char acbuf[20] = "";
        struct sockaddr_in serAddr = {0};
        struct sockaddr_in myAddr = {0};
    
        //1.socket();
        fd = socket(PF_INET,SOCK_STREAM,0);
        if(fd == -1)
        {
            perror("socket");
            return -1;
        }
    
        //2.連接connect() 服務(wù)器的地址
        serAddr.sin_family = AF_INET;
        serAddr.sin_port = htons(1234);
        serAddr.sin_addr.s_addr = inet_addr("192.168.159.5");
        ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));
        if(ret == -1)
        {
            perror("connect");
            return -1;
        }
    
        //獲取自己的地址
        addrLen = sizeof(struct sockaddr_in);
        ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);
        if(ret == -1)
        {
            perror("getsockname");
            return -1;
        }
        printf("client---ip: %s , port: %d\n",\
                    inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));
        //3.通信
        while(1)
        {
            printf("send: ");
            fflush(stdout);
            scanf("%s",acbuf);
            if(strcmp(acbuf,"exit") == 0)
            {
                break;
            }
            write(fd,acbuf,strlen(acbuf));
        }
    
        //4.close()
        close(fd);
        return 0;
    }

    服務(wù)器端:
    select.c

        #include < sys/types.h>     
        #include < sys/socket.h>
        #include < netinet/in.h>    //sockaddr_in
        #include < stdio.h>
        #include < string.h>
        #include < signal.h>
        #include < sys/select.h>
        #include < unistd.h>
        #include < sys/time.h>
        //TCP
        int main()
        {
            int fd;
            int clientfd;
            int ret;
            pid_t pid;
    
            int i;
            int maxfd;          //當(dāng)前最大套接字
            int nEvent;
            fd_set set = {0};   //監(jiān)聽集合
            fd_set oldset = {0};    //存放所有要監(jiān)聽的文件描述符
            struct timeval time = {0};
    
            int reuse = 0;
            char acbuf[20] = "";
            char client_addr[100] = "";
            struct sockaddr_in addr = {0};  //自己的地址
            struct sockaddr_in clientAddr = {0};    //連上的客戶端的地址
            int addrLen = sizeof(struct sockaddr_in);
    
            signal(SIGCHLD,SIG_IGN);
    
            //1.socket()
            fd = socket(PF_INET,SOCK_STREAM,0);
            if(fd == -1)
            {
                perror("socket");
                return -1;
            }
    
            //會(huì)出現(xiàn)沒有活動(dòng)的套接字仍然存在,會(huì)禁止綁定端口,出現(xiàn)錯(cuò)誤:address already in use .
            //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態(tài)會(huì)保留2-4分鐘
            if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
                {
                perror("setsockopet error\n");
                return -1;
                }
    
            //2.bind()
            addr.sin_family = AF_INET;
            addr.sin_port = htons(1234);
            addr.sin_addr.s_addr = inet_addr("192.168.159.5");
            ret = bind(fd,(struct sockaddr *)&addr,addrLen);
            if(ret == -1)
            {
                perror("bind");
                return -1;
            }
    
            //3.listen()
            ret = listen(fd,10);
            if(ret == -1)
            {
                perror("listen");
                return -1;
            }
    
            //創(chuàng)建監(jiān)聽集合
            FD_ZERO(&oldset);
            FD_SET(fd,&oldset);
            //maxfdp1:當(dāng)前等待的最大套接字。比如:當(dāng)前fd的值=3,則最大的套接字就是3
            //所以每當(dāng)有客戶端連接進(jìn)來,就比較一下文件描述符
            maxfd = fd;
            //select
            //select之前,set放的是所有要監(jiān)聽的文件描述符;{3,4,5}
            //select之后,set只剩下有發(fā)生事件的文件描述符。{3}
    
            while(1)
            {
                set = oldset;
                printf("before accept.\n");
                time.tv_sec = 5;
                nEvent = select(maxfd + 1,&set,NULL,NULL,&time);    //返回文件描述符的個(gè)數(shù)(即事件的個(gè)數(shù))
                printf("after accept.%d\n",nEvent);
                if(nEvent == -1)
                {
                    perror("select");
                    return -1;
                }
                else if(nEvent == 0)    //超時(shí)
                {
                    printf("time out");
                    return 1;
                }
                else
                {           
                    //有事件發(fā)生
                    //判斷是否是客戶端產(chǎn)生的事件
                    for(i = 0 ; i <= maxfd ; i++)
                    {
                        if(FD_ISSET(i,&set))
                        {
                            if(i == fd)
                            {
                                clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
                                FD_SET(clientfd,&oldset);
                                printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
                                if(clientfd > maxfd)
                                {
                                    maxfd = clientfd;
                                }
                            }
                            else
                            {
                                memset(acbuf,0,20);
                                if(read(i,acbuf,20) == 0) //客戶端退出
                                {
                                    close(i);
                                    //還要從集合里刪除
                                    FD_CLR(i,&oldset);
                                }
                                else
                                    printf("receive: %s\n",acbuf);
                            }
                        }
                    }
                }
            }
            return 0;
        }

    epoll

    epoll用到的函數(shù)有以下幾個(gè):

                #include <sys/epoll.h>
       int epoll_create(int size);//創(chuàng)建epoll
             int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作函數(shù)
             int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);   

事件集合的結(jié)構(gòu)體:
Socket-IO復(fù)用技術(shù)
(這里 ,還要注意,epoll的超時(shí)參數(shù)是int,單位是us)

使用流程

Socket-IO復(fù)用技術(shù)

        #include <sys/types.h>     
        #include <sys/socket.h>
        #include <netinet/in.h> //sockaddr_in
        #include <stdio.h>
        #include <string.h>
        #include <signal.h>
        #include <sys/epoll.h>

        //epoll
        //epoll_wait() epoll_creat() epoll_ctl()

        //TCP
        int main()
        {
            int fd;
            int clientfd;
            int ret;
            pid_t pid;

            int i;
            int epfd;
            int nEvent;
            struct epoll_event event = {0};
            struct epoll_event rtl_events[20] = {0};    //事件結(jié)果集

            int reuse = 0;
            char acbuf[20] = "";
            char client_addr[100] = "";
            struct sockaddr_in addr = {0};  //自己的地址
            struct sockaddr_in clientAddr = {0};    //連上的客戶端的地址
            int addrLen = sizeof(struct sockaddr_in);

            signal(SIGCHLD,SIG_IGN);

            //1.socket()
            fd = socket(PF_INET,SOCK_STREAM,0);
            if(fd == -1)
            {
                perror("socket");
                return -1;
            }

            //會(huì)出現(xiàn)沒有活動(dòng)的套接字仍然存在,會(huì)禁止綁定端口,出現(xiàn)錯(cuò)誤:address already in use .
            //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,該狀態(tài)會(huì)保留2-4分鐘
            if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
                {
                perror("setsockopet error\n");
                return -1;
                }

            //2.bind()
            addr.sin_family = AF_INET;
            addr.sin_port = htons(1234);
            addr.sin_addr.s_addr = inet_addr("192.168.159.5");
            ret = bind(fd,(struct sockaddr *)&addr,addrLen);
            if(ret == -1)
            {
                perror("bind");
                return -1;
            }

            //3.listen()
            ret = listen(fd,10);
            if(ret == -1)
            {
                perror("listen");
                return -1;
            }

            epfd = epoll_create(1000);  //同時(shí)監(jiān)聽的文件描述符
            event.data.fd = fd;
            event.events = EPOLLIN;  //讀
            epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &event);
            while(1)
            {
        //      nEvent = epoll_wait(epfd,rtl_events,20,-1);  //-1:阻塞    0:非阻塞
                nEvent = epoll_wait(epfd,rtl_events,20,5000);
                if(nEvent == -1)
                {
                    perror("epoll_wait");
                    return -1;
                }
                else if(nEvent == 0)
                {
                    printf("time out.");
                }
                else
                {
                    //有事件發(fā)生,立即處理
                    for(i = 0; i < nEvent;i++)
                    {
                        //如果是 服務(wù)器fd
                        if( rtl_events[i].data.fd == fd )
                        {
                            clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);
                            //添加
                            event.data.fd = clientfd;
                            event.events = EPOLLIN;  //讀
                            epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event);
                            printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
                        }
                        else
                        {
                            //否則 客戶端fd 
                            memset(acbuf,0,20);
                            ret = read(rtl_events[i].data.fd,acbuf,20);
                            printf("%d\n",ret);
                            if( ret == 0) //客戶端退出
                            {
                                close(rtl_events[i].data.fd);
                                //從集合里刪除
                                epoll_ctl(epfd,EPOLL_CTL_DEL,rtl_events[i].data.fd,NULL);
                            }
                            else
                                printf("receive: %s\n",acbuf);
                        }

                    }
                }
            }

            return 0;
        }

運(yùn)行結(jié)果如前,正常收發(fā)。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI