IO multiplexing 與 非阻塞網路程式設計

KingsLanding發表於2014-02-21

  使用I/O multipexing 的網路程式設計中,一般需要採用非阻塞網路程式設計的風格,防止服務端在處理高連線量大時候阻塞在某個檔案描述符上面,比如某個socket 有大量的資料需要寫,但是核心傳送緩衝區已經填滿,無法在一次write中將需要傳送到資料傳送出去,程式就會阻塞在該處,導致select/poll/epoll_wait() 此時不能處理其它到來的請求,同樣read或者accept也可能出現阻塞的情況,比如當客戶端發起connect,之後立刻關閉該連結,在服務端尚未呼叫accept之前就關閉了該連線,當後來服務端accept得以呼叫此時完成佇列中又沒有完成的三次握手的連線,accept就會導致程式睡眠(詳細情況可以參見UNPv1非阻塞accept的描述)。因此I/O multiplexing 一般採用非阻塞網路程式設計的風格。

  對於read/wirte 操作來說,如果採用了非阻塞程式設計則需要為每個connection配備應用層緩衝區,read端主要防止一次來到資料太多,write主要防止出現阻塞,可以把沒有傳送完成的資料寫入緩衝區,等到socket 可寫之後繼續傳送。如果在新一次write請求到來的時候,應用層寫緩衝區中還有之前未傳送完的資料,則應該先將上次未寫入核心的資料寫入核心緩衝區,保證傳送到順序性。此處給一個簡單的例子。

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <vector>
#include <string.h>
#include <stdlib.h>
#include <map>
#include <fcntl.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <sys/select.h>

#define SEVER_PORT 1314
#define MAX_LINE_LEN 1024

using namespace std;

int Accept(int fd, struct sockaddr_in *addr)
{
    socklen_t addr_len = static_cast<socklen_t>( sizeof *addr);
    int connfd,flags;

    connfd = accept(fd,reinterpret_cast<struct sockaddr *>(addr),&addr_len);

    flags = fcntl(connfd,F_GETFL,0);
    fcntl(connfd,F_SETFL,flags | O_NONBLOCK);

    if(connfd < 0)
    {
        int ErrorCode = errno;
        switch(ErrorCode)
        {
            case 0:
            case EWOULDBLOCK:
            case ECONNABORTED:
            case EPROTO:
            case EINTR:
            case EMFILE:
                    errno = ErrorCode;
                    printf("Accept Error: %s\n",strerror(ErrorCode));
                break;
            default:
                break;
        }
    }
    return connfd;
}

int Read(int fd, map<int, string> &bufMap)
{
    struct iovec iov[2];
    char buf[MAX_LINE_LEN+1];
    char exbuf[65535]; // 如果一次read很多資料,則動用該緩衝區
    int nrcv;
    
    iov[0].iov_base = buf;
    iov[0].iov_len = MAX_LINE_LEN;
    
    iov[1].iov_base = exbuf;
    iov[1].iov_len = sizeof exbuf;
    
    nrcv = readv(fd, iov, 2);// 使用readv保證能將資料讀取完
    
    if(nrcv > MAX_LINE_LEN)
    {
        bufMap[fd] += string(buf) + string(exbuf); // test !
        printf("extrabuf in use! \n");
    }
    else if( nrcv > 0)
    {
        bufMap[fd] += string(buf);
    }
    else
    {
        return nrcv;
    }

    return nrcv;
}

int getSocketError(int fd)
{
    int optval;
    
    socklen_t optlen = static_cast<socklen_t>(sizeof optval);
    
    if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
    {
        return errno;
    }
    else
    {
        return optval;
    }
}

int main()
{
    struct sockaddr_in cli_addr, server_addr;
    vector<int> client(FD_SETSIZE,-1);
    map<int ,string> bufMap;// 簡易應用層緩衝區

    fd_set rset,wrset,allset;
    int listenfd, connfd, sockfd, maxfd, nready, ix,maxid, nrcv,flags,nwrt,one;
    char addr_str[INET_ADDRSTRLEN];

    int accepted = 0;

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SEVER_PORT);

    listenfd = socket(AF_INET,SOCK_STREAM,0);

    flags = fcntl(listenfd,F_GETFL,0);
    fcntl(listenfd,F_SETFL,flags | O_NONBLOCK);

    one = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,&one, sizeof(one));

    if(bind(listenfd,(struct sockaddr *)&server_addr,sizeof server_addr) < 0)
    {
        printf("socket bind error: %s\n",strerror(errno));
        return 0;
    }

    listen(listenfd,10);

    FD_ZERO(&rset);
    FD_ZERO(&wrset);
    FD_ZERO(&allset);
    FD_SET(listenfd,&allset);
    maxfd = listenfd;
    maxid = -1;


    while(1)
    {
        rset = allset;
        nready = select(maxfd + 1, &rset,&wrset,NULL,NULL);


        if(nready < 0)
        {
            printf("select error: %s\n",strerror(errno));
            exit(1);
        }

        if(FD_ISSET(listenfd, &rset))
        {

            connfd = Accept(listenfd,&cli_addr);

            printf("recieve from : %s at port %d\n", inet_ntop(AF_INET,&cli_addr.sin_addr,addr_str,INET_ADDRSTRLEN),cli_addr.sin_port);

            for(ix = 0; ix < static_cast<int>(client.size()); ix++)
            {
                if(client[ix] < 0)
                {
                    client[ix] = connfd;
                    break;
                }
            }

            printf("client[%d] = %d\n",ix,connfd);

            if( FD_SETSIZE == ix)
            {
                printf("too many client! \n");
                exit(1);
            }

            if( connfd > maxfd)
            {
                maxfd = connfd;
            }

            FD_SET(connfd, &allset);

            accepted++;
            printf("accepted: %d\n",accepted);

            if(ix > maxid)
            {
                maxid = ix;
            }
            
            if(--nready == 0)
            {
                continue;
            }

        }

        for(ix = 0; ix <= maxid; ix++)
        {
            if((sockfd = client[ix]) < 0)
            {
                continue;
            }

            if(FD_ISSET(sockfd,&rset))
            {
            
                int left_len = bufMap[sockfd].length();
                
                if( 0 == (nrcv = Read(sockfd,bufMap)))
                {
                    client[ix] = -1;
                    printf("close! \n");
                    FD_CLR(sockfd,&allset);
                    bufMap.erase(sockfd);
                    close(sockfd);
                }
                else if ( nrcv > 0)
                {
                    printf("nrcv = %d \n",nrcv);

                    nrcv += left_len;//next time when client write to

                    //nwrt = write(sockfd,bufMap[sockfd].c_str(),200);// 模擬還有剩餘
                    nwrt = write(sockfd,bufMap[sockfd].c_str(),nrcv);

                    if(nwrt < 0)
                    {
                        if( errno != EWOULDBLOCK)
                        {
                            printf("Write error: %s\n", strerror(errno));
                        }
                    }

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

                    if(nwrt == nrcv) // 全部寫到了核心緩衝區
                    {
                        bufMap[sockfd].clear();
                        //bufMap[sockfd].erase(0,nrcv);
                        if(FD_ISSET(sockfd,&wrset))
                        {
                            FD_CLR(sockfd,&wrset);
                        }
                    }
                    else // 還有剩餘
                    {
                        printf("write left \n");
                        bufMap[sockfd].erase(0,nwrt);
                        std::cout << " after erase: "<<bufMap[sockfd] <<std::endl;
                        FD_SET(sockfd,&wrset);//開始關注寫事件
                    }

                }
                else
                {
                    int err = getSocketError(sockfd);
                    
                    printf("SocketError: %s\n",strerror(err));
                }
            }

            if(FD_ISSET(sockfd,&wrset))
            {
                nrcv = bufMap[sockfd].size();
                printf("write again: nrcv left = %d \n",nrcv);
                nwrt = write(sockfd,bufMap[sockfd].c_str(),nrcv);

                if(nwrt == nrcv)
                {
                    bufMap[sockfd].clear();
                    if(FD_ISSET(sockfd,&wrset))
                    {
                        FD_CLR(sockfd,&wrset);
                    }
                    printf("Write complete! \n");
                }
                else
                {
                    bufMap[sockfd].erase(0,nwrt);
                }
            }

            if(--nready == 0)
            {
                break;
            }
        }
    }

    return 0;
}

 

相關文章