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#include #include #include #include #include #include #include #include #include #include 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(8080); 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#include #include #include #include #include #include #include #include #include #include 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;}