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;
}