關於select函數

關於select函數:
其函數原型爲:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct tim *timeout);
此函數的功能是由內核檢測在timeout時間內,是否有readfds,writefds,exceptfds三個句柄集(file descriptors)裏的某個句柄(file descriptor)的狀態符合尋求,即readfds句柄集裏有句柄可讀或writefds句柄集裏有可寫或exceptfds句柄集裏有例外發生,任何一個有變化函數就立即返回,返回值爲timeout發生狀態變化的句柄個數。
n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)裏編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內是否分別可讀和可寫就可以這樣:
先把兩個句柄集(file descriptors)清零:

       FD_ZERO (&readfds);
        FD_ZERO (&writefds);
然後把fd1加入讀檢測集:
        FD_SET (fd1, &readfds);
然後把fd2加入寫檢測集:
        FD_SET (fd2, &writefds);
再給timeout設置值,timeout是這樣的一個結構:
              struct tim {
                  long    tv_sec;        
                  long    tv_usec;       
              };
你可以這樣賦值:
        timeout.tv_sec=1;
        timeout.tv_uec=0;
表示檢測在1秒鐘內是否有句柄狀態發生變化。
如果有句柄發生變化,就可以用FD_ISSET檢測各個句柄,比如:
                FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
                FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
示意程序代碼如下:

    fd1 = socket();//創建一個socket
    fd2 = socket();//創建一個socket
    while(1) {
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
        FD_SET (fd1, &readfds);
        FD_SET (fd2, &writefds);
        timeout.tv_sec=1;
        timeout.tv_uec=0;
        ret = select(fd1>fd2?(fd1+1):(fd2+1), &readfds, &writefds, NULL, &timeout);
        if(ret < 0) {printf("系統錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}
        else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態發生變化!");}
        //有句柄狀態發生了變化
        if(FD_ISSET(fd1, &readfds)) {
            fd1有數據可讀;
            fd1裏的數據被讀出來;
        }
        if(FD_ISSET(fd2, &writefds)) {
            fd2可寫;
            fd2裏發送數據給對方;
        }
    }

經常用到的幾個自定義函數:
1、開啓監聽的函數

int
OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {

    int    sockfd = 0, ret = 0, opt = 0, flags=1;
    struct sockaddr_in    laddr;
    ret = sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer socket() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -1;
    }
    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseORnot, sizeof(int));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() reuse error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }
    ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuflen, sizeof(int));
    if ( ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() recvbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -3;
    }
    ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuflen, sizeof(int));
    if (ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer setsockopt() sendbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        return -4;
    }
    ioctl(sockfd,FIONBIO,&blockORnot);
    laddr.sin_family = PF_INET;
    laddr.sin_port = htons(port);
    laddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(laddr.sin_zero), 8);
    ret = bind(sockfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr));
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer bind() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -5;
    }
    ret = listen(sockfd, total);
    if(ret < 0)    {
        sprintf(errorMessage, "OpenTCPServer listen() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
        close(sockfd);
        return -6;
    }
    sprintf(errorMessage, "OpenTCPServer opened on port.%d(%d) OK, socket(%d), buf(%d:%d)! %s", port, total, sockfd, sendbuflen, recvbuflen, GetCurrentTime(0, 0));
    return sockfd;
}

2、連接服務器的函數

int
ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {

    int    serversock = 0, ret = 0;
    unsigned long    addr;
    struct sockaddr_in    sin;
    struct hostent *he;
    if((he=gethostbyname(serverip))== 0) {
        sprintf(errorMessage, "ConnectSCPServer IP address '%s' error! return:-1 %s", serverip, GetCurrentTime(0, 0));
        return -1;
    }
    serversock = socket(PF_INET, SOCK_STREAM, 0);
    if(serversock == -1)    {
        sprintf(errorMessage, "ConnectSCPServer socket() error! return:-2, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        return -2;
    }
    ioctl(serversock, FIONBIO, &blockORnot); //block or not
    memset((char*)&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = PF_INET;
    sin.sin_port = htons(serverport);
    sin.sin_addr = *((struct in_addr *)he->h_addr);
    ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin));
    if(ret == -1)    {
        sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
        close(serversock);
        return -3;
    }
    return serversock;
}

3、發送數據函數Send

int
Send(int sock, char * buf, size_t size, int flag, int timeout)    {

    int i = 0, ret = 0, intretry = 0;
    struct tim tival;
    fd_set writefds;
    int maxfds = 0;
    tival.tv_sec = timeout;
    tival.tv_usec = 0;
    FD_ZERO(&writefds);
    if(sock > 0) {
        FD_SET(sock, &writefds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Send socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }
    ret = select(maxfds + 1, NULL, &writefds, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Send socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Send socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &writefds)))    {
        sprintf(errorMessage, "Send socket:%d not in writefds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }
    while(i < size)    {
        ret = send(sock, buf + i, size - i, flag);
        if(ret <= 0)    {
            sprintf(errorMessage, "Send socket:%d send() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
            if (EINTR == errno)
              if(intretry < 10) {intretry++;continue;}
              else sprintf(errorMessage, "Send socket:%d send() error!EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Send socket:%d send() OK! %d/%d bytes sent! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}

4、接收數據函數Recv

int
Recv(int sock, char * buf, size_t size, int flag, int timeout)    {

    int i = 0, ret = 0, intretry = 0;
    struct tim tival;
    fd_set readfds;
    int maxfds = 0;
    tival.tv_sec = timeout;
    tival.tv_usec = 0;
    FD_ZERO(&readfds);
    if(sock > 0) {
        FD_SET(sock, &readfds);
        maxfds=((sock > maxfds)?sock:maxfds);
    }
    else    {
        sprintf(errorMessage, "Recv socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
        return -2;
    }
    ret = select(maxfds + 1, &readfds, NULL, NULL, &tival);
    if(ret <= 0) {
        if(ret < 0)    sprintf(errorMessage, "Recv socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
        else sprintf(errorMessage, "Recv socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
        close(sock);
        return -3;
    }
    if(!(FD_ISSET(sock, &readfds)))    {
        sprintf(errorMessage, "Recv socket:%d not in readfds! %s", sock, GetCurrentTime(0, 0));
        close(sock);
        return -4;
    }
    while(i < size)    {
        ret = recv(sock, buf + i, size - i, flag);
        if(ret <= 0){
            sprintf(errorMessage, "Recv socket:%d recv() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
            if(errno == EINTR)   
                if(intretry < 10) {intretry++;continue;}
                else sprintf(errorMessage, "Recv socket:%d recv() error! EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
            close(sock);
            return -1;
        }
        else i += ret;
    }
    sprintf(errorMessage, "Recv socket:%d recv() OK! %d/%d bytes received! %s", sock, i, size, GetCurrentTime(0, 0));
    return i;
}
最後需要說明的是:我這裏講到的源程序並不能實際地作爲一個產品程序來用,實際情況下可能會有其它許多工作要做,比如可能要建立共享隊列來存放 socket裏讀到的消息,也可能把發送消息先進行排隊然後再調用Send函數。還有,如果不是全數字,在發送前一定要htonl轉換爲網絡字節序,同理接收到後一定要先ntohl由網絡字節序轉換爲主機字節序,否則對方發送過來的0x00000001在你這裏可能是0x00010000,因爲高低位順序不同。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章