1. 程式人生 > >Linux socket程式設計示例3 select函式的使用

Linux socket程式設計示例3 select函式的使用

1.select函式簡介

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);

當伺服器響應多個客戶端連線的時候,需要定義一個執行緒函式,在每一個執行緒函式裡面處理該連線,進行資料的讀寫,且connect、accept、recv或recvfrom這樣的函式都是阻塞的。

現在想不用執行緒函式就實現伺服器響應多個客戶端的連線,就可以使用select函式,且是非阻塞的,可以查詢是哪個客戶端的響應。

引數maxfd是需要監視的最大的檔案描述符值+1;  rdset,wrset,exset分別對應於需要檢測的可讀檔案描述符的集合,可寫檔案描述符的集 合及異常檔案描述符的集合。  struct timeval結構用於描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函式返回,返回值為0。  fd_set(它比較重要所以先介紹一下)是一組檔案描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set型別通過下面四個巨集來操作:  FD_ZERO(fd_set *fdset);將指定的檔案描述符集清空,在對檔案描述符集合進行設定前,必須對其進行初始化,如果不清空,由於在系統分配記憶體空間後,通常並不作清空處理,所以結果是不可知的。  FD_SET(fd_set *fdset);用於在檔案描述符集合中增加一個新的檔案描述符。  FD_CLR(fd_set *fdset);用於在檔案描述符集合中刪除一個檔案描述符。  FD_ISSET(int fd,fd_set *fdset);用於測試指定的檔案描述符是否在該集合中。 2.用select實現伺服器響應多個客戶端的連線。

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/time.h>




int main()
{
    int nSocketFd = 0;
    int clifd = 0;
    struct sockaddr_in stServAddr,stClientAddr;
    socklen_t socketAddrLen;
    int nRet = 0;
    char szBuff[BUFSIZ] = {0};
    int nReadSocketLen = 0;
    pthread_t tid;
    int isReuse = 1;

    struct timeval tv;
    int maxfd = 0;
    int retval = 0;
    fd_set readfds;
    int selectFd[100] = {0};
    int selectCount = 0;
    int index = 0;
    int nRdSocketLen = 0;
    char szIP[100][20] = {0};

    /** 產生一個套介面的描數字 */
    nSocketFd = socket(AF_INET,SOCK_STREAM,0);

    memset(&stServAddr,0,sizeof(struct sockaddr_in));

    stServAddr.sin_family = AF_INET;
    stServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    stServAddr.sin_port = htons(8888);

    setsockopt(nSocketFd,SOL_SOCKET,SO_REUSEADDR,(const char*)&isReuse,sizeof(isReuse));

    /** 把這個套接字描述符和本地地址繫結起來 */
    nRet = bind(nSocketFd,(struct sockaddr*)&stServAddr,sizeof(stServAddr));
    if(-1 == nRet)
    {
        perror("bind failed :");
        close(nSocketFd);
        return -1;
    }

    /** 設定該套介面的監聽狀態, */
    listen(nSocketFd,1024);


    FD_ZERO(&readfds);
    FD_SET(nSocketFd,&readfds);
    tv.tv_sec = 10;
    tv.tv_usec = 0;
    maxfd = nSocketFd;

    while(1)
    {
        FD_ZERO(&readfds);
        FD_SET(nSocketFd,&readfds);
        tv.tv_sec = 10;
        tv.tv_usec = 0;
        maxfd = nSocketFd;

        /* 把所有的sock描述符都放入到這個描述符集中去 */
        for(index = 0;index < selectCount;index++)
        {
            FD_SET(selectFd[index],&readfds);
            if(selectFd[index] > maxfd)
            {
                maxfd = selectFd[index];
            }
        }

        retval = select(maxfd+1,&readfds,NULL,NULL,&tv);

        /* 出錯 */
        if(retval < 0)  
        {
            perror("select");
        }

        /* 當沒有響應 */
        if(retval == 0)
        {
            continue;
        }       

        memset(szBuff,0,BUFSIZ);
        /* 判斷是哪個客戶端的響應 */
        for(index = 0; index < selectCount;index++)
        {
            if(FD_ISSET(selectFd[index],&readfds))
            {
                nRdSocketLen = read(selectFd[index],szBuff,BUFSIZ);

                /* 資料回發 */
                write(selectFd[index],szBuff,strlen(szBuff));
                if(nRdSocketLen > 0)
                {
                    printf("read data from %s : %s\n",szIP[index],szBuff);
                }
            }
        }
        /* 當有新的客戶端連線進來 */
        if(FD_ISSET(nSocketFd,&readfds))
        {
            /** 監聽連線,如果有主機要連線過來,則建立套介面連線 */
            socketAddrLen = sizeof(struct sockaddr_in);
            clifd = accept(nSocketFd,(struct sockaddr*)&stClientAddr,&socketAddrLen);
            if(-1 == clifd)
            {
                perror("accept error: ");
                return -1;
            }
            else
            {
                selectFd[selectCount] = clifd;

                /* 把每一個ip地址存放起來*/
                strncpy(szIP[selectCount],inet_ntoa(stClientAddr.sin_addr),20);
                selectCount++;

                printf("commect %s %d successful\n",inet_ntoa(stClientAddr.sin_addr),ntohs(stClientAddr.sin_port));//ntohs(stClientAddr.sin_port)
            }
        }
    }
    close(nSocketFd);

    return 0;
}


另一個例程判斷是從終端讀取資料,還是socket發過來的資料。

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/time.h>




int main(int argc,char* argv[])
{

    int nSocketFd = 0;
    char szBuff[BUFSIZ] = {0};
    struct hostent *pstHostName = NULL; 
    int nRdSocketLen = 0;
    int nRet = 0;
    struct sockaddr_in stClientAddr;
    pthread_t tid;

    fd_set rfds;
    struct timeval tv;
    int maxfd = 0;
    int retval = 0;
    int nWrSocketLen = 0;

    /** 判斷有沒有輸入ip地址 */
    if(2 != argc)
    {
        printf("please input telnet ipaddress\n");
        return -1;
    }

    /** 把輸入的主機名變為主機名結構體 */
    pstHostName = gethostbyname(argv[1]);
    if(NULL == pstHostName)
    {
        perror("gethostbyname failed\n");
        return -1;
    }

    /** 獲得socket的檔案描述符 */
    nSocketFd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == nSocketFd)
    {
        perror("create socket failed : ");
        return -1;
    }

    /** 把socket的檔案描述符的結構體清空 */
    memset(&stClientAddr,0,sizeof(struct sockaddr_in));

    /** 給該結構體賦值 */
    stClientAddr.sin_family = AF_INET;
    stClientAddr.sin_port = htons(8080);
    stClientAddr.sin_addr =*((struct in_addr *)pstHostName->h_addr);

    /** 連線目標的主機的ip */
    nRet = connect(nSocketFd,(struct sockaddr *)&stClientAddr,sizeof(stClientAddr));
    if(-1 == nRet)
    {
        perror("connect ");
        return -1;
    }
    else
    {
        printf("connect hostname %s successful\n",argv[1]);
    }


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

    while(1)
    {
        FD_ZERO(&rfds);
        FD_SET(0,&rfds);//設定鍵盤響應
        FD_SET(nSocketFd,&rfds);
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        retval = select(maxfd+1,&rfds,NULL,NULL,&tv);
        if(retval == -1)
        {
            perror("select:");
            return 0;
        }

        if(retval == 0) //沒有響應
        {
            continue;
        }

        memset(szBuff,0,BUFSIZ);
        if(FD_ISSET(0,&rfds)) //是鍵盤響應
        {
            /** 從終端讀取資料 */
            read(STDIN_FILENO,szBuff,BUFSIZ);

            /** 把從終端讀取的資料傳送出去 */
            nWrSocketLen = write(nSocketFd,szBuff,strlen(szBuff));
            if(nWrSocketLen > 0)
            {
                printf("send message successful\n");
            }
        }
        if(FD_ISSET(nSocketFd,&rfds))
        {
            /** 把伺服器端的資料傳送過來 */
            nRdSocketLen = read(nSocketFd,szBuff,BUFSIZ);
            if(nRdSocketLen > 0)
            {
                printf("read data:%s\n",szBuff);
            }
        }


    }

    return 0;
}

---------------------  作者:dmfrm  來源:CSDN  原文:https://blog.csdn.net/u010889616/article/details/48142483  版權宣告:本文為博主原創文章,轉載請附上博文連結!