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);
    //SO_REUSEADDR这一步好像是必须要设置的,我还没理解这个选项。
    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);

    
    //louis: 下面的4行好像和 while里面的重复了,没有意义吧。
    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 
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自blog.csdn.net/liuxizhen2009/article/details/83690767